¿Llamando al método Objective-C desde la función miembro de C ++?

Tengo una clase ( EAGLView ) que llama a una función miembro de una clase C++ sin problemas. Ahora, el problema es que necesito llamar a esa clase C++ una función objective-C [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; que no puedo hacer en syntax C++ .

Podría envolver esta llamada de Objective-C a la misma clase de Objective-C , que en primer lugar llamó a la clase de C ++, pero luego necesito llamar de alguna manera a ese método desde C++ , y no puedo encontrar la manera de hacerlo.

Intenté dar un puntero al objeto EAGLView para la función miembro de C ++ e incluir el ” EAGLView.h ” en mi encabezado de clase C++ , pero obtuve 3999 errores.

Entonces … ¿cómo debería hacer esto? Un ejemplo sería bueno. Solo encontré ejemplos puros de C de hacer esto.

Puedes mezclar C ++ con Objective-C si lo haces con cuidado. Hay algunas advertencias, pero en general se pueden mezclar. Si desea mantenerlos separados, puede configurar una función contenedora C estándar que le otorgue al objeto Objective-C una interfaz usable de estilo C a partir del código Objective-C (elija mejores nombres para sus archivos, he elegido estos nombres para verbosidad):

MyObject-C-Interface.h

 #ifndef __MYOBJECT_C_INTERFACE_H__ #define __MYOBJECT_C_INTERFACE_H__ // This is the C "trampoline" function that will be used // to invoke a specific Objective-C method FROM C++ int MyObjectDoSomethingWith (void *myObjectInstance, void *parameter); #endif 

MyObject.h

 #import "MyObject-C-Interface.h" // An Objective-C class that needs to be accessed from C++ @interface MyObject : NSObject { int someVar; } // The Objective-C member function you want to call from C++ - (int) doSomethingWith:(void *) aParameter; @end 

MyObject.mm

 #import "MyObject.h" @implementation MyObject // C "trampoline" function to invoke Objective-C method int MyObjectDoSomethingWith (void *self, void *aParameter) { // Call the Objective-C method using Objective-C syntax return [(id) self doSomethingWith:aParameter]; } - (int) doSomethingWith:(void *) aParameter { // The Objective-C function you wanted to call from C++. // do work here.. return 21 ; // half of 42 } @end 

MyCPPClass.cpp

 #include "MyCPPClass.h" #include "MyObject-C-Interface.h" int MyCPPClass::someMethod (void *objectiveCObject, void *aParameter) { // To invoke an Objective-C method from C++, use // the C trampoline function return MyObjectDoSomethingWith (objectiveCObject, aParameter); } 

La función contenedora no necesita estar en el mismo archivo .m que la clase Objective-C, pero el archivo en el que existe debe comstackrse como código Objective-C . El encabezado que declara la función de envoltura debe incluirse tanto en el código CPP como en el código Objective-C.

(NOTA: si el archivo de implementación de Objective-C tiene la extensión “.m” no se vinculará bajo Xcode. La extensión “.mm” le dice a Xcode que espere una combinación de Objective-C y C ++, es decir, Objective-C ++. )


Puede implementar lo anterior de una manera orientada a objetos utilizando la expresión idiomática PIMPL . La implementación es solo ligeramente diferente. En resumen, coloca las funciones de contenedor (declaradas en “MyObject-C-Interface.h”) dentro de una clase con un puntero de vacío (privado) a una instancia de MyClass.

MyObject-C-Interface.h (PIMPL)

 #ifndef __MYOBJECT_C_INTERFACE_H__ #define __MYOBJECT_C_INTERFACE_H__ class MyClassImpl { public: MyClassImpl ( void ); ~MyClassImpl( void ); void init( void ); int doSomethingWith( void * aParameter ); void logMyMessage( char * aCStr ); private: void * self; }; #endif 

Observe que los métodos de contenedor ya no requieren el puntero de vacío a una instancia de MyClass; ahora es un miembro privado de MyClassImpl. El método init se usa para instanciar una instancia de MyClass;

MyObject.h (PIMPL)

 #import "MyObject-C-Interface.h" @interface MyObject : NSObject { int someVar; } - (int) doSomethingWith:(void *) aParameter; - (void) logMyMessage:(char *) aCStr; @end 

MyObject.mm (PIMPL)

 #import "MyObject.h" @implementation MyObject MyClassImpl::MyClassImpl( void ) : self( NULL ) { } MyClassImpl::~MyClassImpl( void ) { [(id)self dealloc]; } void MyClassImpl::init( void ) { self = [[MyObject alloc] init]; } int MyClassImpl::doSomethingWith( void *aParameter ) { return [(id)self doSomethingWith:aParameter]; } void MyClassImpl::logMyMessage( char *aCStr ) { [(id)self doLogMessage:aCStr]; } - (int) doSomethingWith:(void *) aParameter { int result; // ... some code to calculate the result return result; } - (void) logMyMessage:(char *) aCStr { NSLog( aCStr ); } @end 

Observe que MyClass se crea una instancia con una llamada a MyClassImpl :: init. Puede instanciar MyClass en el constructor de MyClassImpl, pero generalmente no es una buena idea. La instancia de MyClass se destruye del destructor de MyClassImpl. Al igual que con la implementación de estilo C, los métodos de envoltura simplemente difieren a los métodos respectivos de MyClass.

MyCPPClass.h (PIMPL)

 #ifndef __MYCPP_CLASS_H__ #define __MYCPP_CLASS_H__ class MyClassImpl; class MyCPPClass { enum { cANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING = 42 }; public: MyCPPClass ( void ); ~MyCPPClass( void ); void init( void ); void doSomethingWithMyClass( void ); private: MyClassImpl * _impl; int _myValue; }; #endif 

MyCPPClass.cpp (PIMPL)

 #include "MyCPPClass.h" #include "MyObject-C-Interface.h" MyCPPClass::MyCPPClass( void ) : _impl ( NULL ) { } void MyCPPClass::init( void ) { _impl = new MyClassImpl(); } MyCPPClass::~MyCPPClass( void ) { if ( _impl ) { delete _impl; _impl = NULL; } } void MyCPPClass::doSomethingWithMyClass( void ) { int result = _impl->doSomethingWith( _myValue ); if ( result == cANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING ) { _impl->logMyMessage( "Hello, Arthur!" ); } else { _impl->logMyMessage( "Don't worry." ); } } 

Ahora tiene acceso a las llamadas a MyClass a través de una implementación privada de MyClassImpl. Este enfoque puede ser ventajoso si estuviera desarrollando una aplicación portátil; simplemente podría cambiar la implementación de MyClass por una específica para la otra plataforma … pero, sinceramente, si esta es una mejor implementación es más una cuestión de gusto y necesidades.

Puede comstackr su código como Objective-C ++: la forma más simple es cambiarle el nombre a .cpp como .mm. Luego comstackrá correctamente si incluye EAGLView.h (estaba recibiendo tantos errores porque el comstackdor de C ++ no entendía ninguna de las palabras clave específicas de Objective-C), y puede (en su mayor parte) mezclar Objective-C y C ++ como quieras.

La solución más sencilla es simplemente decirle a Xcode que compile todo como Objective C ++.

Establezca la configuración de su proyecto o destino para comstackr las fonts en cuanto a Objectivo C ++ y vuelva a comstackr.

Luego puede usar C ++ o Objective C en todas partes, por ejemplo:

 void CPPObject::Function( ObjectiveCObject* context, NSView* view ) { [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)view.layer] } 

Esto tiene el mismo efecto que cambiar el nombre de todos sus archivos fuente de .cpp o .m a .mm.

Hay dos desventajas menores en esto: clang no puede analizar el código fuente de C ++; un código C relativamente extraño no comstack bajo C ++.

Necesita hacer que su archivo C ++ se trate como Objective-C ++. Puede hacer esto en xcode cambiando el nombre de foo.cpp a foo.mm (.mm es la extensión obj-c ++). Luego, como otros han dicho, la syntax estándar de obj-c funcionará.

Paso 1

Cree un archivo objective c (archivo .m) y su correspondiente archivo de encabezado.

// Archivo de encabezado (lo llamamos “ObjCFunc.h”)

 #ifndef test2_ObjCFunc_h #define test2_ObjCFunc_h @interface myClass :NSObject -(void)hello:(int)num1; @end #endif 

// Archivo correspondiente al Objective C (Lo llamamos “ObjCFunc.m”)

 #import  #include "ObjCFunc.h" @implementation myClass //Your objective c code here.... -(void)hello:(int)num1 { NSLog(@"Hello!!!!!!"); } @end 

Paso 2

¡Ahora implementaremos una función c ++ para llamar a la función objective c que acabamos de crear! Así que para eso definiremos un archivo .mm y su archivo de cabecera correspondiente (el archivo “.mm” se usará aquí porque podremos usar la encoding de Objective C y C ++ en el archivo)

// Archivo de encabezado (lo llamamos “ObjCCall.h”)

 #ifndef __test2__ObjCCall__ #define __test2__ObjCCall__ #include  class ObjCCall { public: static void objectiveC_Call(); //We define a static method to call the function directly using the class_name }; #endif /* defined(__test2__ObjCCall__) */ 

// Archivo correspondiente de Objective C ++ (Lo llamamos “ObjCCall.mm”)

 #include "ObjCCall.h" #include "ObjCFunc.h" void ObjCCall::objectiveC_Call() { //Objective C code calling..... myClass *obj=[[myClass alloc]init]; //Allocating the new object for the objective C class we created [obj hello:(100)]; //Calling the function we defined } 

Paso 3

Llamar a la función c ++ (que en realidad llama al método objective c)

 #ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" #include "ObjCCall.h" class HelloWorld : public cocos2d::Layer { public: // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); // a selector callback void menuCloseCallback(cocos2d::Ref* pSender); void ObCCall(); //definition // implement the "static create()" method manually CREATE_FUNC(HelloWorld); }; #endif // __HELLOWORLD_SCENE_H__ 

// Llamada final

 #include "HelloWorldScene.h" #include "ObjCCall.h" USING_NS_CC; Scene* HelloWorld::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autorelease object auto layer = HelloWorld::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Layer::init() ) { return false; } Size visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); ///////////////////////////// // 2. add a menu item with "X" image, which is clicked to quit the program // you may modify it. // add a "close" icon to exit the progress. it's an autorelease object auto closeItem = MenuItemImage::create( "CloseNormal.png", "CloseSelected.png", CC_CALLBACK_1(HelloWorld::menuCloseCallback, this)); closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 , origin.y + closeItem->getContentSize().height/2)); // create menu, it's an autorelease object auto menu = Menu::create(closeItem, NULL); menu->setPosition(Vec2::ZERO); this->addChild(menu, 1); ///////////////////////////// // 3. add your codes below... // add a label shows "Hello World" // create and initialize a label auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24); // position the label on the center of the screen label->setPosition(Vec2(origin.x + visibleSize.width/2, origin.y + visibleSize.height - label- >getContentSize().height)); // add the label as a child to this layer this->addChild(label, 1); // add "HelloWorld" splash screen" auto sprite = Sprite::create("HelloWorld.png"); // position the sprite on the center of the screen sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y)); // add the sprite as a child to this layer this->addChild(sprite, 0); this->ObCCall(); //first call return true; } void HelloWorld::ObCCall() //Definition { ObjCCall::objectiveC_Call(); //Final Call } void HelloWorld::menuCloseCallback(Ref* pSender) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert"); return; #endif Director::getInstance()->end(); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) exit(0); #endif } 

¡Espero que esto funcione!

A veces, renombrar .cpp a .mm no es una buena idea, especialmente cuando el proyecto es una plataforma cruzada. En este caso para el proyecto xcode, abro el archivo de proyecto xcode a través de TextEdit, cadena encontrada que contiene el archivo de interés, debe ser como:

 /* OnlineManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OnlineManager.cpp; sourceTree = ""; }; 

y luego cambie el tipo de archivo de sourcecode.cpp.cpp a sourcecode.cpp.objcpp

 /* OnlineManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = **sourcecode.cpp.objcpp**; path = OnlineManager.cpp; sourceTree = ""; }; 

Es equivalente a cambiar el nombre de .cpp a .mm

Además, puede llamar al tiempo de ejecución de Objective-C para llamar al método.

La respuesta de @DawidDrozd es excelente.

Yo agregaría un punto. Las versiones recientes del comstackdor de Clang se quejan de que se requiere un “reparto puente” si se intenta utilizar su código.

Esto parece razonable: usar un trampolín crea un error potencial: dado que las clases de Objective-C se cuentan como referencias, si pasamos su dirección como un vacío *, corremos el riesgo de tener un puntero colgante si la clase es basura recolectada mientras la callback está todavía activo.

Solución 1) Cocoa proporciona CFBridgingRetain y CFBridgingRelease macro funciones que presumiblemente sumn y restan uno de la cuenta de referencia del objeto Objective-C. Por lo tanto, debemos tener cuidado con múltiples devoluciones de llamadas, para liberar la misma cantidad de veces que retenemos.

 // C++ Module #include  void cppFnRequiringCallback(std::function callback) { callback(); } //Objective-C Module #import "CppFnRequiringCallback.h" @interface MyObj : NSObject - (void) callCppFunction; - (void) myCallbackFn; @end void cppTrampoline(const void *caller) { id callerObjC = CFBridgingRelease(caller); [callerObjC myCallbackFn]; } @implementation MyObj - (void) callCppFunction { auto callback = [self]() { const void *caller = CFBridgingRetain(self); cppTrampoline(caller); }; cppFnRequiringCallback(callback); } - (void) myCallbackFn { NSLog(@"Received callback."); } @end 

Solución 2) La alternativa es usar el equivalente de una referencia débil (es decir, sin cambios en el recuento de retención), sin ninguna seguridad adicional.

El lenguaje Objective-C proporciona el calificador __bridge cast para hacer esto (CFBridgingRetain y CFBridgingRelease parecen ser delgadas envolturas de cocoa sobre el lenguaje Objective-C construye __bridge_retenido y liberado respectivamente, pero Cocoa no parece tener un equivalente para __bridge).

Los cambios requeridos son:

 void cppTrampoline(void *caller) { id callerObjC = (__bridge id)caller; [callerObjC myCallbackFn]; } - (void) callCppFunction { auto callback = [self]() { void *caller = (__bridge void *)self; cppTrampoline(caller); }; cppFunctionRequiringCallback(callback); } 

Puede mezclar C ++ con Objectiv-C (Objective C ++). Escriba un método C ++ en su clase Objective C ++ que simplemente llame a [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; y llámalo desde tu C ++.

No lo he intentado antes que a mí mismo, pero pruébalo y comparte los resultados con nosotros.