13

Objective-C に ViewController があり、コードのほとんどは c++ (.mm) です。obj-c (c++) からメンバー関数へのコールバックをいくつか設定し、c++ から呼び出したいと思います。このようなもの(非常に単純化されています):

@interface MyClass
{ }
-(void)my_callback;
@end

@implementation MyClass

-(void)my_callback
{
   printf("called!\n");
}

-(void)viewDidLoad
{
   // setup_callback( "to my_callback ?" );
}
@end

と:

void setup_callback(void(*func)()) { func(); }

もちろん、これは正しくありません。どうすればいいですか?

4

4 に答える 4

17

いくつかのオプションがあります。

ブロックの使用

ブロックを使用して、コールバック作業を伝えることができます。これは、コールバック「関数」にパラメーターを渡すことなくコードを呼び出すことができるため、おそらく最も簡単なソリューションです。ブロックは C とそのすべてのスーパーセットで Clang で機能し、Clang++ ではブロックとラムダの間の暗黙的なキャストも可能です。

#include <dispatch/dispatch.h>

void setup_callback(dispatch_block_t block)
{
    // required to copy the block to the heap, otherwise it's on the stack
    dispatch_block_t copy = [block copy];

    // setup stuff here
    // when you want to call the callback, do as if it was a function pointer:
    // block();
}

int main()
{
    MyClass* instance = [[MyClass alloc] init];

    setup_callback(^{
        [instance callback_method];
    });
}

関数ポインターの代わりにファンクター (または単純な場合はブロックのみ) を受け入れるようにするには、C++ 側でいくつかの再作業が必要になる場合があります。

ブロックはクロージャを作るので、そういう作業にはとても便利です。

ブロックは、C、C++、および Objective-C に対する Apple の拡張機能です。詳細については、こちらを参照してください

Objective-C ランタイムを使用して、呼び出したいメソッドへの関数ポインタを取得します

Objective-C ランタイムを使用して、セレクターの関数ポインターにアクセスします。これはより面倒で、3 つの変数 (メソッドを呼び出すオブジェクト、使用するセレクター、メソッドの実装) を追跡する必要がありますが、Objective-C を使用できない場合でも実際には機能します。構文。

Objective-C メソッドの実装は、次のシグネチャを持つ関数ポインターです。

typedef void (*IMP)(id self, SEL _cmd, ...);

あなたselfが期待して_cmdいるのは、このメソッド呼び出しを引き起こしたセレクターです (_cmd変数は実際にはすべての Objective-C メソッドで使用できます。試してみてください)。残りは可変長と見なされます。変数を適切な関数シグネチャにキャストする必要があるIMPのは、可変長 C 関数の呼び出し規則が Objective-C メソッド呼び出しの呼び出し規則と常に一致するとは限らないためです (Objective-C メソッド呼び出しは、コンパイラの標準関数呼び出し規則です。おそらくcdeclまたは amd64 呼び出し規則のいずれかであり、可変長呼び出し規則は常に同じとは限りません)。Areinterpret_castできるようになります。

同様の目的でまとめたコードを次に示します。C++11 可変個引数テンプレートを使用して、適切な関数シグネチャを取得するのに役立ちます。

#include <objc/runtime.h>

template<typename TReturnType, typename... TArguments>
auto GetInstanceMethodPointer(Class class, SEL selector) -> TReturnType (*)(id, SEL, TArguments...)
{
    Method m = class_getInstanceMethod(class, selector);
    IMP imp = method_getImplementation(m);
    return reinterpret_cast<TReturnType (*)(id, SEL, TArguments...)>(imp);
}

int main()
{
    MyClass* instance = [[MyClass alloc] init];
    auto foo = GetInstanceMethodPointer<void>(
        [MyClass class],
        @selector(my_callback));
    // foo is a void (*)(id, SEL) function pointer
    foo(instance, @selector(my_callback));
}

また、チェックは Objective-C ランタイムによって処理されるnilため、インスタンスが関数呼び出しを使用する前にないように注意してください。nilこの場合、それをバイパスしています。

オブジェクトとSEL

コールバックを実行するために使用-[NSObject performSelector:]します。基本的に、Objective-C ランタイム ソリューションの単純なバージョンです。

void setup_callback(id object, SEL selector)
{
    // do stuff
    // to execute the callback:
    // [object performSelector:selector];
}

int main()
{
    MyClass* instance = [[MyClass alloc] init];

    setup_callback(instance, @selector(my_callback));
}

呼び出しを C++ 関数内にラップする

これは本当に例を必要としないと思います。オブジェクト型を最初のパラメーターとして受け入れる関数を作成し、必要なメソッドを呼び出します。ソリューションと同様に、SEL呼び出す関数とそれを呼び出すオブジェクトを個別に追跡する必要があります。

于 2012-08-30T16:31:00.547 に答える
2

C++ と Obj-C はどちらも C のスーパーセットであるため、C++ コードを C 関数にコールバックすることができます。コールバックを Obj-C オブジェクトに戻すには、C コールバック引数に元の Obj-C オブジェクトへのポインターが含まれていることを確認してください。

// C++ calls this C function
void exampleCallback(void* classInstance)
{
    // callback arguments include a pointer to the Obj-C object
    [((MYObjCClassType*)classInstance) exampleCallback];
}
于 2012-08-30T16:39:28.073 に答える
1

C++コード内でObjective-Cコールバックを呼び出すことは不可能だと思います。その逆も可能です。

この問題が発生したときに私がしたことは、Objective-C で永久に実行される EVENT_LOOP を記述し、C++インスタンスからイベントを取得することでした。std::vectorこのように、C++ では、Objective-C でコールバックを「呼び出し」たい場合は、対応する にイベントを追加するだけstd::vectorです。

于 2012-08-30T16:26:51.163 に答える
1

.mm ファイルに Obj-C と C++ を自由に混在させることができます。これは「Objective-C++」と呼ばれます。

C++ コードと Objective-C コードの分離を維持するために、C++ コールバック内から (通常の方法で) Objective-C メソッド呼び出しを行う軽量のファサードをそれらの間に作成できます。

于 2012-08-30T16:31:16.297 に答える