¿Llamando al método Objective-C desde la función miembro de C ++?

111

Tengo una clase ( EAGLView) que llama a una función miembro de una C++clase sin problemas. Ahora, el problema es que necesito llamar a esa C++clase a, lo objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];que no puedo hacer en C++sintaxis.

Podría ajustar esta Objective-Cllamada a la misma Objective-Cclase que en primer lugar llamó a la clase C ++, pero luego necesito llamar a ese método de alguna manera C++, y no puedo averiguar cómo hacerlo.

Intenté dar un puntero para EAGLViewobjetar la función miembro de C ++ e incluir " EAGLView.h" en el C++encabezado de mi clase, pero obtuve 3999 errores.

Entonces ... ¿cómo debo hacer esto? Un ejemplo sería bueno ... Solo encontré Cejemplos puros de hacer esto.

juvenis
fuente

Respuestas:

204

Puede mezclar C ++ con Objective-C si lo hace con cuidado. Hay algunas salvedades, pero en general se pueden mezclar. Si desea mantenerlos separados, puede configurar una función de contenedor C estándar que le da al objeto Objective-C una interfaz de estilo C utilizable a partir de código que no sea Objective-C (elija mejores nombres para sus archivos, he elegido estos nombres para verbosidad):

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

No es necesario que la función contenedora esté en el mismo .marchivo que la clase Objective-C, pero el archivo en el que existe debe compilarse como código Objective-C . El encabezado que declara la función contenedora debe incluirse en el código CPP y Objective-C.

(NOTA: si el archivo de implementación de Objective-C tiene la extensión ".m", no se vinculará bajo Xcode. La extensión ".mm" le dice a Xcode que espere una combinación de Objective-C y C ++, es decir, Objective-C ++. )


Puede implementar lo anterior de una manera orientada a objetos utilizando el lenguaje PIMPL . La implementación es solo ligeramente diferente. En resumen, coloca las funciones contenedoras (declaradas en "MyObject-C-Interface.h") dentro de una clase con un puntero vacío (privado) a una instancia 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

Observe que los métodos de contenedor ya no requieren el puntero vacío a una instancia de MyClass; ahora es un miembro privado de MyClassImpl. El método init se utiliza para crear una instancia de 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

Observe que MyClass se crea una instancia con una llamada a MyClassImpl :: init. Puede crear una instancia de MyClass en el constructor de MyClassImpl, pero eso generalmente no es una buena idea. La instancia de MyClass se destruye del destructor de MyClassImpl. Al igual que con la implementación de estilo C, los métodos de contenedor simplemente se remiten a los métodos respectivos 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." );
    }
}

Ahora accede a las llamadas a MyClass a través de una implementación privada de MyClassImpl. Este enfoque puede resultar ventajoso si está desarrollando una aplicación portátil; simplemente podría cambiar la implementación de MyClass por una específica para la otra plataforma ... pero honestamente, si esta es una mejor implementación es más una cuestión de gustos y necesidades.

Dreamlax
fuente
6
Hola, lo probé pero aparece un error de enlace que dice que no se encontraron los símbolos. es decir, no puede encontrar MyObjectDoSomethingWith. ¿algunas ideas?
3
Es posible que deba agregar extern "C"antes delint MyObjectDoSomethingWith
dreamlax
2
ya intenté eso, no funciona y tiene sentido porque extern "C" se usa cuando queremos llamar a la función C ++ desde C, en este caso estamos llamando a una función C desde C ++, ¿no?
6
Además, ¿cómo se crea una instancia de objectCObject en MyCPPClass.cpp?
Raffi Khatchadourian
2
Impresionante @dreamlax, eso se está compilando ahora, pero no sé llamarlo "algún método". ¿Cuáles deberían ser los parámetros que se deben agregar a: int MyCPPClass :: someMethod (void * objectCObject, void * aParameter) ???
the_moon
15

Puede compilar su código como Objective-C ++; la forma más sencilla es cambiar el nombre de su .cpp a .mm. Luego se compilará correctamente si lo incluye EAGLView.h(recibía tantos errores porque el compilador de C ++ no entendía ninguna de las palabras clave específicas de Objective-C), y puede (en su mayor parte) mezclar Objective-C y C ++ como quiera me gusta.

Jesse Beder
fuente
¿Está recibiendo todos esos errores del compilador en este archivo C ++, o están en algún otro archivo C ++ que incluye este encabezado C ++?
Jesse Beder
Parece que no puedo incluir EAGLView.h en el archivo de encabezado de C ++ porque, por alguna razón, espera que el código de Objective C sea C ++, y no comprende @ + otros símbolos
juvenis
12

La solución más sencilla es simplemente decirle a Xcode que compile todo como Objective C ++.

Establezca la configuración de su proyecto o destino para Compilar fuentes como para Objective C ++ y vuelva a compilar.

Entonces puede usar C ++ u Objective C en todas partes, por ejemplo:

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

Esto tiene el mismo efecto que cambiar el nombre de todos sus archivos de origen de .cpp o .m a .mm.

Hay dos desventajas menores en esto: clang no puede analizar el código fuente de C ++; algún código C relativamente extraño no se compila en C ++.

Peter N Lewis
fuente
Tengo un poco de curiosidad, cuando compila todo como Objective-C ++, ¿recibe advertencias sobre el uso de conversiones de estilo C y / u otras advertencias específicas de C ++ sobre el código de estilo C válido?
dreamlax
Seguro, está programando en C ++, por lo que se espera que se comporte de manera apropiada, pero como regla general, C ++ es mejor C que C, incluso si nunca crea una clase. No te permitirá hacer cosas estúpidas, y te permitirá hacer cosas agradables (como mejores constantes y enumeraciones y demás). Aún puede lanzar de la misma manera (por ejemplo, (CFFloat) x).
Peter N Lewis
10

Paso 1

Cree un archivo objetivo c (archivo .m) y su archivo de encabezado correspondiente.

// Archivo de encabezado (lo llamamos "ObjCFunc.h")

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

// Archivo de objetivo C correspondiente (lo llamamos "ObjCFunc.m")

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

Paso 2

¡Ahora implementaremos una función c ++ para llamar a la función objetivo c que acabamos de crear! Entonces, para eso, definiremos un archivo .mm y su archivo de encabezado correspondiente (el archivo ". Mm" se usará aquí porque podremos usar codificación Objective C y C ++ en el archivo)

// Archivo de encabezado (lo llamamos "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__) */

// Archivo Objective C ++ correspondiente (lo llamamos "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
}

Paso 3

Llamar a la función c ++ (que en realidad llama al método objetivo 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__

// Llamada final

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

¡Espero que esto funcione!

Mishal Shah
fuente
9

Necesita hacer que su archivo C ++ sea tratado como Objective-C ++. Puede hacer esto en xcode cambiando el nombre de foo.cpp a foo.mm (.mm es la extensión obj-c ++). Entonces, como han dicho otros, la sintaxis de mensajería estándar obj-c funcionará.

olliej
fuente
1

A veces, cambiar el nombre de .cpp a .mm no es una buena idea, especialmente cuando el proyecto es multiplataforma. En este caso, para el proyecto xcode, abro el archivo del proyecto xcode a través de TextEdit, encontré una cadena que contiene el archivo de interés, debería ser como:

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

y luego cambie el tipo de archivo de sourcecode.cpp.cpp a sourcecode.cpp.objcpp

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

Es equivalente a cambiar el nombre de .cpp a .mm

Yevgeniy Logachev
fuente
1

Además, puede llamar al tiempo de ejecución de Objective-C para llamar al método.

Maxthon Chan
fuente
1

La respuesta de @ DawidDrozd anterior es excelente.

Yo agregaría un punto. Las versiones recientes del compilador de Clang se quejan de que se requiere una "transmisión de puente" si se intenta utilizar su código.

Esto parece razonable: usar un trampolín crea un error potencial: dado que las clases de Objective-C cuentan con referencias, si pasamos su dirección como un vacío *, corremos el riesgo de tener un puntero colgante si la clase se recolecta como basura mientras la devolución de llamada aún está activo.

Solución 1) Cocoa proporciona las funciones macro CFBridgingRetain y CFBridgingRelease que presumiblemente suman y restan uno del recuento de referencia del objeto Objective-C. Por lo tanto, debemos tener cuidado con las devoluciones de llamada múltiples, para lanzar la misma cantidad de veces que retenemos.

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

Solución 2) La alternativa es utilizar el equivalente de una referencia débil (es decir, sin cambios en el recuento de retención), sin ninguna seguridad adicional.

El lenguaje Objective-C proporciona el calificador de conversión __bridge para hacer esto (CFBridgingRetain y CFBridgingRelease parecen ser envoltorios finos de Cocoa sobre las construcciones del lenguaje Objective-C __bridge_retained y release respectivamente, pero Cocoa no parece tener un equivalente para __bridge).

Los cambios requeridos son:

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
fuente
Debo admitir que sospecho un poco acerca de si la Solución 1 proporciona seguridad adicional a pesar de todas las teatrales de retención / liberación. Un punto es que estamos pasando una copia de selfal cierre, que podría quedar desactualizado. El otro punto es que no está claro cómo interactúa esto con el recuento automático de referencias y si el compilador puede averiguar qué está pasando. En la práctica, no logré crear una situación en la que cualquiera de las versiones fallara en un ejemplo de juguete simple de un módulo.
QuesterZen
-1

Puede mezclar C ++ con Objectiv-C (Objective C ++). Escriba un método C ++ en su clase Objective C ++ que simplemente lo llame [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];y lo llame desde su C ++.

No lo he probado antes, pero inténtalo y comparte los resultados con nosotros.

hhafez
fuente
2
Pero el problema es cómo puedo llamarlo ... porque si incluyo "EAGLview.h" en mi clase de C ++ obtengo miles de errores.
Juvenis