Chamando o método Objective-C da função de membro C ++?

111

Eu tenho um class ( EAGLView) que chama uma função de membro de uma C++classe sem problemas. Agora, o problema é que preciso chamar essa C++classe de um, o objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];que não posso fazer na C++sintaxe.

Eu poderia encapsular essa Objective-Cchamada para a mesma Objective-Cclasse que, em primeiro lugar, chamou a classe C ++, mas então preciso chamar esse método de alguma forma C++e não consigo descobrir como fazer isso.

Tentei fornecer um ponteiro para o EAGLViewobjeto da função de membro C ++ e incluir o " EAGLView.h" no C++cabeçalho da minha classe, mas obtive erros 3999.

Então ... como devo fazer isso? Um exemplo seria bom .. Eu só encontrei Cexemplos puros de como fazer isso.

juvenis
fonte

Respostas:

204

Você pode misturar C ++ com Objective-C se fizer isso com cuidado. Existem algumas ressalvas, mas, de modo geral, elas podem ser misturadas. Se você quiser mantê-los separados, você pode configurar uma função de wrapper C padrão que dá ao objeto Objective-C uma interface de estilo C utilizável de código não Objective-C (escolha nomes melhores para seus arquivos, eu escolhi esses nomes para verbosidade):

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

A função wrapper não precisa estar no mesmo .marquivo que a classe Objective-C, mas o arquivo em que ela existe precisa ser compilado como código Objective-C . O cabeçalho que declara a função wrapper precisa ser incluído no código CPP e Objective-C.

(NOTA: se o arquivo de implementação Objective-C receber a extensão ".m", ele não será vinculado ao Xcode. A extensão ".mm" diz ao Xcode para esperar uma combinação de Objective-C e C ++, ou seja, Objective-C ++. )


Você pode implementar o acima de uma maneira orientada a objetos usando o idioma PIMPL . A implementação é apenas ligeiramente diferente. Resumindo, você coloca as funções de wrapper (declaradas em "MyObject-C-Interface.h") dentro de uma classe com um ponteiro void (privado) para uma instância 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 os métodos de wrapper não requerem mais o ponteiro void para uma instância de MyClass; agora é um membro privado de MyClassImpl. O método init é usado para instanciar uma instância 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 é instanciado com uma chamada para MyClassImpl :: init. Você poderia instanciar MyClass no construtor de MyClassImpl, mas isso geralmente não é uma boa ideia. A instância MyClass é destruída do destruidor de MyClassImpl. Tal como acontece com a implementação do estilo C, os métodos de wrapper simplesmente se referem aos respectivos métodos 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." );
    }
}

Agora você acessa chamadas para MyClass por meio de uma implementação privada de MyClassImpl. Essa abordagem pode ser vantajosa se você estiver desenvolvendo um aplicativo portátil; você poderia simplesmente trocar a implementação de MyClass por uma específica para a outra plataforma ... mas, honestamente, se esta é uma implementação melhor é mais uma questão de gosto e necessidades.

dreamlax
fonte
6
Olá, tentei, mas recebo um erro de ligação dizendo que o (s) símbolo (s) não foram encontrados. ou seja, não consegue encontrar MyObjectDoSomethingWith. alguma ideia?
3
Talvez seja necessário adicionar extern "C"antes deint MyObjectDoSomethingWith
dreamlax
2
já tentei isso, não funciona e faz sentido porque extern "C" é usado quando queremos chamar uma função C ++ de C, neste caso estamos chamando uma função C de C ++, não?
6
Além disso, como o goalCObject é instanciado em MyCPPClass.cpp?
Raffi Khatchadourian
2
Excelente @dreamlax, que está compilando agora, mas não sei como chamar isso de "algumMetodo". Quais devem ser os parâmetros que devem ser adicionados a: int MyCPPClass :: someMethod (void * goalCObject, void * aParameter) ???
the_moon
15

Você pode compilar seu código como Objective-C ++ - a maneira mais simples é renomear seu .cpp como .mm. Ele irá então compilar corretamente se você incluir EAGLView.h(você estava recebendo tantos erros porque o compilador C ++ não entendia nenhuma das palavras-chave específicas do Objective-C), e você pode (na maior parte) misturar Objective-C e C ++ como quiser gostar.

Jesse Beder
fonte
Você está recebendo todos os erros do compilador neste arquivo C ++ ou eles estão em algum outro arquivo C ++ que por acaso inclui este cabeçalho C ++?
Jesse Beder
Parece que não posso incluir o EAGLView.h no arquivo de cabeçalho C ++ porque, por algum motivo, ele espera que o código Objective C seja C ++ e não entende @ + outros símbolos
juvenis
12

A solução mais fácil é simplesmente dizer ao Xcode para compilar tudo como Objective C ++.

Defina suas configurações de projeto ou destino para Compile Sources As to Objective C ++ e recompile.

Então você pode usar C ++ ou Objective C em qualquer lugar, por exemplo:

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

Isso tem o mesmo efeito que renomear todos os seus arquivos de origem de .cpp ou .m para .mm.

Existem duas pequenas desvantagens nisso: o clang não pode analisar o código-fonte C ++; alguns códigos C relativamente estranhos não compilam em C ++.

Peter N Lewis
fonte
Estou apenas um pouco curioso, quando você compila tudo como Objective-C ++, você recebe avisos sobre o uso de casts de estilo C e / ou outro C ++ - avisos específicos sobre código de estilo C válido?
dreamlax
Claro, você está programando em C ++, portanto, espera-se que se comporte de maneira adequada - mas, como regra geral, C ++ é melhor C do que C, mesmo que você nunca crie uma classe. Ele não deixará você fazer coisas estúpidas e permite que você faça coisas legais (como constantes e enums melhores e assim por diante). Você ainda pode lançar da mesma forma (por exemplo, (CFFloat) x).
Peter N Lewis
10

Passo 1

Crie um arquivo c objetivo (arquivo .m) e seu arquivo de cabeçalho correspondente.

// Arquivo de cabeçalho (chamamos de "ObjCFunc.h")

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

// Arquivo Objective C correspondente (nós o chamamos de "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

Agora vamos implementar uma função c ++ para chamar a função c objetiva que acabamos de criar! Portanto, para isso definiremos um arquivo .mm e seu arquivo de cabeçalho correspondente (o arquivo ". Mm" deve ser usado aqui porque poderemos usar codificação Objective C e C ++ no arquivo)

// Arquivo de cabeçalho (chamamos de "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__) */

// Arquivo Objective C ++ correspondente (nós o chamamos de "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
}

etapa 3

Chamar a função c ++ (que realmente chama o método c objetivo)

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

//Última chamada

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

Mishal Shah
fonte
9

Você precisa fazer com que seu arquivo C ++ seja tratado como Objective-C ++. Você pode fazer isso no xcode renomeando foo.cpp para foo.mm (.mm é a extensão obj-c ++). Então, como outros disseram, a sintaxe padrão de mensagens obj-c funcionará.

olliej
fonte
1

Às vezes, renomear .cpp para .mm não é uma boa ideia, especialmente quando o projeto é multiplataforma. Neste caso, para o projeto xcode, eu abro o arquivo do projeto xcode através do TextEdit, encontrei uma string cujo conteúdo é o arquivo de interesse, deve ser assim:

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

e, em seguida, altere o tipo de arquivo de sourcecode.cpp.cpp para sourcecode.cpp.objcpp

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

É equivalente a renomear .cpp para .mm

Yevgeniy Logachev
fonte
1

Além disso, você pode chamar o tempo de execução Objective-C para chamar o método.

Maxthon Chan
fonte
1

A resposta de @ DawidDrozd acima é excelente.

Eu acrescentaria um ponto. Versões recentes do compilador Clang reclamam da necessidade de um "elenco de ponte" ao tentar usar seu código.

Isso parece razoável: usar um trampolim cria um bug em potencial: como as classes Objective-C são contadas por referência, se passarmos seu endereço como um vazio *, corremos o risco de ter um ponteiro pendurado se a classe for coletada como lixo enquanto o retorno de chamada ainda está ativo.

Solução 1) Cocoa fornece funções de macro CFBridgingRetain e CFBridgingRelease que provavelmente adicionam e subtraem um da contagem de referência do objeto Objective-C. Devemos, portanto, ter cuidado com vários retornos de chamada, para liberar o mesmo número de vezes que retemos.

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

Solução 2) A alternativa é usar o equivalente a uma referência fraca (ou seja, nenhuma alteração na contagem de retenção), sem qualquer segurança adicional.

A linguagem Objective-C fornece o qualificador de conversão __bridge para fazer isso (CFBridgingRetain e CFBridgingRelease parecem ser wrappers Cocoa finos sobre as construções da linguagem Objective-C __bridge_retained e release respectivamente, mas Cocoa não parece ter um equivalente para __bridge).

As mudanças necessárias são:

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
fonte
Tenho que admitir que estou um pouco desconfiado sobre se a Solução 1 oferece alguma segurança adicional, apesar de todos os recursos de retenção / liberação. Um ponto é que estamos passando uma cópia de selfpara o encerramento, que pode ficar desatualizado. O outro ponto é que não está claro como isso interage com a contagem automática de referência e se o compilador pode descobrir o que está acontecendo. Na prática, não consegui criar uma situação em que nenhuma das versões falhasse em um exemplo simples de brinquedo de um módulo.
QuesterZen
-1

Você pode misturar C ++ com Objectiv-C (Objective C ++). Escreva um método C ++ em sua classe Objective C ++ que simplesmente o chame [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];e chame de seu C ++.

Não tentei antes, mas experimente e compartilhe os resultados conosco.

Hhafez
fonte
2
Mas o problema é como posso chamá-lo ... porque se eu incluir "EAGLview.h" na minha classe C ++, recebo milhares de erros.
juvenis de