web-dev-qa-db-fra.com

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

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

Je pourrais encapsuler cet appel Objective-C dans la même classe Objective-C qui s'appelait en premier lieu la classe C++, mais je dois ensuite appeler cette méthode d'une manière ou d'une autre à partir de C++ et je ne vois pas comment le faire.

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

Alors .. comment devrais-je faire cela? Un exemple serait Nice .. Je n’ai trouvé que des exemples purs de C.

104
juvenis

Vous pouvez mélanger C++ avec Objective-C si vous le faites avec précaution. Il y a quelques mises en garde, mais elles peuvent généralement être mélangées. Si vous voulez 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 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

MonObjet.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 ne nécessite pas doit figurer dans le même fichier .m que la classe Objective-C, mais le fichier qu’elle existe dans doit être compilé en tant que Code C. L'en-tête qui déclare la fonction wrapper doit être inclus dans les codes CPP et Objective-C.

(REMARQUE: si l'extension ".m" est attribuée au fichier d'implémentation d'Objective-C, 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 de manière orientée objet en utilisant le idiome PIMPL . La mise en œuvre n'est que légèrement différente. En bref, vous placez les fonctions d'encapsulation (déclarées dans "MyObject-C-Interface.h") dans une classe avec un pointeur (privé) de vide sur 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 d'emballage ne nécessitent plus le pointeur void sur une instance de MyClass; il est maintenant un membre privé de MyClassImpl. La méthode init est utilisée pour instancier une instance de 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ée 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 d'encapsidation sont simplement basées sur les 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 maintenant aux appels de MyClass via une implémentation privée de MyClassImpl. Cette approche peut être avantageuse si vous développez une application portable. vous pouvez simplement échanger la mise en œuvre de MyClass avec une autre spécifique à la plate-forme ... mais honnêtement, le fait de savoir s'il s'agit d'une meilleure mise en œuvre est davantage une question de goût et de besoins.

192
dreamlax

Vous pouvez compiler votre code en tant que Objective-C++ - la méthode la plus simple consiste à renommer votre fichier .cpp en .mm. Il sera ensuite compilé correctement si vous incluez EAGLView.h (vous avez eu tellement d’erreurs car le compilateur C++ n’avait pas compris les mots-clés spécifiques à Objective-C), et vous pouvez (pour la plupart) mélanger Objective-C et C++, cependant vous aimez.

15
Jesse Beder

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

Définissez les paramètres de votre projet ou de votre cible pour Compiler les sources selon Objective C++ et recompiler.

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.

Cela présente deux inconvénients mineurs: clang ne peut pas analyser le code source C++; certains codes C relativement étranges ne sont pas compilés sous C++.

12
Peter N Lewis

Étape 1

Créez un fichier Objective 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! Pour cela, nous allons définir un fichier .mm et son fichier d’en-tête correspondant (le fichier ".mm" doit être utilisé ici car nous pourrons utiliser les codages Objective C et C++ du 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 réalité 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 ça marche!

9
Mishal Shah

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 ++). Comme d’autres l’ont dit, la syntaxe de messagerie standard obj-c fonctionnera.

8
olliej

De plus, vous pouvez appeler le runtime d'Objective-C pour appeler la méthode.

1
Maxthon Chan

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 projet xcode via TextEdit, qui contient une chaîne indiquant le fichier d’intérêt du contenu.

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

1
Yevgeniy Logachev

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

Je voudrais ajouter un point. Les versions récentes du compilateur Clang se plaignent de la nécessité d'un "casting de pontage" si vous essayez d'utiliser son code.

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

Solution 1) Cocoa fournit les fonctions macro CFBridgingRetain et CFBridgingRelease qui, vraisemblablement, ajoutent et soustraient une personne du compte de référence de l'objet Objective-C. Nous devons donc faire attention avec les rappels multiples, afin de libérer le même nombre de fois que nous conservons.

// 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’est-à-dire qu’aucune modification ne soit apportée au nombre de retenues), sans sécurité supplémentaire.

Le langage Objective-C fournit le qualificatif __bridge cast à cette fin (CFBridgingRetain et CFBridgingRelease semblent être des enveloppes minces de cacao par rapport aux constructions de langage Objective-C __bridge_retained et release, mais Cocoa ne semble pas avoir d'équivalent pour __bridge).

Les modifications requises sont les suivantes:

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