114 votes

Appeler une méthode Objective-C depuis une fonction membre C++ ?

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

Je pourrais emballer ça Objective-C appel au même Objective-C qui, en premier lieu, a appelé la classe C++, mais je dois ensuite appeler cette méthode d'une manière ou d'une autre à partir de C++ et je n'arrive pas à trouver comment le faire.

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

Alors comment dois-je faire ? Un exemple serait bien Je n'ai trouvé que du pur C des exemples de cette démarche.

212voto

dreamlax Points 47152

Vous pouvez mélanger C++ et Objective-C si vous le faites avec soin. Il y a quelques réserves, mais en général, on peut les mélanger. Si vous voulez les garder séparés, vous pouvez mettre en place une fonction wrapper C standard qui donne à l'objet Objective-C une interface de style C utilisable depuis du 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 pour être dans le même .m comme la classe Objective-C, mais le fichier dans lequel elle existe est le suivant doit être compilé en 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 porte l'extension ".m", il ne sera pas lié sous Xcode. L'extension ".mm" indique à Xcode de s'attendre à une combinaison d'Objective-C et de C++, c'est-à-dire d'Objective-C++).


Vous pouvez mettre en œuvre ce qui précède d'une manière orientée objet en utilisant la fonction 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") à l'intérieur d'une classe avec un pointeur (privé) void 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

Remarquez que les méthodes wrapper ne nécessitent plus le pointeur void vers 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 ;

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

Remarquez que MyClass est instanciée par un appel à MyClassImpl::init. Vous pourriez instancier MyClass dans le constructeur de MyClassImpl, mais ce n'est généralement pas une bonne idée. L'instance de MyClass est détruite par le destructeur de MyClassImpl. Comme dans l'implémentation de style C, les méthodes du 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 à MyClass via une implémentation privée de MyClassImpl. Cette approche peut être avantageuse si vous développez une application portable ; vous pouvez simplement remplacer l'implémentation de MyClass par une implémentation spécifique à l'autre plateforme ... mais honnêtement, la question de savoir s'il s'agit d'une meilleure implémentation est plus une question de goût et de besoins.

6 votes

Bonjour, j'ai essayé mais j'obtiens une erreur de liaison disant que le(s) symbole(s) n'a(ont) pas été trouvé(s). c'est-à-dire qu'il ne peut pas trouver le MyObjectDoSomethingWith. des idées ?

3 votes

Vous devrez peut-être ajouter extern "C" avant le int MyObjectDoSomethingWith

2 votes

J'ai déjà essayé, ça ne marche pas et c'est logique car extern "C" est utilisé quand on veut appeler une fonction C++ depuis C, dans ce cas on appelle une fonction C depuis C++, non ?

16voto

Jesse Beder Points 14026

Vous pouvez compiler votre code en Objective-C++ - le moyen le plus simple est de renommer votre .cpp en .mm. Il sera alors compilé correctement si vous incluez EAGLView.h (vous obteniez tant d'erreurs parce que le compilateur C++ ne comprenait aucun des mots-clés spécifiques à l'Objective-C), et vous pouvez (pour la plupart) mélanger Objective-C et C++ comme bon vous semble.

0 votes

Est-ce que vous obtenez toutes ces erreurs de compilation en este C++, ou sont-ils dans un autre fichier C++ qui se trouve inclure cet en-tête C++ ?

0 votes

Il semble que je ne puisse pas inclure EAGLView.h dans le fichier d'en-tête C++ parce qu'alors, pour une raison quelconque, il s'attend à ce que le code Objective C soit du C++, et ne comprend pas les symboles @ et autres.

14voto

Peter N Lewis Points 12025

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

Réglez les paramètres de votre projet ou de votre cible pour Compile Sources As sur Objective C++ et recompilez.

Vous pouvez alors 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 sources de .cpp ou .m en .mm.

Il y a deux inconvénients mineurs à cela : clang ne peut pas analyser le code source C++ ; certains codes C relativement bizarres ne compilent pas sous C++.

0 votes

Je suis juste un peu curieux, lorsque vous compilez tout en Objective-C++, obtenez-vous des avertissements concernant l'utilisation de casts de style C et/ou d'autres avertissements spécifiques au C++ concernant du code valide de style C ?

0 votes

Bien sûr, vous programmez en C++ et on attend de vous un comportement approprié - mais en règle générale, le C++ est un meilleur C que le C, même si vous ne créez jamais de classe. Il ne vous laisse pas faire des choses stupides, et il vous permet de faire des choses agréables (comme de meilleures constantes, des enums, etc.). Vous pouvez toujours effectuer des castings de la même manière (par exemple (CFFloat)x).

9voto

olliej Points 16255

Vous devez faire en sorte que votre fichier C++ soit traité comme un fichier 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 standard de messagerie d'obj-c fonctionnera.

1voto

Yevgeniy Points 11

Parfois, renommer un .cpp en .mm n'est pas une bonne idée, surtout lorsque le projet est multiplateforme. Dans ce cas, pour un projet xcode, j'ouvre le fichier du projet xcode avec TextEdit, je trouve la chaîne de caractères qui contient le fichier d'intérêt, il devrait être comme :

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

et ensuite changer le type de fichier de code source.cpp.cpp a code source.cpp.objcpp

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

Il est équivalent de renommer .cpp en .mm

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X