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


111

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

我可以将此Objective-C调用包装到Objective-C最初称为C ++类的同一类中,但是随后我需要以某种方式从中调用该方法C++,而我不知道该怎么做。

我试图提供一个指向EAGLViewC ++成员函数的对象的指针,并EAGLView.hC++类标题中包含“ ”,但出现3999错误。

所以..我应该怎么做?有一个很好的例子。我只发现C这样做的纯粹例子。

Answers:


204

如果仔细做,您可以将C ++与Objective-C混合使用。有一些警告,但总的来说,它们可以混合在一起。如果要将它们分开,则可以设置一个标准的C包装函数,该函数从非Object-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实例的void指针;它现在是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

注意,通过调用MyClassImpl :: init实例化了MyClass。您可以在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

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

现在,您可以通过MyClassImpl的私有实现访问对MyClass的调用。如果您正在开发便携式应用程序,则此方法可能会很有优势。您可以简单地将MyClass的实现与另一个平台的特定交换掉……但是说实话,这是否是一个更好的实现,更多地取决于品味和需求。


6
嗨,我尝试过,但是出现链接错误,提示未找到符号。即它找不到MyObjectDoSomethingWith。有任何想法吗?

3
您可能需要extern "C"int MyObjectDoSomethingWith
dreamlax 2010年

2
已经尝试过了,不起作用,这很有意义,因为当我们想从C调用C ++函数时,使用了extern“ C”,在这种情况下,我们要从C ++调用C函数,不是吗?

6
另外,如何在MyCPPClass.cpp中实例化ObjectiveCObject?
拉菲·哈查杜安

2
很棒的@dreamlax,现在正在编译,但是我不知道该怎么称呼它为“ someMethod”。应该将哪些参数添加到:int MyCPPClass :: someMethod(void * objectiveCObject,void * aParameter)???
the_moon 2014年

15

您可以将代码编译为Objective-C ++-最简单的方法是将.cpp重命名为.mm。如果包含,它将正确编译EAGLView.h(由于C ++编译器无法理解任何特定于Objective-C的关键字,您将收到很多错误),并且可以(大部分情况下)混合使用Objective-C和C ++,但是您可以喜欢。


您是在 C ++文件中得到所有这些编译器错误,还是在碰巧包含此C ++标头的某些其他C ++文件中得到这些错误?
杰西·贝德

似乎我无法在C ++头文件中包含EAGLView.h,因为由于某种原因,它希望目标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 ++源代码; clang无法分析C ++源代码。一些相对怪异的C代码无法在C ++下编译。


我只是有点好奇,当您将所有内容编译为Objective-C ++时,会收到有关使用C样式强制转换的警告和/或其他有关有效C样式代码的特定于C ++的警告吗?
dreamlax

当然,您正在使用C ++进行编程,因此您应该表现得很正常-但作为一般规则,即使您从未创建类,C ++也比C是更好的C。它不会让你做愚蠢的事情,并且可以让你做美好的事情(比如更好的常量和枚举等等)。您仍然可以进行相同的投射(例如(CFFloat)x)。
Peter N Lewis

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
}

第三步

调用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项目,我通过TextEdit打开xcode项目文件,找到了包含感兴趣文件的字符串,它应该像这样:

/* 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)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语言提供了__bridge强制转换限定符(CFBridgingRetain和CFBridgingRelease似乎分别是Objective-C语言构造__bridge_retained和release的薄可可包装器,但Cocoa似乎不具有__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(目标C ++)混合使用。在您的Objective C ++类中编写一个简单地调用的C ++方法[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];从C ++并调用它。

我没有尝试过自己,但尝试一下,然后与我们分享结果。


2
但是问题在于我该怎么称呼它……因为如果我在C ++类中包含“ EAGLview.h”,则会出现数千个错误。
juvenis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.