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


111

У мене є клас ( EAGLView), який C++без проблем викликає функцію члена класу. Тепер проблема полягає в тому, що мені потрібно викликати в тому C++класі клас, 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-стилі, з коду, що не є об'єктивним-С (виберіть кращі назви для своїх файлів, я вибрав ці імена для багатослівності):

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") всередині класу з (приватним) недійсним покажчиком на екземпляр 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

Зауважте, що способи обгортки більше не вимагають вказівника недійсності на екземпляр 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 однією конкретною платформою на іншу ... але чесно кажучи, чи це краща реалізація - це питання смаку та потреб.


6
Привіт, я спробував це, але я отримую помилку зв’язку, кажучи, що символ (и) не знайдено. тобто він не може знайти MyObjectDoSomethingWith. якісь ідеї?

3
Можливо, вам потрібно буде додати extern "C"доint MyObjectDoSomethingWith
dreamlax

2
спробував це вже, не працює, і це має сенс, тому що зовнішній "C" використовується, коли ми хочемо викликати функцію C ++ з C, в цьому випадку ми викликаємо функцію C від C ++, ні?

6
Крім того, як об’єктивується objectCObject в MyCPPClass.cpp?
Раффі Хатчадуріан

2
Дивовижний @dreamlax, який зараз компілюється, але я не знаю, як би це називати "someMethod". Які параметри слід додати до: int MyCPPClass :: someMethod (void * aimCObject, void * aParameter) ???
the_moon

15

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


Чи отримуєте ви всі ці помилки компілятора в цьому файлі C ++, чи вони містяться в іншому файлі C ++, який, зокрема, містить цей заголовок C ++?
Джессі Бедер

Здається, я не можу включити EAGLView.h до файлу заголовка C ++, оскільки тоді він чомусь очікує, що код об'єктивного C - це C ++, і не розуміє @ + інші символи
juvenis

12

Найпростіше рішення - просто сказати Xcode скласти все як об'єктив C ++.

Встановіть проект або цільові параметри для джерел компіляції щодо цілі C ++ та перекомпілюйте.

Тоді ви можете використовувати C ++ або Objective C скрізь, наприклад:

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

Це має той самий ефект, що і перейменування всіх вихідних файлів з .cpp або .m у .mm.

У цьому є два незначні мінуси: кланг не може проаналізувати вихідний код 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

// Відповідний файл 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, яку ми тільки що створили! Отже, для цього ми визначимо.

// Заголовок файлу (ми називаємо його "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__) */

// Відповідний цільовий файл 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 ++ (яка фактично викликає метод об'єктивного 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 буде працювати.


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

@ Відповідь ДавідДрозда вище відмінна.

Я б додав один бал. Останні версії компілятора Clang скаржаться на те, що вимагає "мостового моменту" при спробі використання його коду.

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

Рішення 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 здаються тонкими обгортками какао над конструкціями мови Objective-C __bridge_retain та випускають відповідно, але, схоже, какао не має еквівалента __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);
}

Я маю визнати, що я дещо підозрілий щодо того, чи забезпечує Рішення 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.