在没有回调的情况下将选择器分配给Objective-C中的C函数

时间:2017-12-20 06:19:51

标签: objective-c c cocoa selector

我正在尝试Obj-C中的方法调配,但我想传递一个纯C函数。这意味着我需要以某种方式分配选择器和/或手动构建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里面(我想避免),即使这样也没有用。

2 个答案:

答案 0 :(得分:2)

你无法调动C函数本身; swizzling基于方法查找,该方法查找通过方法描述(由运行时函数由Method类型表示)并且C函数没有方法描述。

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

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

所以你有三个主要选择:

  1. 最简单的方法是定义一个方法,其身体是你的“纯”C函数,然后调整推荐方式(注意正确处理继承,请参阅this answer)。

    < / LI>
  2. 如果你真的想编写C函数并且C函数需要调用该方法的原始实现,那么:

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

    • 如果您正在编写/拥有C函数的源代码,您只需将其定义为采用上述两个隐式参数。获取此函数的地址并将其强制转换为IMP,这对于适当类型的C函数指针只是typedef,可在下面使用。
    • 如果您正在使用其定义无法更改的C函数,则可以执行以下操作之一:
      • 编写一个C包装函数,它接受额外的参数,忽略它们并调用目标C函数。获取此包装函数的地址并将其转换为IMP以供下面的使用。
      • 中包含对C函数的调用,并使用imp_implementationWithBlock()从中生成IMP值。有关使用imp_implementationWithBlock()
      • 的说明,请参阅this article

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

  3. 如果你真的想编写C函数并且C函数 需要调用该方法的原始实现,那么你需要在你的类中添加一个方法,其实现是你的C函数 - 修改/包装在(2)中,然后用你原来的方法调入你添加的方法,如(1)所示,这样原始实现仍然可用作方法。要添加方法,请使用class_addMethod()

  4. HTH

答案 1 :(得分:2)

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

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

#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上测试)