Вызов метода Objective-C из функции-члена C ++?

111

У меня есть class ( EAGLView), который C++без проблем вызывает функцию-член класса. Теперь проблема в том, что мне нужно вызвать этот C++класс a, objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];что я не могу сделать в C++синтаксисе.

Я мог бы перенести этот Objective-Cвызов в тот же Objective-Cкласс, который вначале называл класс C ++, но затем мне нужно как-то вызвать этот метод C++, и я не могу понять, как это сделать.

Я попытался указать указатель на EAGLViewобъект функции-члена C ++ и включить " EAGLView.h" в C++заголовок моего класса, но получил 3999 ошибок.

Итак .. как мне это сделать? Пример был бы хорош ... Я нашел только чистые Cпримеры этого.

ювенис
источник

Ответы:

204

Вы можете смешивать C ++ с Objective-C, если будете делать это осторожно. Есть несколько предостережений, но в целом их можно смешивать. Если вы хотите сохранить их отдельно, вы можете настроить стандартную функцию-оболочку C, которая дает объекту Objective-C удобный интерфейс в стиле C из кода, отличного от Objective-C (выберите более удачные имена для ваших файлов, я выбрал эти имена за многословие):

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

Функция-оболочка не обязательно должна находиться в том же .mфайле, что и класс Objective-C, но файл, в котором она существует, должен быть скомпилирован как код Objective-C. . Заголовок, объявляющий функцию-оболочку, должен быть включен как в код CPP, так и в код Objective-C.

(ПРИМЕЧАНИЕ: если файлу реализации Objective-C присвоено расширение «.m», он не будет связываться под Xcode. Расширение «.mm» указывает Xcode ожидать комбинации Objective-C и C ++, т. Е. Objective-C ++. )


Вы можете реализовать описанное выше объектно-ориентированным образом, используя идиому PIMPL . Реализация немного отличается. Короче говоря, вы помещаете функции-оболочки (объявленные в «MyObject-C-Interface.h») внутри класса с (частным) указателем void на экземпляр 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

Обратите внимание, что методам-оболочкам больше не требуется указатель void на экземпляр MyClass; теперь он является частным членом MyClassImpl. Метод init используется для создания экземпляра 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

Обратите внимание, что MyClass создается с помощью вызова MyClassImpl :: init. Вы можете создать экземпляр MyClass в конструкторе MyClassImpl, но обычно это не очень хорошая идея. Экземпляр MyClass уничтожен деструктором MyClassImpl. Как и в случае реализации в стиле C, методы оболочки просто подчиняются соответствующим методам 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." );
    }
}

Теперь вы получаете доступ к вызовам MyClass через частную реализацию MyClassImpl. Этот подход может быть выгоден, если вы разрабатываете переносимое приложение; вы можете просто поменять реализацию MyClass на одну, специфичную для другой платформы ... но, честно говоря, лучшая ли эта реализация - это скорее вопрос вкуса и потребностей.

dreamlax
источник
6
Привет, я попробовал, но получаю сообщение об ошибке связывания, что символ (символы) не найден. т.е. он не может найти MyObjectDoSomethingWith. Любые идеи?
3
Возможно, вам придется добавить extern "C"доint MyObjectDoSomethingWith
dreamlax
2
пробовал это уже, не работает, и это имеет смысл, потому что extern «C» используется, когда мы хотим вызвать функцию C ++ из C, в этом случае мы вызываем функцию C из C ++, нет?
6
Кроме того, как объект objectiveCObject создается в MyCPPClass.cpp?
Раффи Хатчадурян,
2
Замечательный @dreamlax, который сейчас компилируется, но я не знаю, как назвать это «someMethod». Какие параметры следует добавить к: int MyCPPClass :: someMethod (void * objectiveCObject, void * aParameter) ???
the_moon
15

Вы можете скомпилировать свой код как Objective-C ++ - самый простой способ - переименовать ваш .cpp как .mm. Затем он будет правильно скомпилирован, если вы включите EAGLView.h(вы получали так много ошибок, потому что компилятор C ++ не понимал ни одного из ключевых слов Objective-C), и вы можете (по большей части) смешивать Objective-C и C ++, но вы лайк.

Джесси Бедер
источник
Вы получаете все эти ошибки компилятора в этом файле C ++ или они есть в каком-либо другом файле C ++, который включает этот заголовок C ++?
Джесси Бедер,
Кажется, я не могу включить EAGLView.h в файл заголовка C ++, потому что тогда он по какой-то причине ожидает, что код Objective C будет C ++, и не понимает @ + другие символы
juvenis
12

Самое простое решение - просто указать Xcode скомпилировать все как Objective C ++.

Задайте для вашего проекта или цели параметры компиляции исходных файлов как Objective C ++ и перекомпилируйте.

Тогда вы можете везде использовать C ++ или Objective C, например:

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

Это имеет тот же эффект, что и переименование всех ваших исходных файлов из .cpp или .m в .mm.

У этого есть два незначительных недостатка: clang не может анализировать исходный код C ++; какой-то относительно странный код C не компилируется под C ++.

Питер Н. Льюис
источник
Мне просто немного любопытно, когда вы компилируете все как Objective-C ++, получаете ли вы предупреждения об использовании приведений в стиле C и / или других специфических для C ++ предупреждений о правильном коде в стиле C?
dreamlax
Конечно, вы программируете на C ++, поэтому от вас ожидается, что вы будете вести себя надлежащим образом, но, как правило, C ++ лучше C, чем C, даже если вы никогда не создаете класс. Он не позволит вам делать глупые вещи, и он позволяет вам делать приятные вещи (например, улучшенные константы, перечисления и т. Д.). Вы все еще можете использовать то же самое (например, (CFFloat) x).
Питер Н Льюис,
10

Шаг 1

Создайте целевой файл c (файл .m) и соответствующий ему файл заголовка.

// Заголовочный файл (мы называем его ObjCFunc.h)

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

// Соответствующий файл Objective C (мы называем его ObjCFunc.m)

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

Шаг 2

Теперь мы реализуем функцию c ++ для вызова только что созданной целевой функции c! Поэтому для этого мы определим файл .mm и соответствующий ему файл заголовка (здесь должен использоваться файл «. Mm», потому что мы сможем использовать в файле кодировку как Objective C, так и C ++)

// Заголовочный файл (мы называем его 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__) */

// Соответствующий файл Objective C ++ (мы называем его 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
}

Шаг 3

Вызов функции c ++ (которая фактически вызывает метод target 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__

//Последний вызов

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

Надеюсь, это сработает!

Мишал Шах
источник
9

Вам нужно, чтобы ваш файл C ++ обрабатывался как Objective-C ++. Вы можете сделать это в xcode, переименовав foo.cpp в foo.mm (.mm - это расширение obj-c ++). Тогда, как говорили другие, стандартный синтаксис обмена сообщениями obj-c будет работать.

olliej
источник
1

Иногда переименование .cpp в .mm не является хорошей идеей, особенно если проект является кроссплатформенным. В этом случае для проекта xcode я открываю файл проекта xcode через TextEdit, обнаруживаю строку, которая содержит интересующий файл, она должна быть такой:

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

а затем измените тип файла с sourcecode.cpp.cpp на sourcecode.cpp.objcpp

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

Это эквивалентно переименованию .cpp в .mm

Евгений Логачев
источник
1

Кроме того, вы можете вызвать среду выполнения Objective-C для вызова метода.

Макстон Чан
источник
1

Ответ @ DawidDrozd выше отличный.

Я бы добавил одно очко. Последние версии компилятора Clang жалуются на то, что при попытке использовать его код требуется «мостовое приведение».

Это кажется разумным: использование трамплина создает потенциальную ошибку: поскольку классы Objective-C подсчитываются по ссылкам, если мы передадим их адрес как void *, мы рискуем получить висящий указатель, если класс собирает мусор, пока обратный вызов все еще активный.

Решение 1) Какао предоставляет макрофункции CFBridgingRetain и CFBridgingRelease, которые предположительно добавляют и вычитают единицу из счетчика ссылок объекта Objective-C. Поэтому мы должны быть осторожны с несколькими обратными вызовами, чтобы выпускать столько же раз, сколько мы сохраняем.

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

Решение 2) Альтернативой является использование эквивалента слабой ссылки (т. Е. Без изменения счетчика удержания) без какой-либо дополнительной безопасности.

Язык Objective-C предоставляет для этого квалификатор приведения __bridge (CFBridgingRetain и CFBridgingRelease кажутся тонкими оболочками Cocoa над конструкциями языка Objective-C __bridge_rehibited и release соответственно, но какао, похоже, не имеет эквивалента для __bridge).

Необходимые изменения:

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
источник
Я должен признать, что я несколько подозрительно отношусь к тому, обеспечивает ли Решение 1 дополнительную безопасность, несмотря на всю театральность сохранения / освобождения. Во-первых, мы передаем selfв закрытие копию , которая может устареть. Другой момент заключается в том, что неясно, как это взаимодействует с автоматическим подсчетом ссылок и может ли компилятор выяснить, что происходит. На практике мне не удалось создать ситуацию, когда ни одна из версий не сработала в простом примере с одной модульной игрушкой.
QuesterZen
-1

Вы можете смешивать C ++ с Objectiv-C (Objective C ++). Напишите метод C ++ в своем классе Objective C ++, который просто вызывает [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];и вызывает его из вашего C ++.

Я не пробовал это раньше, но попробую и поделитесь с нами результатами.

хафез
источник
2
Но проблема в том, как я могу это назвать ... потому что, если я включаю "EAGLview.h" в свой класс C ++, я получаю тысячи ошибок.
juvenis