2012-12-04 9 views
5

Ho una variabile con un blocco che accetta alcuni argomenti. Il numero esatto di argomenti e i loro tipi possono variare. Ad esempio può essere un bloccoBlocco di chiamata con argomenti da va_list quando gli argomenti del blocco numero e tipi possono variare

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

o un blocco

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

tipi di argomenti sono limitati a {id, BOOL, char, int, unsigned int, float}.

Conosco il conteggio corrente degli argomenti e dei relativi tipi. Ho bisogno di implementare un metodo che può eseguire il blocco con determinati argomenti:

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

ho una soluzione di lavoro ingenuo, ma è abbastanza brutto, supporta solo i tipi di alcuna dimensione più di 4 byte e si basa sull'allineamento. Quindi sto cercando qualcosa di meglio. La mia soluzione è qualcosa di simile:

#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; 
    } 
} 

risposta

6

Ebbene si sono in esecuzione contro il classico mancanza-di-metadati e problema ABI in C qui. Basato su Awesome Article about MABlockClosure di Mike Ash, penso che potresti esaminare la struttura sottostante del blocco e assumere che la va_list corrisponda a ciò che il blocco si aspetta. Puoi lanciare il blocco su struct Block_layout, quindi block-> descriptor ti darà la struct BlockDescriptor. Quindi hai la stringa @encode che rappresenta gli argomenti e i tipi del blocco (@encode è un intero altro tipo di worm).

Quindi, una volta ottenuto l'elenco degli argomenti e dei relativi tipi, è possibile scavare nel blocco_layout, richiamare grab, quindi trattarlo come un puntatore a funzione in cui il primo parametro è il blocco che fornisce il contesto. Mike Ash ha anche alcune informazioni su Trampolining Blocks che potrebbero funzionare se non ti interessa nessuna delle informazioni sul tipo, ma vuoi semplicemente richiamare il blocco.

Lasciami aggiungere un grosso avviso "Ci sono dei draghi qui". Tutto ciò è molto permaloso, varia in base all'ABI e si basa su caratteristiche oscure e/o non documentate.

Sembra anche che si possa chiamare il blocco direttamente dovunque fosse necessario, possibilmente utilizzando un NSArray come unico parametro e id come tipo di ritorno. Quindi non devi preoccuparti di alcun attacco "intelligente" che ti ritorti contro.

modifica: è possibile utilizzare la firma NSMethodSignatureWithObjCTypes :, passando la firma del blocco. Quindi puoi chiamare la chiamata di NSInvocationWithMethodSignature :, ma dovrai chiamare il metodo private invokeWithIMP: in realtà per attivarlo perché non hai un selettore. Imposterai il target sul blocco, quindi invocheraiWithIMP, passando il puntatore del richiamo della struct Block. Vedi Generic Block Proxying

+1

Grazie per la risposta. Ho già la lista degli argomenti e dei loro tipi. E sì, posso trovare e prendere il puntatore a funzione _invoke_. Ma come posso in particolare definirlo meglio delle mie macro 'BLOCK_ARG (N)' per la trasmissione e una matrice temporanea per gli argomenti? C'è qualche altra soluzione tranne come 'ffi_call'? L'uso di ** libffi ** sembra troppo sovraccarico per il mio piccolo compito. – Yan

+0

Bene, ecco la cosa: devi usare l'assembly per impostare i parametri nel modo in cui C si aspetta. Questo è ciò che NSInvocation e altri budella del runtime Objective-C fanno perché in C gli argomenti devono essere disposti in certi registri, riversandosi nello stack in un certo modo. Questo cambia in base alla piattaforma (x86, x64, ARM). – russbishop

0

Una buona alternativa all'utilizzo del private invokeWithIMP: consiste nel far girare il metodo che si desidera avere implementazioni differenti e farlo cercare l'IMP desiderato ogni volta che viene chiamato. Usiamo qualcosa di simile in: https://github.com/tuenti/TMInstanceMethodSwizzler

0

La vera soluzione è avere un blocco con un parametro va_list e lasciare che il blocco lo risolva da solo. Questo è un metodo provato e semplice.