Chiamare il metodo Objective-C dalla funzione membro C ++?

Ho una class ( EAGLView ) che chiama una funzione membro di una class C++ senza problemi. Ora, il problema è che ho bisogno di chiamare in questa class C++ una function objective-C [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; che non posso fare nella syntax C++ .

Potrei racchiudere questa chiamata Objective-C alla stessa class Objective-C che in primo luogo chiamava la class C ++, ma poi ho bisogno di chiamare in qualche modo quel metodo da C++ e non riesco a capire come farlo.

Ho provato a dare un puntatore all’object EAGLView alla funzione membro C ++ e includere ” EAGLView.h ” nell’intestazione della class C++ , ma ho ricevuto 3999 errori ..

Quindi .. come dovrei farlo? Un esempio sarebbe carino .. Ho trovato solo esempi puri di questo.

Puoi mescolare C ++ con Objective-C se lo fai con attenzione. Ci sono alcuni avvertimenti, ma in generale possono essere mescolati. Se vuoi tenerli separati, puoi impostare una funzione wrapper standard C che dia all’object Objective-C un’interfaccia in stile C utilizzabile dal codice C non oggettivo (scegli nomi migliori per i tuoi file, ho scelto questi nomi per verbosità):

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 funzione wrapper non deve necessariamente trovarsi nello stesso file .m della class Objective-C, ma il file in cui esiste deve essere compilato come codice Objective-C . L’intestazione che dichiara la funzione wrapper deve essere inclusa in entrambi i codici CPP e Objective-C.

(NOTA: se il file di implementazione di Objective-C ha l’estensione “.m” non si collegherà sotto Xcode. L’estensione “.mm” dice a Xcode di aspettarsi una combinazione di Objective-C e C ++, cioè Objective-C ++. )


È ansible implementare quanto sopra in modo orientato agli oggetti usando l’ idioma PIMPL . L’implementazione è solo leggermente diversa. In breve, si posizionano le funzioni wrapper (dichiarate in “MyObject-C-Interface.h”) all’interno di una class con un puntatore void (privato) a un’istanza di 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 

Si noti che i metodi wrapper non richiedono più il puntatore del vuoto a un’istanza di MyClass; ora è un membro privato di MyClassImpl. Il metodo init è usato per istanziare un’istanza 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 

Si noti che MyClass è un’istanza con una chiamata a MyClassImpl :: init. Puoi istanziare MyClass nel costruttore di MyClassImpl, ma in genere non è una buona idea. L’istanza di MyClass viene distrutta dal distruttore di MyClassImpl. Come con l’implementazione in stile C, i metodi wrapper si rimettono semplicemente ai rispettivi metodi di 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." ); } } 

Ora accedi alle chiamate a MyClass tramite un’implementazione privata di MyClassImpl. Questo approccio può essere vantaggioso se si sviluppasse un’applicazione portatile; potresti semplicemente sostituire l’implementazione di MyClass con una specifica all’altra piattaforma … ma onestamente, se questa è una migliore implementazione è più una questione di gusti e necessità.

Puoi compilare il tuo codice come Objective-C ++ – il modo più semplice è rinominare il tuo .cpp come .mm. Verrà quindi compilato correttamente se includi EAGLView.h ( EAGLView.h così tanti errori perché il compilatore C ++ non capiva nessuna delle parole chiave Objective-C specifiche) e puoi (per la maggior parte) mixare Objective-C e C ++ comunque ti piace.

La soluzione più semplice è semplicemente dire a Xcode di compilare tutto come Objective C ++.

Imposta le impostazioni del tuo progetto o destinazione per Compile Sources As Objective C ++ e ricompila.

Quindi puoi usare C ++ o Objective C ovunque, ad esempio:

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

Questo ha lo stesso effetto del rinominare tutti i file sorgente da .cpp o .m a .mm.

Ci sono due aspetti negativi minori: clang non può analizzare il codice sorgente C ++; qualche codice C relativamente strano non viene compilato in C ++.

Devi rendere il tuo file C ++ da trattare come Objective-C ++. Puoi farlo in xcode rinominando foo.cpp in foo.mm (.mm è l’estensione obj-c ++). Quindi, come altri hanno affermato, la syntax di messaggistica standard obj-c funzionerà.

Passo 1

Crea un file c objective (file .m) e il suo file di intestazione corrispondente.

// File di intestazione (lo chiamiamo “ObjCFunc.h”)

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

// Corrispondente file Objective C (lo chiamiamo “ObjCFunc.m”)

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

Passo 2

Ora implementeremo una funzione c ++ per chiamare la funzione objective c che abbiamo appena creato! Quindi per questo definiremo un file .mm e il suo file di intestazione corrispondente (qui verrà usato il file “. Mm” perché saremo in grado di usare sia la codifica Objective C che C ++ nel file)

// File di intestazione (lo chiamiamo “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__) */ 

// File Objective C ++ corrispondente (lo chiamiamo “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 } 

Passaggio 3

Chiamando la funzione c ++ (che in realtà chiama il metodo dell’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__ 

//Chiamata finale

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

Spero che questo funzioni!

A volte, rinominare .cpp in .mm non è una buona idea, specialmente quando il progetto è multipiattaforma. In questo caso per il progetto xcode apro il file di progetto xcode tramite TextEdit, stringa trovata che contiene il file di interesse, dovrebbe essere come:

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

e quindi modificare il tipo di file da sourcecode.cpp.cpp a sourcecode.cpp.objcpp

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

È equivalente a rinominare .cpp in .mm

Inoltre, è ansible chiamare in runtime Objective-C per chiamare il metodo.

@ La risposta di DawidDrozd sopra è eccellente.

Vorrei aggiungere un punto. Le versioni recenti del compilatore Clang si lamentano di richiedere un “cast di collegamento” se si tenta di usare il suo codice.

Ciò sembra ragionevole: l’uso di un trampolino crea un potenziale insetto: poiché le classi Objective-C sono conteggiate di riferimento, se passiamo il loro indirizzo come un vuoto *, rischiamo di avere un puntatore pendente se la class è raccolta di dati inutili mentre il callback è ancora attivo.

Soluzione 1) Cocoa fornisce le funzioni macro CFBridgingRetain e CFBridgingRelease che presumibilmente aggiungono e sottraggono una dal conteggio di riferimento dell’object Objective-C. Dovremmo pertanto fare attenzione con più callback, per rilasciare lo stesso numero di volte che conserviamo.

 // 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 

Soluzione 2) L’alternativa consiste nell’utilizzare l’equivalente di un riferimento debole (cioè nessuna modifica al conteggio dei ritiri), senza alcuna sicurezza aggiuntiva.

Il linguaggio Objective-C fornisce il qualificatore cast __bridge per fare ciò (CFBridgingRetain e CFBridgingRelease sembrano essere sottili wrapper Cocoa sui costrutti del linguaggio Objective-C __bridge_retained e release rispettivamente, ma Cocoa non sembra avere un equivalente per __bridge).

Le modifiche richieste sono:

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

Puoi mescolare C ++ con Objectiv-C (Objective C ++). Scrivi un metodo C ++ nella class Objective C ++ che chiama semplicemente [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; e chiamalo dal tuo C ++.

Non l’ho provato prima di me stesso, ma provalo e condividi i risultati con noi.