动态定义C函数

2019-09-28  本文已影响0人  skogt

上一篇文章中我们了解了函数指针,函数地址,调用惯例及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/

上一篇下一篇

猜你喜欢

热点阅读