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.

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.


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.


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


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!


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


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


1

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


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

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.


2
Mas o problema é como posso chamá-lo ... porque se eu incluir "EAGLview.h" na minha classe C ++, recebo milhares de erros.
juvenis de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.