Memanggil metode Objective-C dari fungsi anggota C ++?

111

Saya memiliki kelas ( EAGLView) yang memanggil fungsi anggota C++kelas tanpa masalah. Sekarang, masalahnya adalah saya perlu memanggil di C++kelas itu a objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];yang tidak dapat saya lakukan dalam C++sintaks.

Saya bisa membungkus Objective-Cpanggilan ini ke Objective-Ckelas yang sama yang pertama disebut kelas C ++, tetapi kemudian saya perlu memanggil metode itu dari C++, dan saya tidak tahu bagaimana melakukannya.

Saya mencoba memberikan penunjuk ke EAGLViewobjek ke fungsi anggota C ++ dan menyertakan " EAGLView.h" di C++tajuk kelas saya tetapi saya mendapat 3999 kesalahan ..

Jadi .. bagaimana saya harus melakukan ini? Sebuah contoh akan menyenangkan .. Saya hanya menemukan Ccontoh murni untuk melakukan ini.

juvenis
sumber

Jawaban:

204

Anda dapat mencampur C ++ dengan Objective-C jika Anda melakukannya dengan hati-hati. Ada beberapa peringatan tetapi secara umum mereka dapat dicampur. Jika Anda ingin memisahkannya, Anda dapat mengatur fungsi pembungkus C standar yang memberikan objek Objective-C antarmuka gaya-C yang dapat digunakan dari kode non-Objective-C (pilih nama yang lebih baik untuk file Anda, saya telah memilih nama-nama ini untuk verbositas):

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

Fungsi pembungkus tidak perlu berada dalam .mfile yang sama dengan kelas Objective-C, tetapi file yang ada di dalamnya perlu dikompilasi sebagai kode Objective-C . Header yang mendeklarasikan fungsi wrapper harus disertakan dalam kode CPP dan Objective-C.

(CATATAN: jika file implementasi Objective-C diberi ekstensi ".m" itu tidak akan terhubung di bawah Xcode. Ekstensi ".mm" memberitahu Xcode untuk mengharapkan kombinasi Objective-C dan C ++, yaitu Objective-C ++. )


Anda dapat mengimplementasikan di atas dengan cara Berorientasi Objek dengan menggunakan idiom PIMPL . Implementasinya hanya sedikit berbeda. Singkatnya, Anda menempatkan fungsi pembungkus (dideklarasikan dalam "MyObject-C-Interface.h") di dalam kelas dengan penunjuk kosong (pribadi) ke sebuah instance dari 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

Perhatikan bahwa metode pembungkus tidak lagi memerlukan penunjuk kosong ke instance MyClass; sekarang menjadi anggota pribadi MyClassImpl. Metode init digunakan untuk membuat instance 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

Perhatikan bahwa MyClass dibuat dengan panggilan ke MyClassImpl :: init. Anda bisa membuat instance MyClass di konstruktor MyClassImpl, tetapi itu umumnya bukan ide yang bagus. Instance MyClass dihancurkan dari destruktor MyClassImpl. Seperti dengan implementasi C-style, metode pembungkus hanya tunduk pada metode MyClass masing-masing.

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

Anda sekarang mengakses panggilan ke MyClass melalui implementasi pribadi MyClassImpl. Pendekatan ini dapat menguntungkan jika Anda mengembangkan aplikasi portabel; Anda dapat dengan mudah menukar implementasi MyClass dengan yang satu spesifik ke platform lain ... tapi jujur, apakah ini implementasi yang lebih baik lebih merupakan masalah selera dan kebutuhan.

dreamlax
sumber
6
Hai, Saya mencobanya tetapi saya mendapatkan kesalahan tautan yang mengatakan simbol tidak ditemukan. yaitu tidak dapat menemukan MyObjectDoSomethingWith. ada ide?
3
Anda mungkin perlu menambahkan extern "C"sebelumint MyObjectDoSomethingWith
dreamlax
2
mencoba itu, tidak berhasil dan masuk akal karena extern "C" digunakan ketika kita ingin memanggil fungsi C ++ dari C, dalam hal ini kita memanggil fungsi C dari C ++, bukan?
6
Juga, bagaimana objektifCObject bisa dipakai di MyCPPClass.cpp?
Raffi Khatchadourian
2
Keren @dreamlax, yang sedang dikompilasi sekarang tapi saya tidak tahu untuk menyebutnya "beberapa metode". Manakah yang harus menjadi parameter yang harus ditambahkan ke: int MyCPPClass :: someMethod (void * objectiveCObject, void * aParameter) ???
the_moon
15

Anda dapat mengompilasi kode Anda sebagai Objective-C ++ - cara termudah adalah dengan mengganti nama .cpp Anda menjadi .mm. Ini kemudian akan dikompilasi dengan benar jika Anda menyertakan EAGLView.h(Anda mendapatkan begitu banyak kesalahan karena kompiler C ++ tidak memahami salah satu kata kunci spesifik Objective-C), dan Anda dapat (untuk sebagian besar) mencampur Objective-C dan C ++ bagaimanapun Anda Suka.

Jesse Beder
sumber
Apakah Anda mendapatkan semua kesalahan-kesalahan compiler di ini C ++ berkas, atau mereka dalam beberapa lainnya C ++ file yang terjadi untuk menyertakan C ini ++ sundulan?
Jesse Beder
Sepertinya saya tidak dapat menyertakan EAGLView.h di file header C ++ karena untuk beberapa alasan mengharapkan kode Objective C adalah C ++, dan tidak mengerti @ + simbol lain
juvenis
12

Solusi termudah adalah dengan memberi tahu Xcode untuk mengkompilasi semuanya sebagai Objective C ++.

Setel project atau setelan target Anda untuk Compile Sources As ke Objective C ++ dan kompilasi ulang.

Kemudian Anda dapat menggunakan C ++ atau Objective C di mana saja, misalnya:

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

Ini memiliki pengaruh yang sama seperti mengganti nama semua file sumber Anda dari .cpp atau .m menjadi .mm.

Ada dua kelemahan kecil untuk ini: clang tidak dapat menganalisis kode sumber C ++; beberapa kode C yang relatif aneh tidak dapat dikompilasi di bawah C ++.

Peter N Lewis
sumber
Saya hanya sedikit penasaran, ketika Anda mengkompilasi semuanya sebagai Objective-C ++ apakah Anda mendapatkan peringatan tentang penggunaan cast gaya-C dan / atau C ++ lainnya - peringatan khusus tentang kode gaya-C yang valid?
dreamlax
Tentu, Anda memprogram dalam C ++ sehingga Anda diharapkan berperilaku dengan tepat - tetapi sebagai aturan umum, C ++ adalah C yang lebih baik daripada C, meskipun Anda tidak pernah membuat kelas. Itu tidak akan membiarkan Anda melakukan hal-hal bodoh, dan itu memungkinkan Anda melakukan hal-hal baik (seperti konstanta dan enum yang lebih baik dan semacamnya). Anda masih dapat melakukan cast yang sama (misalnya (CFFloat) x).
Peter N Lewis
10

Langkah 1

Buat file c tujuan (file .m) dan file header yang sesuai.

// File header (Kami menyebutnya "ObjCFunc.h")

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

// File C Tujuan yang sesuai (Kami menyebutnya "ObjCFunc.m")

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

Langkah 2

Sekarang kita akan mengimplementasikan fungsi c ++ untuk memanggil fungsi tujuan c yang baru saja kita buat! Jadi untuk itu kita akan mendefinisikan file .mm dan file header yang sesuai (file ".mm" akan digunakan di sini karena kita akan dapat menggunakan pengkodean Objective C dan C ++ dalam file)

// File header (Kami menyebutnya "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__) */

// File C ++ Objective yang sesuai (Kami menyebutnya "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
}

LANGKAH 3

Memanggil fungsi c ++ (yang sebenarnya memanggil metode tujuan 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__

//Panggilan terakhir

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

Semoga berhasil!

Mishal Shah
sumber
9

Anda perlu membuat file C ++ Anda diperlakukan sebagai Objective-C ++. Anda dapat melakukan ini di xcode dengan mengganti nama foo.cpp menjadi foo.mm (.mm adalah ekstensi obj-c ++). Kemudian seperti yang dikatakan orang lain, sintaks perpesanan standar obj-c akan berfungsi.

olliej.dll
sumber
1

Terkadang mengganti nama .cpp menjadi .mm bukanlah ide yang baik, terutama jika proyek bersifat lintas platform. Dalam hal ini untuk proyek xcode saya membuka file proyek xcode melalui TextEdit, menemukan string yang berisi file yang menarik, seharusnya seperti:

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

dan kemudian ubah jenis file dari sourcecode.cpp.cpp menjadi sourcecode.cpp.objcpp

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

Ini setara dengan mengganti nama .cpp menjadi .mm

Yevgeniy Logachev
sumber
1

Selain itu, Anda dapat memanggil runtime Objective-C untuk memanggil metode tersebut.

Maxthon Chan
sumber
1

Jawaban @ DawidDrozd di atas luar biasa.

Saya akan menambahkan satu poin. Versi terbaru dari compiler Clang mengeluh tentang perlunya "bridging cast" jika mencoba menggunakan kodenya.

Ini tampaknya masuk akal: menggunakan trampolin menciptakan bug potensial: karena kelas Objective-C adalah referensi yang dihitung, jika kita meneruskan alamatnya sebagai void *, kita berisiko mengalami penunjuk yang menggantung jika kelas tersebut dikumpulkan sampah saat callback masih aktif.

Solusi 1) Kakao menyediakan fungsi makro CFBridgingRetain dan CFBridgingRelease yang mungkin menambah dan mengurangi satu dari jumlah referensi objek Objective-C. Oleh karena itu, kita harus berhati-hati dengan beberapa callback, untuk merilis jumlah yang sama seperti yang kita pertahankan.

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

Solusi 2) Alternatifnya adalah dengan menggunakan ekuivalen dari referensi lemah (mis. Tidak ada perubahan pada jumlah penahan), tanpa keamanan tambahan.

Bahasa Objective-C menyediakan __bridge cast qualifier untuk melakukan ini (CFBridgingRetain dan CFBridgingRelease tampaknya menjadi pembungkus Kakao tipis di atas konstruksi bahasa Objective-C __bridge_retained dan rilis masing-masing, tetapi Cocoa tampaknya tidak memiliki padanan untuk __bridge).

Perubahan yang dibutuhkan adalah:

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
sumber
Saya harus mengakui bahwa saya agak curiga tentang apakah Solusi 1 memberikan keamanan tambahan terlepas dari semua sandiwara penahan / rilis. Satu hal adalah bahwa kita meneruskan salinan selfke dalam closure, yang bisa menjadi usang. Hal lainnya adalah tidak jelas bagaimana hal ini berinteraksi dengan penghitungan referensi otomatis dan apakah compiler dapat mengetahui apa yang sedang terjadi. Dalam praktiknya saya tidak berhasil membuat situasi di mana salah satu versi gagal dalam contoh mainan satu modul sederhana.
QuesterZen
-1

Anda dapat mencampur C ++ dengan Objectiv-C (Objective C ++). Tulis metode C ++ di kelas Objective C ++ Anda yang hanya memanggil [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];dan memanggilnya dari C ++.

Saya belum pernah mencobanya sebelumnya, tetapi mencobanya, dan membagikan hasilnya kepada kami.

hhafez.dll
sumber
2
Tetapi masalahnya adalah bagaimana saya bisa menyebutnya ... karena jika saya menyertakan "EAGLview.h" di kelas C ++ saya, saya mendapatkan ribuan kesalahan.
juvenis