2012-12-04 23 views
5

我有一个块接受一些参数的变量。参数的精确数量及其类型可能会有所不同。例如,它可以是嵌段当块的参数数量和类型可以变化时,调用来自va_list参数的块

void(^testBlock1)(int) = ^(int i){} 

或嵌段

void(^testBlock2)(NSString *,BOOL,int,float) = ^(NSString *str,BOOL b,int i,float f){} 

自变量类型被限制为{id, BOOL, char, int, unsigned int, float}

我知道参数及其类型的当前计数。我需要实现可在给定的参数执行块的方法:

-(void)runBlock:(id)block withArguments:(va_list)arguments 
      types:(const char *)types count:(NSUInteger)count; 

我有一个工作天真的解决方案,但它是相当难看,只支持类型不超过4个字节大小,并且依赖于定位。所以我正在寻找更好的东西。 我的解决办法是这样的:

#define MAX_ARGS_COUNT 5 
-(void)runBlock:(id)block withArguments:(va_list)arguments 
      types:(const char *)types count:(NSUInteger)count{ 

    // We will store arguments in this array. 
    void * args_table[MAX_ARGS_COUNT]; 

    // Filling array with arguments 
    for (int i=0; i<count; ++i) { 
     switch (types[i]) { 
      case '@': 
      case 'c': 
      case 'i': 
      case 'I': 
       args_table[i] = (void *)(va_arg(arguments, int)); 
       break; 
      case 'f': 
       *((float *)(args_table+i)) = (float)(va_arg(arguments, double)); 
       break; 
      default: 
       @throw [NSException exceptionWithName:@"runBlock" reason:[NSString stringWithFormat:@"unsupported type %c",types[i]] userInfo:nil]; 
       break; 
     } 
    } 

    // Now we need to call our block with appropriate count of arguments 

#define ARG(N) args_table[N] 

#define BLOCK_ARG1 void(^)(void *) 
#define BLOCK_ARG2 void(^)(void *,void *) 
#define BLOCK_ARG3 void(^)(void *,void *,void *) 
#define BLOCK_ARG4 void(^)(void *,void *,void *,void *) 
#define BLOCK_ARG5 void(^)(void *,void *,void *,void *,void *) 
#define BLOCK_ARG(N) BLOCK_ARG##N 

    switch (count) { 
     case 1: 
      ((BLOCK_ARG(1))block)(ARG(0)); 
      break; 
     case 2: 
      ((BLOCK_ARG(2))block)(ARG(0),ARG(1)); 
      break; 
     case 3: 
      ((BLOCK_ARG(3))block)(ARG(0),ARG(1),ARG(2)); 
      break; 
     case 4: 
      ((BLOCK_ARG(4))block)(ARG(0),ARG(1),ARG(2),ARG(3)); 
      break; 
     case 5: 
      ((BLOCK_ARG(5))block)(ARG(0),ARG(1),ARG(2),ARG(3),ARG(4)); 
      break; 
     default: 
      break; 
    } 
} 

回答

6

那么你是针对缺的元数据在C和ABI问题的经典在这里跑起来。基于Mike Ash的Awesome Article about MABlockClosure,我认为你可以检查块的底层结构,并假定va_list与块的期望值相匹配。您可以将该块转换为结构Block_layout,然后block-> descriptor将为您提供struct BlockDescriptor。然后你有@encode字符串,它代表块的参数和类型(@encode是其他蠕虫的全部)。因此,一旦你有参数列表和它们的类型,你可以挖掘到block_layout,抓取调用,然后将其视为一个函数指针,其中第一个参数是提供上下文的块。 Mike Ash也有一些关于Trampolining Blocks的信息,如果您不关心任何类型信息,但只想调用该块,则可能会有效。

让我添加一个大胖子“有龙在这里”的警告。这一切都非常敏感,根据ABI而变化,并且依赖于模糊和/或未记录的特征。

它似乎也可以直接在需要的地方直接调用块,可能使用NSArray作为唯一参数,并将id作为返回类型。那么你不必担心任何“巧妙”的黑客攻击你。

编辑:您可能能够使用NSMethodSignature的signatureWithObjCTypes:传递该块的签名。然后你可以调用NSInvocation的invocationWithMethodSignature :,但是你必须调用private的invokeWithIMP:方法来实际触发它,因为你没有选择器。您将目标设置为块,然后invokeWithIMP,传递Block结构的调用指针。请参阅Generic Block Proxying

+1

谢谢你的回答。 我已经有参数列表和它们的类型。是的,我可以找到并获取_invoke_函数指针。但是我怎么能特别以比我的'BLOCK_ARG(N)'宏更好的方式来调用它,以及用于参数的临时数组呢?除了'ffi_call'之外,还有其他解决方案吗?使用** libffi **对于我的小任务看起来太高了。 – Yan

+0

嗯,这是事情 - 你必须使用程序集来设置C预期的参数。这就是NSInvocation和Objective-C运行库的其他许多内容,因为在C中,参数必须在某些寄存器中进行布局,并以某种方式溢出到堆栈上。这种变化基于平台(x86,x64,ARM)。 – russbishop

0

真正的解决方案是用va_list参数创建一个块,并让块自行排序。这是一种经过验证的简单方法。