美文网首页
动态定义C函数

动态定义C函数

作者: skogt | 来源:发表于2019-09-28 16:11 被阅读0次

    上一篇文章中我们了解了函数指针,函数地址,调用惯例及libffi动态调用C函数的例子,最后也提出一个问题:如果动态定义C函数,支持不同类型及个数的参数呢

    libffi同样支持在运行时动态定义C函数

    先举个例子看下怎么使用的

    void JPBlockInterpreter(ffi_cif *cif, void *ret, void **args, void *userdata)
    {
        //①
        //函数实体
        //通过 userdata / args 提取参数
        //返回值赋给 ret
    }
    
    void main() {
        //②
        ffi_type *returnType = &ffi_type_void;
        NSUInteger argumentCount = 2;
        ffi_type **_args = malloc(sizeof(ffi_type *)*argumentCount) ;
        _args[0] = &ffi_type_sint;
        _args[1] = &ffi_type_pointer;
        ffi_cif *_cifPtr = malloc(sizeof(ffi_cif));
        ffi_prep_cif(_cifPtr, FFI_DEFAULT_ABI, (unsigned int)argumentCount, returnType, _args);
    
        //③
        void *blockImp = NULL;
    
        //④
        ffi_closure *_closure = ffi_closure_alloc(sizeof(ffi_closure), (void **)&blockImp);
        ffi_prep_closure_loc(_closure, _cifPtr, JPBlockInterpreter, (__bridge void *)self, blockImp);
    
    }
    

    流程:

    1. 准备一个通用的函数JPBlockInterpreter
    2. 根据函数参数个数,参数类型返回值类型封装cif对象,表示原型
    3. 准备一个函数指针(壳),用于调用
    4. 通过ffi_closure把实现函数原型_cifPtr / 函数实体JPBlockInterpreter / 上下文对象self / 函数指针blockImp 关联起来。
    5. 调用函数指针blockImp的时候,会调用JPBlockInterpreter。其中JPBlockInterpreter的userdata就是传入的上下文

    上述例子中,这一系列处理后 blockImp 就可以当成一个指向返回值类型是void,参数类型是 (int a, NSString *b) 的函数去调用,调用后会去到 JPBlockInterpreter 这个函数实体,在这个函数里面可以通过 args 提取传进来的参数,通过userdata 取上下文进行处理。这里可以根据参数类型的不同动态创建不同的 cif 对象,生成不同的 blockImp 函数指针。

    block

    在之前一篇文章中聊过block,了解到block本质是个OC对象。此外,block是个匿名函数,block在编译后会生成相应的结构体和函数指针,这边就不详细阐述。简单的来讲,block是一个包含函数指针的结构体。

    举个例子:

    struct JPSimulateBlock {
        void *isa;
        int flags;
        int reserved;
        void *invoke;
        struct JPSimulateBlockDescriptor *descriptor;
    };
    
    struct JPSimulateBlockDescriptor {
        unsigned long int reserved;
        unsigned long int size;
    };
    
    void blockImp(){
        NSLog(@"call block succ");
    }
    
    void genBlock() {
        struct JPSimulateBlockDescriptor descriptor = {0, sizeof(struct JPSimulateBlock)};
    
        struct JPSimulateBlock simulateBlock = {
            &_NSConcreteStackBlock,
            0, 0, blockImp, &descriptor
        };
    
        void *blockPtr = &simulateBlock;
        void (^blk)() = ((__bridge id)blockPtr);
        blk();  //output "call block succ"
    }
    

    一个存有函数指针的特定结构体就是一个 block,调用这个 block 就是调用里面函数指针指向的函数

    signature

    这里需要补充下block的签名。block签名中包含了参数类型和个数相关信息,是构建cif模板的必要条件。

    一个 block 的签名格式是:[返回值类型和偏移][@?0][参数0类型和偏移][参数1类型和偏移]…,比如 arm64 下 int (^block)(int, int) 的签名是 i16@?0i8i12。block 指针占 8 字节,参数和返回值 int 都是 4 字节。

    然后需要把 signature 字符串处理分拆成参数类型列表,在 libffi 中使用 ffi_type 表示各种类型。

    有了参数类型列表,返回值类型,参数个数后,就可以调用 ffi_prep_cif() 函数创建 ffi_cif 了

    block 完整的数据结构如下

    enum {
      BLOCK_DEALLOCATING =      (0x0001),  // runtime
      BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
      BLOCK_NEEDS_FREE =        (1 << 24), // runtime
      BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
      BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
      BLOCK_IS_GC =             (1 << 27), // runtime
      BLOCK_IS_GLOBAL =         (1 << 28), // compiler
      BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
      BLOCK_HAS_SIGNATURE  =    (1 << 30)  // compiler
    };
    
    // revised new layout
    
    #define BLOCK_DESCRIPTOR_1 1
    struct Block_descriptor_1 {
      unsigned long int reserved;
      unsigned long int size;
    };
    
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
      // requires BLOCK_HAS_COPY_DISPOSE
      void (*copy)(void *dst, const void *src);
      void (*dispose)(const void *);
    };
    
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
      // requires BLOCK_HAS_SIGNATURE
      const char *signature;
      const char *layout;
    };
    
    struct Block_layout {
      void *isa;
      volatile int flags; // contains ref count
      int reserved;
      void (*invoke)(void *, ...);
      struct Block_descriptor_1 *descriptor;
      // imported variables
    };
    

    block 的 flags 有两个位 BLOCK_HAS_COPY_DISPOSE(1 << 25) / BLOCK_HAS_SIGNATURE(1 << 30) 分别表示这个 block 的 descriptor 有没有 Block_descriptor_2 和 Block_descriptor_3 这两组数据。block 的签名就保存在 Block_descriptor_3 结构体里。所以如果我们要让 block 有 signature 签名,就需要:block 的 flags 需要有 BLOCK_HAS_SIGNATURE 标记,表示这个 block 有 signature 数据。
    然后根据 block 的 flag 位掩码计算偏移拿到 Type Encoding 签名 signature

    杂谈

    目前这2天都在看libffi相关的东西,比如如何动态调用C函数,如何动态定义C函数,如何hook block之类的。hook block
    这块内容有点多,涉及到的细节也不少,等后面消化完了,有时间了记录下。

    因为前段时间在备考(寻找下一家),很久没更新了,趁这段时间赶紧补一下。另外也趁这段时间沉淀下,静一静(最近这段时间面试有点躁)。等国庆回来再观望下有没有合适的机会吧

    参考:http://blog.cnbang.net/tech/3332/

    相关文章

      网友评论

          本文标题:动态定义C函数

          本文链接:https://www.haomeiwen.com/subject/rzifpctx.html