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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在没有回调的情况下将选择器分配给Objective-C中的C函数相关的知识,希望对你有一定的参考价值。

我正在尝试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(我想避免)中,即使这样它也无效。

答案

你本身不能调侃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)。
  2. 如果你真的想编写一个C函数并且C函数不需要调用该方法的原始实现,那么: (a)您需要将C函数转换为可用作方法实现的函数。您可以: 如果您正在编写/拥有C函数的源代码,您只需将其定义为采用上述两个隐式参数。获取此函数的地址并将其转换为IMP,它只是适合类型的C函数指针的typedef,以供使用。 如果您使用的C函数的定义无法更改,则可以执行以下操作之一: 编写一个C包装函数,它接受额外的参数,忽略它们并调用目标C函数。获取此包装函数的地址并将其转换为IMP以供下面使用。 在块中包含对C函数的调用,并使用imp_implementationWithBlock()从中生成IMP值。您可以阅读this article以获取使用imp_implementationWithBlock()的描述。 (b)使用method_setImplementation()将实现设置为您在(a)中生成的IMP值。
  3. 如果你真的想编写一个C函数并且C函数确实需要调用该方法的原始实现,那么你需要在你的类中添加一个方法,它的实现是你的C函数 - 修改/包装在(2)中,然后使用原始方法调整添加的方法,如(1)所示,以便原始实现仍然可用作方法。要添加方法,请使用class_addMethod()

HTH

另一答案

这里的关键是找到一个在函数指针和上下文之间进行映射的机制。最简单的方法是生成一个新的函数指针。您可以使用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上测试过)

以上是关于在没有回调的情况下将选择器分配给Objective-C中的C函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有 Git 的情况下将 Git SHA1 分配给文件?

在laravel中没有包的情况下将一条路由分配给多个用户

Objective-C/CocoaHTTPServer - 是不是可以在没有任何 UI 的情况下将 CocoaHTTPServer 作为后台应用程序运行?

我们如何在不将 ViewController 对象推入其中的情况下将对象分配给 `UINavigationController`。

如何在不使用循环的情况下将json分配给另一个[重复]

如何在不为其分配内存的情况下将缓冲区传递给 write()