2012-10-22 8 views
9

Muszę zapewnić wywołania zwrotnego w stylu C dla określonej biblioteki C w aplikacji na iOS. Callback nie ma wartości void *userData lub czegoś podobnego. Więc nie jestem w stanie zapętlić kontekstu. Chciałbym uniknąć wprowadzenia globalnego kontekstu, aby rozwiązać ten problem. Idealnym rozwiązaniem byłby blok Objective-C.Czy istnieje sposób na zawinięcie bloku ObjectiveC do wskaźnika funkcji?

Moje pytanie: Czy istnieje sposób na "wrzucenie" bloku do wskaźnika funkcji lub jakoś go owinąć/okryć?

Odpowiedz

6

Technicznie można uzyskać dostęp do wskaźnika funkcji dla bloku. Ale jest to całkowicie niebezpieczne, więc z pewnością nie polecam tego. Aby zobaczyć, jak Rozważmy następujący przykład:

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

Running że plony:

Howdy 1 
ret = 1 

Która jest to, czego oczekujemy od czysto wykonywania tych bloków bezpośrednio z block(). Więc możesz użyć invoke jako wskaźnika funkcji.

Ale jak mówię, jest to całkowicie niebezpieczne. Nie używaj tego!

Jeśli chcesz zobaczyć write-up na drodze do zrobienia to pytasz, to sprawdź to: http://www.mikeash.com/pyblog/friday-qa-2010-02-12-trampolining-blocks-with-mutable-code.html

To tylko wielki write-up, co trzeba zrobić aby to zadziałało. Niestety, nigdy nie będzie działać na iOS (ponieważ musisz oznaczyć stronę jako plik wykonywalny, którego nie możesz wykonywać w piaskownicy twojej aplikacji). Ale mimo to świetny artykuł.

+0

Proszę wybaczyć moją ignorancję, ale dlaczego tak naprawdę jest to niebezpieczne? Przypuszczam, że to dlatego, że struktura jest wewnętrzna i może się zmienić w przyszłości, prawda? –

+0

Zgadza się :-). – mattjgalloway

+0

Problem z tym, że nadal wymaga przekazania bloku do funkcji invoke, podczas gdy OP mówi, że nie można przekazać kontekstu do wywołania zwrotnego. – newacct

4

Jeśli twój blok potrzebuje informacji kontekstowych, a wywołanie zwrotne nie oferuje żadnego kontekstu, obawiam się, że odpowiedź jest jednoznaczna. Bloki muszą przechowywać gdzieś informacje kontekstowe, więc nigdy nie będziesz w stanie rzucić takiego bloku w wskaźnik funkcji no-arguments.

Starannie opracowana koncepcja zmiennych globalnych jest prawdopodobnie najlepszym rozwiązaniem w tym przypadku.

0

Wiem, że zostało to rozwiązane, ale dla zainteresowanych mam inne rozwiązanie.

Zmień całą funkcję na nową przestrzeń adresową. Nowy wynikowy adres może być użyty jako klucz do wymaganych danych.

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

Pamiętaj, aby obsługiwać stron, które nie są już potrzebne i pamiętać, że ma dużo więcej pamięci niż MABlockClosure za wezwaniem.

(Testowane na iOS)