iOSアプリの特定のCライブラリにCスタイルのコールバックを提供する必要があります。コールバックには、void *userData
類似したものがありません。そのため、コンテキスト内でループすることはできません。これを解決するためにグローバルコンテキストを導入することは避けたいと思います。理想的なソリューションは、Objective-Cブロックです。
私の質問:ブロックを関数ポインタに「キャスト」する方法、または何らかの方法でブロックをラップ/クロークする方法はありますか?
iOSアプリの特定のCライブラリにCスタイルのコールバックを提供する必要があります。コールバックには、void *userData
類似したものがありません。そのため、コンテキスト内でループすることはできません。これを解決するためにグローバルコンテキストを導入することは避けたいと思います。理想的なソリューションは、Objective-Cブロックです。
私の質問:ブロックを関数ポインタに「キャスト」する方法、または何らかの方法でブロックをラップ/クロークする方法はありますか?
技術的には、ブロックの関数ポインタにアクセスできます。しかし、そうすることは完全に危険なので、私は確かにそれをお勧めしません。方法を確認するには、次の例を検討してください。
#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);
}
}
実行すると、次のようになります。
Howdy 1
ret = 1
これは、これらのブロックを。で直接実行することから期待されることblock()
です。invoke
したがって、関数ポインタとして使用できます。
しかし、私が言うように、これは完全に危険です。実際には使用しないでください!
あなたが求めていることを行う方法の記述を見たい場合は、これをチェックしてください:http: //www.mikeash.com/pyblog/friday-qa-2010-02-12-trampolining-blocks -with-mutable-code.html
これは、これを機能させるために何をする必要があるかについての素晴らしい記事です。残念ながら、iOSでは機能しません(アプリのサンドボックス内での実行が許可されていないページを実行可能ファイルとしてマークする必要があるため)。しかし、それにもかかわらず、素晴らしい記事です。
ブロックにコンテキスト情報が必要で、コールバックがコンテキストを提供しない場合、答えは明らかにノーです。ブロックはコンテキスト情報をどこかに格納する必要があるため、そのようなブロックを引数なしの関数ポインターにキャストすることはできません。
この場合、慎重に設計されたグローバル変数アプローチがおそらく最良の解決策です。
MABlockClosureはまさにこれを行うことができます。しかし、それはあなたが必要とするものにはやり過ぎかもしれません。
私はこれが解決されたことを知っていますが、利害関係者のために、私は別の解決策を持っています。
関数全体を新しいアドレス空間に再マップします。結果として得られる新しいアドレスは、必要なデータへのキーとして使用できます。
#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;
}
不要になったページを処理することを忘れないでください。また、 MABlockClosureよりも呼び出しごとに多くのメモリが必要になることに注意してください。
(iOSでテスト済み)