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


111

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

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

Ho provato a fornire un puntatore a un EAGLViewoggetto alla funzione membro C ++ e a includere " EAGLView.h" nell'intestazione della mia C++classe ma ho ricevuto 3999 errori ..

Allora .. come dovrei farlo? Un esempio sarebbe carino .. Ho trovato solo Cesempi puri di fare questo.

Risposte:


204

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 C standard che fornisce all'oggetto Objective-C un'interfaccia in stile C utilizzabile da codice non Objective-C (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 essere nello stesso .mfile della classe 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 sia nel codice CPP che nel codice Objective-C.

(NOTA: se al file di implementazione Objective-C viene assegnata 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 ++. )


È possibile implementare quanto sopra in modo orientato agli oggetti utilizzando l' idioma PIMPL . L'implementazione è solo leggermente diversa. In breve, inserisci le funzioni wrapper (dichiarate in "MyObject-C-Interface.h") all'interno di una classe 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

Notare che i metodi wrapper non richiedono più il puntatore void a un'istanza di MyClass; ora è un membro privato di MyClassImpl. Il metodo init viene utilizzato per creare un'istanza di 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 viene istanziato con una chiamata a MyClassImpl :: init. Puoi istanziare MyClass nel costruttore di MyClassImpl, ma generalmente non è una buona idea. L'istanza di MyClass viene distrutta dal distruttore di MyClassImpl. Come con l'implementazione in stile C, i metodi wrapper rimandano 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 stavi sviluppando un'applicazione portatile; potresti semplicemente sostituire l'implementazione di MyClass con una specifica per l'altra piattaforma ... ma onestamente, se questa è un'implementazione migliore è più una questione di gusti e bisogni.


6
Ciao, l'ho provato ma ricevo un errore di collegamento che dice che i simboli non sono stati trovati. cioè non riesce a trovare MyObjectDoSomethingWith. qualche idea?

3
Potrebbe essere necessario aggiungere extern "C"prima delint MyObjectDoSomethingWith
dreamlax

2
l'ho già provato, non funziona e ha senso perché extern "C" viene utilizzato quando vogliamo chiamare la funzione C ++ da C, in questo caso stiamo chiamando una funzione C da C ++, no?

6
Inoltre, come viene creata un'istanza di objectCObject in MyCPPClass.cpp?
Raffi Khatchadourian

2
Fantastico @dreamlax, che si sta compilando ora ma non so chiamarlo "someMethod". Quali dovrebbero essere i parametri che dovrebbero essere aggiunti a: int MyCPPClass :: someMethod (void * ObjectiveCObject, void * aParameter) ???
the_moon

15

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(stavi ricevendo così tanti errori perché il compilatore C ++ non comprendeva nessuna delle parole chiave specifiche di Objective-C) e puoi (per la maggior parte) mescolare Objective-C e C ++ come preferisci piace.


Stai ricevendo tutti quegli errori del compilatore in questo file C ++ o sono in qualche altro file C ++ che include questa intestazione C ++?
Jesse Beder

Sembra che non riesco a includere EAGLView.h nel file di intestazione C ++ perché allora per qualche motivo si aspetta che il codice Objective C sia C ++ e non comprende @ + altri simboli
juvenis

12

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

Imposta il tuo progetto o le impostazioni di destinazione per Compile Sources As to 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]
}

Ciò ha lo stesso effetto della ridenominazione di tutti i file sorgente da .cpp o .m a .mm.

Ci sono due piccoli svantaggi in questo: clang non può analizzare il codice sorgente C ++; alcuni codici C relativamente strani non vengono compilati in C ++.


Sono solo un po 'curioso, quando compili tutto come Objective-C ++ ricevi avvisi sull'utilizzo di cast in stile C e / o altri avvisi specifici C ++ sul codice in stile C valido?
dreamlax

Certo, stai programmando in C ++, quindi ci si aspetta che ti comporti in modo appropriato, ma come regola generale, C ++ è un C migliore di C, anche se non crei mai una classe. Non ti consente di fare cose stupide e ti consente di fare cose carine (come costanti ed enumerazioni migliori e così via). Puoi ancora lanciare lo stesso (ad esempio (CFFloat) x).
Peter N Lewis,

10

Passo 1

Crea un file c obiettivo (file .m) e il 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

// File C Objective corrispondente (lo chiamiamo "ObjCFunc.m")

#import <Foundation/Foundation.h>
#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 c obiettivo che abbiamo appena creato! Quindi per questo definiremo un file .mm e il suo file di intestazione corrispondente (il file ". Mm" deve essere utilizzato qui perché saremo in grado di utilizzare sia la codifica Objective C che C ++ nel file)

// File di intestazione (lo chiamiamo "ObjCCall.h")

#ifndef __test2__ObjCCall__
#define __test2__ObjCCall__
#include <stdio.h>
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 C ++ Objective 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

Chiamare la funzione c ++ (che chiama effettivamente il metodo c dell'obiettivo)

#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 funzioni!


9

Devi fare in modo che il tuo file C ++ venga trattato come Objective-C ++. Puoi farlo in xcode rinominando foo.cpp in foo.mm (.mm è l'estensione obj-c ++). Quindi, come altri hanno detto, la sintassi standard della messaggistica obj-c funzionerà.


1

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 del progetto xcode tramite TextEdit, trovata una stringa che contiene il file di interesse, dovrebbe essere come:

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

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 = "<group>"; };

Equivale a rinominare .cpp in .mm


1

Inoltre, puoi chiamare il runtime Objective-C per chiamare il metodo.


1

La risposta di @ DawidDrozd sopra è eccellente.

Aggiungerei un punto. Le versioni recenti del compilatore Clang si lamentano di richiedere un "cast di bridging" se si tenta di utilizzare il suo codice.

Questo sembra ragionevole: l'uso di un trampolino crea un potenziale bug: dal momento che le classi Objective-C sono conteggiate come riferimento, se passiamo il loro indirizzo come un vuoto *, rischiamo di avere un puntatore sospeso se la classe viene raccolta dalla spazzatura mentre il callback è ancora attivo.

Soluzione 1) Cocoa fornisce le funzioni macro CFBridgingRetain e CFBridgingRelease che presumibilmente aggiungono e sottraggono uno dal conteggio dei riferimenti dell'oggetto Objective-C. Dovremmo quindi stare attenti con più callback, per rilasciare lo stesso numero di volte che conserviamo.

// C++ Module
#include <functional>

void cppFnRequiringCallback(std::function<void(void)> 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 è usare l'equivalente di un riferimento debole (cioè nessuna modifica al conteggio ritenuto), senza alcuna sicurezza aggiuntiva.

Il linguaggio Objective-C fornisce il qualificatore cast __bridge per fare ciò (CFBridgingRetain e CFBridgingRelease sembrano essere dei 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);
}

Devo ammettere che sono un po 'sospettoso sul fatto che la Soluzione 1 offra una sicurezza aggiuntiva nonostante tutte le teatralità di conservazione / rilascio. Un punto è che stiamo passando una copia di selfnella chiusura, che potrebbe diventare obsoleta. L'altro punto è che non è chiaro come questo interagisce con il conteggio automatico dei riferimenti e se il compilatore può capire cosa sta succedendo. In pratica non sono riuscito a creare una situazione in cui nessuna delle versioni falliva in un semplice esempio di giocattolo a un modulo.
QuesterZen

-1

Puoi combinare C ++ con Objectiv-C (Objective C ++). Scrivi un metodo C ++ nella tua classe Objective C ++ che semplicemente lo chiami [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];e lo chiami dal tuo C ++.

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


2
Ma il problema è che come posso chiamarlo ... perché se includo "EAGLview.h" nella mia classe C ++ ottengo migliaia di errori.
juvenis
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.