2012-10-22 15 views
9

Devo fornire un callback in stile C per una libreria C specifica in un'app iOS. La richiamata non ha void *userData o qualcosa di simile. Quindi non sono in grado di eseguire il ciclo in un contesto. Vorrei evitare di introdurre un contesto globale per risolvere questo problema. Una soluzione ideale sarebbe un blocco Objective-C.C'è un modo per racchiudere un blocco ObjectiveC nel puntatore di funzione?

La mia domanda: c'è un modo per "castare" un blocco in un puntatore a funzione o per avvolgerlo/mascherarlo in qualche modo?

risposta

6

Tecnicamente, è possibile ottenere l'accesso a un puntatore di funzione per il blocco. Ma è del tutto pericoloso farlo, quindi certamente non lo consiglio. Per vedere come, si consideri il seguente esempio:

#import <Foundation/Foundation.h> 

struct Block_layout { 
    void *isa; 
    int flags; 
    int reserved; 
    void (*invoke)(void *, ...); 
    struct Block_descriptor *descriptor; 
}; 

int main(int argc, char *argv[]) { 
    @autoreleasepool { 
     // Block that doesn't take or return anything 
     void(^block)() = ^{ 
      NSLog(@"Howdy %i", argc); 
     }; 

     // Cast to a struct with the same memory layout 
     struct Block_layout *blockStr = (struct Block_layout *)(__bridge void *)block; 

     // Now do same as `block()': 
     blockStr->invoke(blockStr); 




     // Block that takes an int and returns an int 
     int(^returnBlock)(int) = ^int(int a){ 
      return a; 
     }; 

     // Cast to a struct with the same memory layout 
     struct Block_layout *blockStr2 = (struct Block_layout *)(__bridge void *)returnBlock; 

     // Now do same as `returnBlock(argc)': 
     int ret = ((int(*)(void*, int a, ...))(blockStr2->invoke))(blockStr2, argc); 
     NSLog(@"ret = %i", ret); 
    } 
} 

esecuzione che i rendimenti:

Howdy 1 
ret = 1 

che è quello che ci si aspetterebbe da puramente esecuzione di quei blocchi direttamente con block(). Quindi, è possibile utilizzare invoke come puntatore funzione.

Ma come ho detto, questo è totalmente pericoloso. In realtà non usare questo!

Se volete vedere un write-up di un modo per fare quello che stai chiedendo, quindi check this out: http://www.mikeash.com/pyblog/friday-qa-2010-02-12-trampolining-blocks-with-mutable-code.html

E 'solo un grande write-up di quello che si avrebbe bisogno di fare per farlo funzionare. Purtroppo, non funzionerà mai su iOS (dal momento che è necessario contrassegnare una pagina come eseguibile che non è possibile fare all'interno della sandbox della tua app). Ma comunque, un grande articolo.

+0

Si prega di scusare la mia ignoranza, ma perché è esattamente pericoloso? Suppongo sia perché la struttura è interna e potrebbe cambiare in futuro, giusto? –

+0

Proprio così :-). – mattjgalloway

+0

Il problema con questo è che richiede ancora di passare il blocco alla funzione invoke, mentre l'OP dice che non è possibile passare un contesto al callback. – newacct

4

Se il blocco richiede informazioni di contesto e la richiamata non offre alcun contesto, temo che la risposta sia chiara. I blocchi devono memorizzare le informazioni di contesto da qualche parte, quindi non sarà mai possibile lanciare un blocco di questo tipo in un puntatore a funzione senza argomenti.

Un approccio variabile globale attentamente progettato è probabilmente la soluzione migliore in questo caso.

1

MABlockClosure può fare esattamente questo. Ma potrebbe essere eccessivo per qualsiasi cosa tu abbia bisogno.

0

So che questo è stato risolto ma, per le parti interessate, ho un'altra soluzione.

Rimappa l'intera funzione in un nuovo spazio indirizzo. Il nuovo indirizzo risultante può essere utilizzato come chiave per i dati richiesti.

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

Ricordati di gestire le pagine che non sono necessari più e notare che ci vuole molto di più memoria per l'invocazione di MABlockClosure.

(Testato su iOS)

Problemi correlati