我有一个类(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
这样做的例子。
答案 0 :(得分:194)
如果仔细操作,可以将C ++与Objective-C混合使用。有一些警告,但一般来说,他们可以混合。如果你想将它们分开,你可以设置一个标准的C包装函数,它为Objective-C对象提供了一个非Objective-C代码的可用C风格接口(为你的文件选择更好的名字,我选择了这些名字)为了冗长):
#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
#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
#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
#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
文件中,但它所存在的文件< strong> 需要编译为Objective-C代码 。声明包装函数的标头需要包含在CPP和Objective-C代码中。
(注意:如果Objective-C实现文件的扩展名为“.m”,它将不会在Xcode下链接。“。mm”扩展名告诉Xcode期望Objective-C和C ++的组合,即Objective -C ++)
您可以使用PIMPL idiom以对象定向方式实现上述内容。实施只是略有不同。简而言之,您将包装器函数(在“MyObject-C-Interface.h”中声明)放在一个类中,该类具有指向MyClass实例的(私有)void指针。
#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实例;
#import "MyObject-C-Interface.h"
@interface MyObject : NSObject
{
int someVar;
}
- (int) doSomethingWith:(void *) aParameter;
- (void) logMyMessage:(char *) aCStr;
@end
#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的相应方法。
#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
#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的实现替换为另一个特定于另一个平台的实现......但老实说,这是否是一个更好的实现更多的是品味和需求。
答案 1 :(得分:15)
您可以将代码编译为Objective-C ++ - 最简单的方法是将.cpp重命名为.mm。如果包含EAGLView.h
(由于C ++编译器不理解任何Objective-C特定关键字,你得到的错误很多),它将正确编译,你可以(在大多数情况下)混合Objective-但是你喜欢C和C ++。
答案 2 :(得分:12)
最简单的解决方案是简单地告诉Xcode将所有内容编译为Objective C ++。
为Compile Sources设置项目或目标设置,如同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 ++下编译。
答案 3 :(得分: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 ++函数(实际调用目标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
}
希望这有效!
答案 4 :(得分:8)
您需要将C ++文件视为Objective-C ++。您可以通过将foo.cpp重命名为foo.mm(.mm是obj-c ++扩展名)在xcode中执行此操作。然后正如其他人所说的标准obj-c消息语法一样。
答案 5 :(得分: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
答案 6 :(得分:1)
此外,您可以调用Objective-C运行时来调用该方法。
答案 7 :(得分:0)
@DawidDrozd的回答非常好。
我要加一点。最新版本的Clang编译器抱怨如果尝试使用他的代码,则需要“桥接强制转换”。
这似乎是合理的:使用蹦床会造成潜在的错误:由于对Objective-C类进行了引用计数,如果我们将其地址作为空*传递,则如果类被垃圾回收,则可能会有挂起指针的风险回调仍处于活动状态。
解决方案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);
}
答案 8 :(得分:-1)
您可以将C ++与Objectiv-C(Objective C ++)混合使用。在Objective C ++类中编写一个C ++方法,只需调用[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
并从C ++中调用它。
我没有在我自己之前尝试过,但请试一试,并与我们分享结果。