Appel de la méthode Objective-C à partir d'une fonction membre C ++?

111

J'ai une classe ( EAGLView) qui appelle une fonction membre d'une C++classe sans problème. Maintenant, le problème est que je dois appeler dans cette C++classe un objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];ce que je ne peux pas faire en C++syntaxe.

Je pourrais encapsuler cet Objective-Cappel à la même Objective-Cclasse qui en premier lieu a appelé la classe C ++, mais je dois ensuite appeler cette méthode à partir de C++, et je ne peux pas comprendre comment le faire.

J'ai essayé de donner un pointeur pour EAGLViewobjecter à la fonction membre C ++ et d'inclure le " EAGLView.h" dans mon en C++-tête de classe mais j'ai eu 3999 erreurs.

Alors ... comment dois-je faire ça? Un exemple serait bien .. Je n'ai trouvé que des Cexemples purs de faire cela.

juvenis
la source

Réponses:

204

Vous pouvez mélanger C ++ avec Objective-C si vous le faites soigneusement. Il y a quelques mises en garde, mais d'une manière générale, elles peuvent être mélangées. Si vous souhaitez les garder séparés, vous pouvez configurer une fonction d'encapsulation C standard qui donne à l'objet Objective-C une interface de style C utilisable à partir de code non-Objective-C (choisissez de meilleurs noms pour vos fichiers, j'ai choisi ces noms pour la 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

MonObjet.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 fonction wrapper n'a pas besoin d'être dans le même .mfichier que la classe Objective-C, mais le fichier dans lequel elle existe doit être compilé en tant que code Objective-C . L'en-tête qui déclare la fonction wrapper doit être inclus dans le code CPP et Objective-C.

(NOTE: si le fichier d'implémentation Objective-C est doté de l'extension ".m", il ne sera pas lié sous Xcode. L'extension ".mm" indique à Xcode d'attendre une combinaison d'Objective-C et de C ++, c'est-à-dire Objective-C ++. )


Vous pouvez implémenter ce qui précède d'une manière orientée objet en utilisant l' idiome PIMPL . La mise en œuvre n'est que légèrement différente. En bref, vous placez les fonctions wrapper (déclarées dans "MyObject-C-Interface.h") à l'intérieur d'une classe avec un pointeur void (privé) vers une instance 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

Notez que les méthodes wrapper ne nécessitent plus le pointeur void vers une instance de MyClass; c'est maintenant un membre privé de MyClassImpl. La méthode init est utilisée pour instancier une instance MyClass;

MonObjet.h (PIMPL)

#import "MyObject-C-Interface.h"

@interface MyObject : NSObject
{
    int someVar;
}

- (int)  doSomethingWith:(void *) aParameter;
- (void) logMyMessage:(char *) aCStr;

@end

MonObjet.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

Notez que MyClass est instancié avec un appel à MyClassImpl :: init. Vous pouvez instancier MyClass dans le constructeur de MyClassImpl, mais ce n'est généralement pas une bonne idée. L'instance MyClass est détruite du destructeur de MyClassImpl. Comme pour l'implémentation de style C, les méthodes wrapper se réfèrent simplement aux méthodes respectives 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." );
    }
}

Vous accédez désormais aux appels vers MyClass via une implémentation privée de MyClassImpl. Cette approche peut être avantageuse si vous développiez une application portable; vous pouvez simplement échanger l'implémentation de MyClass avec une implémentation spécifique à l'autre plate-forme ... mais honnêtement, s'il s'agit d'une meilleure implémentation est plus une question de goût et de besoins.

dreamlax
la source
6
Salut, je l'ai essayé mais j'obtiens une erreur de liaison indiquant que le ou les symboles sont introuvables. c'est-à-dire qu'il ne trouve pas le MyObjectDoSomethingWith. des idées?
3
Vous devrez peut-être ajouter extern "C"avant leint MyObjectDoSomethingWith
dreamlax
2
déjà essayé, cela ne fonctionne pas et cela a du sens car extern "C" est utilisé lorsque nous voulons appeler une fonction C ++ à partir de C, dans ce cas, nous appelons une fonction C à partir de C ++, non?
6
En outre, comment objectiveCObject est-il instancié dans MyCPPClass.cpp?
Raffi Khatchadourian
2
Génial @dreamlax, qui est en train de compiler maintenant mais je ne sais pas comment appeler ça "someMethod". Quels devraient être les paramètres à ajouter à: int MyCPPClass :: someMethod (void * objectiveCObject, void * aParameter) ???
the_moon
15

Vous pouvez compiler votre code en Objective-C ++ - le moyen le plus simple est de renommer votre .cpp en .mm. Il se compilera ensuite correctement si vous incluez EAGLView.h(vous obteniez tellement d'erreurs car le compilateur C ++ ne comprenait aucun des mots clés spécifiques à Objective-C), et vous pouvez (pour la plupart) mélanger Objective-C et C ++ comme vous le souhaitez comme.

Jesse Beder
la source
Obtenez-vous toutes ces erreurs du compilateur dans ce fichier C ++ ou sont-elles dans un autre fichier C ++ qui inclut cet en-tête C ++?
Jesse Beder le
Il semble que je ne puisse pas inclure le EAGLView.h dans le fichier d'en-tête C ++ car, pour une raison quelconque, il s'attend à ce que le code Objective C soit en C ++, et ne comprend pas @ + autres symboles
juvenis
12

La solution la plus simple consiste simplement à dire à Xcode de tout compiler en Objective C ++.

Définissez les paramètres de votre projet ou cible pour Compiler les sources en tant que sur Objective C ++ et recompilez.

Ensuite, vous pouvez utiliser C ++ ou Objective C partout, par exemple:

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

Cela a le même effet que de renommer tous vos fichiers source de .cpp ou .m en .mm.

Il y a deux inconvénients mineurs à cela: clang ne peut pas analyser le code source C ++; un code C relativement étrange ne se compile pas sous C ++.

Peter N Lewis
la source
Je suis juste un peu curieux, lorsque vous compilez tout en Objective-C ++, recevez-vous des avertissements sur l'utilisation de casts de style C et / ou d'autres avertissements spécifiques au C ++ concernant du code de style C valide?
dreamlax
Bien sûr, vous programmez en C ++ et vous devrez donc vous comporter de manière appropriée - mais en règle générale, C ++ est un meilleur C que C, même si vous ne créez jamais de classe. Cela ne vous laissera pas faire des choses stupides, et cela vous permettra de faire de belles choses (comme de meilleures constantes et énumérations, etc.). Vous pouvez toujours lancer la même chose (par exemple (CFFloat) x).
Peter N Lewis
10

Étape 1

Créez un fichier objectif c (fichier .m) et son fichier d'en-tête correspondant.

// Fichier d'en-tête (nous l'appelons "ObjCFunc.h")

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

// Fichier Objective C correspondant (nous l'appelons "ObjCFunc.m")

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

Étape 2

Nous allons maintenant implémenter une fonction c ++ pour appeler la fonction objective c que nous venons de créer! Donc pour cela nous allons définir un fichier .mm et son fichier d'en-tête correspondant (le fichier ". Mm" est à utiliser ici car nous pourrons utiliser le codage Objective C et C ++ dans le fichier)

// Fichier d'en-tête (nous l'appelons "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__) */

// Fichier Objective C ++ correspondant (nous l'appelons "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
}

Étape 3

Appel de la fonction c ++ (qui appelle en fait la méthode 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__

//Dernier appel

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

J'espère que cela fonctionne!

Mishal Shah
la source
9

Vous devez faire en sorte que votre fichier C ++ soit traité comme Objective-C ++. Vous pouvez le faire dans xcode en renommant foo.cpp en foo.mm (.mm est l'extension obj-c ++). Ensuite, comme d'autres l'ont dit, la syntaxe de messagerie obj-c standard fonctionnera.

olliej
la source
1

Parfois, renommer .cpp en .mm n'est pas une bonne idée, en particulier lorsque le projet est multi-plateforme. Dans ce cas, pour le projet xcode, j'ouvre le fichier de projet xcode via TextEdit, j'ai trouvé une chaîne contenant le fichier d'intérêt, cela devrait ressembler à:

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

puis changez le type de fichier de sourcecode.cpp.cpp en sourcecode.cpp.objcpp

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

Cela équivaut à renommer .cpp en .mm

Yevgeniy Logachev
la source
1

En outre, vous pouvez appeler le runtime Objective-C pour appeler la méthode.

Maxthon Chan
la source
1

La réponse de @ DawidDrozd ci-dessus est excellente.

J'ajouterais un point. Les versions récentes du compilateur Clang se plaignent d'exiger un "pontage" si vous essayez d'utiliser son code.

Cela semble raisonnable: l'utilisation d'un trampoline crée un bug potentiel: puisque les classes Objective-C sont comptées par référence, si nous passons leur adresse comme un vide *, nous risquons d'avoir un pointeur suspendu si la classe est récupérée alors que le rappel est toujours actif.

Solution 1) Cocoa fournit des fonctions macro CFBridgingRetain et CFBridgingRelease qui ajoutent et soustraient vraisemblablement une du nombre de références de l'objet Objective-C. Il faut donc être prudent avec plusieurs callbacks, pour libérer le même nombre de fois que l'on retient.

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

Solution 2) L'alternative consiste à utiliser l'équivalent d'une référence faible (c.-à-d. Aucun changement du nombre de retenues), sans aucune sécurité supplémentaire.

Le langage Objective-C fournit le qualificatif de conversion __bridge pour ce faire (CFBridgingRetain et CFBridgingRelease semblent être de minces wrappers Cocoa sur les constructions du langage Objective-C __bridge_retained et release respectivement, mais Cocoa ne semble pas avoir d'équivalent pour __bridge).

Les changements requis sont:

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);
}
QuesterZen
la source
Je dois admettre que je suis quelque peu méfiant quant à savoir si la solution 1 offre une sécurité supplémentaire malgré toutes les théâtres de conservation / libération. Un point est que nous transmettons une copie de selfdans la fermeture, qui pourrait devenir obsolète. L'autre point est qu'il n'est pas clair comment cela interagit avec le comptage automatique de références et si le compilateur peut comprendre ce qui se passe. En pratique, je n'ai pas réussi à créer une situation où l'une ou l'autre des versions échouait dans un simple exemple de jouet à un module.
QuesterZen
-1

Vous pouvez mélanger C ++ avec Objectiv-C (Objective C ++). Écrivez une méthode C ++ dans votre classe Objective C ++ qui appelle simplement[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; et l' partir de votre C ++.

Je ne l'ai jamais essayé avant moi-même, mais essayez-le et partagez les résultats avec nous.

Hhafez
la source
2
Mais le problème est que comment puis-je l'appeler ... car si j'inclus "EAGLview.h" dans ma classe C ++, j'obtiens des milliers d'erreurs.
juvenis