从 C + + 成员函数调用 Objective-C 方法?

我有一个类(EAGLView) ,它调用一个 C++类的成员函数,没有问题。现在,问题是我需要在那个 C++类中调用一个 objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];,这在 C++语法中是做不到的。

我可以将这个 Objective-C调用封装到同一个 Objective-C类中,这个 Objective-C类首先被称为 C + + 类,但是我需要从 C++中调用这个方法,而且我不知道如何做到这一点。

我尝试给一个指向 EAGLView对象的指针指向 C + + 成员函数,并在我的 C++类头中包含“ EAGLView.h”,但是我得到了3999个错误。.

那么. . 我该怎么做呢? 举个例子就好了. . 我只找到了纯 C的例子。

111719 次浏览

You can mix C++ in with Objectiv-C (Objective C++). Write a C++ method in your Objective C++ class that simply calls [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; and call it from your C++.

我以前没有尝试过,但是给它一个机会,并与我们分享结果。

您可以将代码编译为 Objective-C + +-最简单的方法是重命名您的。中央结算系统。嗯。然后,如果你包含 EAGLView.h(你会得到很多错误,因为 C + + 编译器不理解任何一个 Objective-C 特定的关键字) ,它将正确地编译,你可以(在大多数情况下)混合 Objective-C 和 C + + ,只要你喜欢。

您需要将您的 C + + 文件作为 Objective-C + + 对待。可以在 xcode 中将 foo.cpp 重命名为 foo.mm (。Mm 是 obj-c + + 扩展名)。然后,正如其他人所说,标准的 obj-c 消息传递语法将起作用。

如果你小心的话,你可以把 C + + 和 Objective-C 混合起来。有一些警告,但一般来说,它们可能是混合的。如果你想把它们分开,你可以建立一个标准的 C 包装函式,让 Objective-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

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

包装函式中的 不需要与 Objective-C 类在同一个 .m文件中,但它确实存在于 需要编译为 Objective-C code 文件中。声明包装函式的头部需要同时包含在 CPP 和 Objective-C 代码中。

(注意: 如果 Objective-C 实现文件被赋予了扩展名”。它不会在 Xcode 下链接。「。Mm”扩展告诉 Xcode 期望 Objective-C 和 C + + 的组合,即 Objective-C + +)


可以使用 PIMPL idiom以面向对象的方式实现上述内容。实现只是略有不同。简而言之,将包装函数(在“ MyObject-C-Interface.h”中声明)放在一个类中,该类具有指向 MyClass 实例的(私有) void 指针。

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

Notice the wrapper methods no longer require the void pointer to an instance of MyClass; it is now a private member of MyClassImpl. The init method is used to instantiate a MyClass instance;

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 来实例化的。您可以在 MyClassImpl 的构造函数中实例化 MyClass,但这通常不是一个好主意。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

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

现在可以通过 MyClassImpl 的私有实现访问对 MyClass 的调用。如果您正在开发一个可移植的应用程序,这种方法可能是有利的; 您可以简单地将 MyClass 的实现与特定于其他平台的实现交换... ... 但老实说,这是否是一个更好的实现更多的是一个品味和需求的问题。

最简单的解决方案是简单地告诉 Xcode 将所有内容编译为 Objective C + + 。

为 Compile Source As to Objective C + + 设置项目或目标设置并重新编译。

然后你可以在任何地方使用 C + + 或 Objective C,例如:

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

这与将所有源文件从.cpp 或.m 重命名为.mm 具有相同的效果。

There are two minor downsides to this: clang cannot analyse C++ source code; some relatively weird C code does not compile under C++.

Sometimes renaming .cpp to .mm is not good idea, especially when project is crossplatform. In this case for xcode project I open xcode project file throught TextEdit, found string which contents interest file, it should be like:

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

然后将文件类型从 Cpp.cpp改为 Sourcecode.cpp.objcpp

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

它等效于将.cpp 重命名为. mm

此外,还可以调用 Objective-C 运行时来调用该方法。

第一步

创建一个目标 c 文件(. m 文件)及其相应的头文件。

//头文件(我们称之为“ ObjecCFunc.h”)

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

//相应的 Objective C 文件(我们称之为“ ObjecCFunc.m”)

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

第二步

现在我们将实现一个 c + + 函数来调用我们刚刚创建的目标 c 函数! 因此,我们将定义一个。毫米文件及其相应的头文件(”。这里将使用“ mm”文件,因为我们将能够在该文件中同时使用 Objective C 和 C + + 编码)

//头文件(我们称之为“ ObjecCCall.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 + + 文件(我们称之为“ ObjecCCall.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
}

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

Hope this works!

@ DawidDrozd 上面的回答很棒。

我要补充一点。Clang 编译器的最新版本抱怨说,如果尝试使用他的代码,就需要“桥接强制转换”。

This seems reasonable: using a trampoline creates a potential bug: since Objective-C classes are reference counted, if we pass their address around as a void *, we risk having a hanging pointer if the class is garbage collected up while the callback is still active.

解决方案1) Cocoa 提供 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 语言为此提供了桥接类型的限定符(CFBridgingRetain 和 CFBridgingrelease 似乎分别是 Objective-C 语言结构保留和发布上的薄 Cocoa 包装器,但 Cocoa 似乎没有相应的 for _ 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);
}