可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a class (EAGLView) which calls a method of a C++ class without problems. Now, the problem is that I need to call in that C++ class a objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; which I cannot do in C++ syntax.
I could wrap this Objective-C call to the same Objective-C class which in the first place called the C++ class, but then I need to somehow call that method from C++, and I cannot figure out how to do it.
I tried to give a pointer to EAGLView object to the C++ method and include the "EAGLView.h" in my C++ class header but I got 3999 errors..
So.. how should I do this? An example would be nice.. I only found pure C examples of doing this.
回答1:
You can mix C++ with Objective-C if you do it carefully. There are a few caveats but generally speaking they can be mixed. If you want to keep them separate, you can set up a standard C wrapper function that gives the Objective-C object a usable C-style interface from non-Objective-C code (pick better names for your files, I have picked these names for verbosity):
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); }
The wrapper function does not need to be in the same .m file as the Objective-C class, but the file that it does exist in needs to be compiled as Objective-C code. The header that declares the wrapper function needs to be included in both CPP and Objective-C code.
(NOTE: if the Objective-C implementation file is given the extension ".m" it will not link under Xcode. The ".mm" extension tells Xcode to expect a combination of Objective-C and C++, i.e., Objective-C++.)
You can implement the above in an Object-Orientented manner by using the PIMPL idiom. The implementation is only slightly different. In short, you place the wrapper functions (declared in "MyObject-C-Interface.h") inside a class with a (private) void pointer to an instance of 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
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
Notice that MyClass is instantiated with a call to MyClassImpl::init. You could instantiate MyClass in MyClassImpl's constructor, but that generally isn't a good idea. The MyClass instance is destructed from MyClassImpl's destructor. As with the C-style implementation, the wrapper methods simply defer to the respective methods of 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." ); } }
You now access calls to MyClass through a private implementation of MyClassImpl. This approach can be advantageous if you were developing a portable application; you could simply swap out the implementation of MyClass with one specific to the other platform ... but honestly, whether this is a better implementation is more a matter of taste and needs.
回答2:
You can compile your code as Objective-C++ - the simplest way is to rename your .cpp as .mm. It will then compile properly if you include EAGLView.h (you were getting so many errors because the C++ compiler didn't understand any of the Objective-C specific keywords), and you can (for the most part) mix Objective-C and C++ however you like.
回答3:
The easiest solution is to simply tell Xcode to compile everything as Objective C++.
Set your project or target settings for Compile Sources As to Objective C++ and recompile.
Then you can use C++ or Objective C everywhere, for example:
void CPPObject::Function( ObjectiveCObject* context, NSView* view ) { [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)view.layer] }
This has the same affect as renaming all your source files from .cpp or .m to .mm.
There are two minor downsides to this: clang cannot analyse C++ source code; some relatively weird C code does not compile under C++.
回答4:
You need to make your C++ file be treated as Objective-C++. You can do this in xcode by renaming foo.cpp to foo.mm (.mm is the obj-c++ extension). Then as others have said standard obj-c messaging syntax will work.
回答5:
Step 1
Create a objective c file(.m file) and it's corresponding header file.
// Header file (We call it "ObjCFunc.h")
#ifndef test2_ObjCFunc_h #define test2_ObjCFunc_h @interface myClass :NSObject -(void)hello:(int)num1; @end #endif
// Corresponding Objective C file(We call it "ObjCFunc.m")
#import #include "ObjCFunc.h" @implementation myClass //Your objective c code here.... -(void)hello:(int)num1 { NSLog(@"Hello!!!!!!"); } @end
Step 2
Now we will implement a c++ function to call the objective c function that we just created! So for that we will define a .mm file and its corresponding header file(".mm" file is to be used here because we will be able to use both Objective C and C++ coding in the file)
//Header file(We call it "ObjCCall.h")
#ifndef __test2__ObjCCall__ #define __test2__ObjCCall__ #include 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__) */
//Corresponding Objective C++ file(We call it "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 }
Step 3
Calling the c++ function(which actually calls the objective c method)
#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__
//Final call
#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!
回答6:
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 = ""; };
and then change file type from sourcecode.cpp.cpp to sourcecode.cpp.objcpp
/* OnlineManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = **sourcecode.cpp.objcpp**; path = OnlineManager.cpp; sourceTree = ""; };
It is equivalent to rename .cpp to .mm
回答7:
Also, you can call into Objective-C runtime to call the method.
回答8:
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++.
I haven't tried it before my self, but give it a shot, and share the results with us.