将选择器分配给 Objective-C 中的 C 函数而无需回调

Assign selector to C function in Objective-C without callback

我正在尝试在 Obj-C 中调整方法,但我想将它传递给纯 C 函数。这意味着我需要以某种方式分配一个选择器 and/or 手动构建一个 objc_method 结构。也许以某种方式利用 NSInvocation?

我的理解是,由于 Obj-C 是 C 的严格超集,因此完全兼容。

我现在要做的事情:

main.m :

#include....

CFStringRef strRet(void) {
    return CFSTR("retString");
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SEL _strRet = sel_registerName("strRet");
        //I also tried: SEL _strRet = NSSelectorFromString(@"strRet");

        Class bundle = objc_getClass("NSBundle");

        method_exchangeImplementations(
            class_getInstanceMethod(bundle, sel_registerName("anySelector")), 
            class_getInstanceMethod(bundle, sel_registerName("_strRet")
        );

我曾尝试将 C 函数放在 @implementation 中(我想避免这样做),但即使那样它也不起作用。

您不能调配 C 函数本身;调配基于方法查找,它通过方法描述(运行时函数由 Method 类型表示)并且 C 函数没有方法描述。

然而一个方法的实现只是一个C函数。这样的 C 函数必须至少有两个参数,即调用该方法的对象(Objective-C 隐式参数 self)和选择器(Objective-C 隐式参数 _cmd).当你调整一个方法时,替换实现,一个 C 函数,必须与原始的类型完全相同——完成两个隐式参数——所以你的 strRet() 不适合原样,你需要改变它至:

CFStringRef strRet(NSObject *self, CMD sel, void)
{
   return CFSTR("retString");
}

所以你有三个主要选择:

  1. 最简单的方法是定义一个方法,其主体是您的 "pure" C 函数,然后使用推荐的方法进行调整(注意正确处理继承,请参阅 ) .

  2. 如果你真的想写一个C函数并且那个C函数不需要调用方法的原始实现那么:

    (a) 您需要将 C 函数转换为可用作方法实现的函数。您可以:

    • 如果您是 writing/have C 函数的来源,您只需将其定义为采用上述两个隐式参数。获取此函数的地址并将其转换为 IMP,这只是一个 typedef 用于适当类型的 C 函数指针,供下面使用。
    • 如果您使用的是无法更改其定义的 C 函数,那么您可以执行以下操作之一:
      • 编写一个 C 包装函数,它接受额外的参数,忽略它们并调用您的目标 C 函数。获取此包装函数的地址并将其转换为 IMP 以供下面使用。
      • 将对 C 函数的调用包装在 块中 并使用 imp_implementationWithBlock() to produce an IMP value from it. You can read this article 描述使用 imp_implementationWithBlock().

    (b) 使用 method_setImplementation() 将实现设置为您在 (a) 中生成的 IMP 值。

  3. 如果您真的想编写一个 C 函数并且该 C 函数 确实 需要调用该方法的原始实现,那么您将需要添加一个将方法添加到 class,其实现是您的 C 函数 – modified/wrapped,如 (2) 中所示,然后将添加的方法与 (1) 中的原始方法混合,以便原始实现仍可用作方法。要添加方法,您可以使用 class_addMethod()

HTH

这里的关键是找到一种在函数指针和上下文之间进行映射的机制。最简单的方法是生成一个新的函数指针。您可以使用 imp_implementationWithBlock()、MABlockClosure 或自己滚动。

我发现创建新函数指针的最简单机制是将整个函数重新映射到新地址 space。新的结果地址可以用作所需数据的密钥。

#import <mach/mach_init.h>
#import <mach/vm_map.h>

void *remap_address(void* address, int page_count)
{
    vm_address_t source_address = (vm_address_t) address;
    vm_address_t source_page = source_address & ~PAGE_MASK;

    vm_address_t destination_page = 0;
    vm_prot_t cur_prot;
    vm_prot_t max_prot;
    kern_return_t status = vm_remap(mach_task_self(),
                                &destination_page,
                                PAGE_SIZE*(page_count ? page_count : 4),
                                0,
                                VM_FLAGS_ANYWHERE,
                                mach_task_self(),
                                source_page,
                                FALSE,
                                &cur_prot,
                                &max_prot,
                                VM_INHERIT_NONE);

    if (status != KERN_SUCCESS)
    {
        return NULL;
    }

    vm_address_t destination_address = destination_page | (source_address & PAGE_MASK);

    return (void*) destination_address;
}

请注意 page_count 应该足够大以包含所有原始函数。此外,请记住处理不再需要的页面,并注意每次调用比 MABlockClosure.

占用更多内存

(在 iOS 上测试)