обратный вызов с c++ на target c

У меня есть ViewController в Objective-C, и большая часть моего кода написана на C++ (.mm). Я хотел бы настроить некоторые обратные вызовы для функций-членов из obj-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(); }

это не правильно конечно. Любые советы, как я могу это сделать, пожалуйста?


person velkyel    schedule 30.08.2012    source источник


Ответы (4)


У вас есть несколько вариантов.

Использование блоков

Вы можете использовать блоки для передачи вашей работы обратного вызова. Это, вероятно, самое простое решение, поскольку оно позволяет вам вызывать ваш код без необходимости передавать какой-либо параметр в «функцию» обратного вызова. Блоки работают в 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];
    });
}

Это может потребовать некоторой доработки на стороне С++, чтобы принимать функторы (или просто блоки, если это проще) вместо указателей на функции.

Так как блоки создают замыкания, они очень удобны для такого рода работ.

Блоки — это расширение Apple для C, C++ и Objective-C. Подробнее о них см. здесь.

Используйте среду выполнения Objective-C для получения указателя функции на метод, который вы хотите вызвать.

Используйте среду выполнения Objective-C для доступа к указателю функции вашего селектора. Это более утомительно и требует, чтобы вы отслеживали три переменные (объект для вызова метода, используемый селектор и реализацию метода), но на самом деле это работает даже в том случае, если вы не можете использовать Objective-C. синтаксис.

Реализации метода Objective-C представляют собой указатели на функции с такой сигнатурой:

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

Где self — это то, что вы ожидаете, _cmd — это селектор, вызвавший вызов этого метода (переменная _cmd на самом деле доступна во всех методах Objective-C, попробуйте), а остальное считается вариативным. Вам необходимо преобразовать IMP переменных в соответствующую сигнатуру функции, потому что соглашение о вызовах для вариативных функций C не всегда соответствует соглашению о вызовах для вызовов методов Objective-C (вызов метода Objective-C является стандартным соглашение о вызовах функций для вашего компилятора, вероятно, либо cdecl, либо соглашение о вызовах amd64, а соглашение о вызовах с переменным числом аргументов не всегда одинаково). reinterpret_cast сможет это сделать.

Вот некоторый код, который я собрал для аналогичных целей. Он использует вариативные шаблоны С++ 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));
}

Также позаботьтесь о том, чтобы ваш экземпляр не был nil, прежде чем использовать вызов функции, потому что проверка nil обрабатывается средой выполнения Objective-C. В данном случае мы его обходим.

Следите за объектом и 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, вам нужно отдельно отслеживать вызываемую функцию и объект, для которого ее вызывают.

person zneak    schedule 30.08.2012
comment
вам даже не нужно получать реализацию. Вы всегда можете просто использовать objc_msgSend вместо реализации - person user102008; 24.07.2013
comment
Истинный. В этом случае вы будете использовать селектор вместо указателя метода. - person zneak; 24.07.2013
comment
Нет. С вашим IMP вам все равно придется носить с собой селектор. Так что это то же самое, за исключением того, что теперь вам не нужно получать IMP. - person user102008; 24.07.2013
comment
Использование блоков. Красивый. Спасибо! - person jrc; 17.03.2015

Поскольку 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];
}
person James    schedule 30.08.2012
comment
Чтобы сделать то, что вы показываете, вам нужно сделать это с помощью бесплатного моста: [((__bridge MYObjCClassType*)classInstance) exampleCallback]; - person dadude999; 30.01.2016

Я не думаю, что это возможно: вызов обратного вызова Objective-C в коде C++. Возможен обратный путь.

Что я сделал, когда у меня возникла эта проблема, так это написал EVENT_LOOP, работающий вечно в Objective-C, и получил события из экземпляра C++ std::vector. Таким образом, в C++, если вы хотите «вызвать» обратный вызов в Objective-C, вы просто добавляете событие в соответствующий std::vector.

person Pablo Santa Cruz    schedule 30.08.2012

Вы можете свободно смешивать Obj-C и C++ в своих файлах .mm — это называется «Objective-C++».

Чтобы сохранить изоляцию вашего кода C++ и кода Objective-C, вы можете создать легкий фасад между ними, который будет выполнять вызовы методов Objective-C (обычным способом) из обратных вызовов C++.

person ikuramedia    schedule 30.08.2012