Calling Objective-C method from C++ member function?
Asked Answered
A

9

121

I have a class (EAGLView) which calls a member function 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++ member function 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.

Acquittal answered 29/6, 2009 at 23:5 Comment(0)
B
219

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.

Burkley answered 30/6, 2009 at 3:28 Comment(22)
Hi, I tried it but I get linkage error saying symbol(s) not found. i.e. it can't find the MyObjectDoSomethingWith. any ideas?Tingley
You might need to add extern "C" before the int MyObjectDoSomethingWithBurkley
tried that already, doesn't work and it makes sense because extern "C" is used when we want to call C++ function from C, in this case we are calling a C function from C++, no?Tingley
Why is MyObject.h #import "MyObject-C-Interface.h"?Kreit
Also, how does objectiveCObject get instantiated in MyCPPClass.cpp?Kreit
Furthermore, isn't self a keyword in Objective-C? Why would you use it as a parameter name in MyObject.mm?Kreit
@RaffiKhatchadourian: Because it's not a keyword, it's an implicit parameter in Objective-C methods.Burkley
@Burkley It is a keyword that refers to the implicit parameter in Objective-C methods. Are you referring to the implicit parameter here or are you referring to an explicit parameter?Kreit
@RaffiKhatchadourian: It's not a keyword, you need to learn what a keyword is. Things like if, const, and long are keywords. They can't be used as an identifier (i.e. a function or variable) because they are keywords. self is not a keyword, it is an argument that is declared automatically for each Objective-C method, and there is another argument called _cmd which is also declared for each method. Check out the documentation for NSInvocation or look at the runtime library source code. If self were a keyword, you would not be able to do self = [super init].Burkley
@RaffiKhatchadourian: Since the function MyObjectDoSomethingWith is NOT an Objective-C method, there is no implicitly declared self argument, so I can declare a variable of that name myself.Burkley
@Burkley It may not be an Objective-C method but it is in an Objective-C++ file, which makes using self in this context confusing.Kreit
Question is about calling an Objective-C method from C++. The answer provides a C interface. The question was not about how to call an Objective-C method from C.Kreit
@RaffiKhatchadourian: I created a C interface because C interfaces are compatible with both Objective-C and C++ (and can be dynamically linked with a plethora of other languages). Your inability to understand the concepts of this answer when evidently so many others can, perhaps should serve as an indication that your level of understanding of C, C++ and/or Objective-C is not as good as you think.Burkley
@ishaq How did you solve the linker error issue? I am getting the same error.Cuddy
Hi @dreamlax, I'm getting linker errors with this while trying to work through it. Can you please elaborate on what MyClass is in the self = [[MyClass alloc] init]; line in the init method of MyObject.mm. It's not clear what "MyClass" is as it's not defined. Did you mean to put "MyObject" instead? Or perhaps "MyClassImpl"? Appreciate any clarification you can provide to help alleviate my confusion! :)Sabinesabino
Hi @Burkley how to call doSomethingWith( void * aParameter ); function how to pass aParameter. is it a Objective C class pointer? can you please explain this also in your answer.Squamous
I think that MyCPPClass::init() needs to check if(_impl == NULL) before creating a new MyClassImpl object. Otherwise, memory leak, inconsistent behaviour, etc.Source
Hi @dreamlax, I am getting an error in this line: return [(id)self notifyVolumeToUI]; (notifyVolumeToUI is my objective c method that do what its name says). The error is that I need a bridged cast (cast of C pointer type 'void*' to objetive-c pointer type 'id' requires a bridged cast). Sorry if it's like a newbie error, I am not so familiar with iOS development. Thanks.Instead
@leo2_uru: Just use (__bridge id) self instead of (id) self. This just means that you want to use self as an Objective-C object but there is no explicit change of ownership.Burkley
Awesome @dreamlax, that is compiling now but I don't know to call that "someMethod". Which should be the parameters that should be added to :int MyCPPClass::someMethod (void *objectiveCObject, void *aParameter)???Instead
@Burkley in MyCPPClass::someMethod method in MyCPPClass.cpp how i am supposed to pass objectivecobject as void * in a cpp file ? in singleton ios class when i don't need to cal self the solution work perfectly though . thanksSchwarzwald
i am getting Thread 4: EXC_BAD_ACCESS (code=EXC_I386_GPFLT) , has anyone tried this ?Hebetate
S
16

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.

Silly answered 29/6, 2009 at 23:15 Comment(2)
Are you getting all of those compiler errors in this C++ file, or are they in some other C++ file that happens to include this C++ header?Silly
It seems I cannot include the EAGLView.h in the C++ header file because then it for some reason expects that the Objective C code is C++, and does not understand @ + other symbolsAcquittal
B
14

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++.

Bengt answered 30/6, 2009 at 3:28 Comment(2)
I'm just a little curious, when you compile everything as Objective-C++ do you get warnings about using C-style casts and/or other C++-specific warnings about valid C-style code?Burkley
Sure, you are programming in C++ so you'll be expected to behave appropriately - but as a general rule, C++ is a better C than C is, even if you never create a class. It wont let you do stupid things, and it lets you do nice things (like better constants and enums and such). You can still cast just the same (eg (CFFloat)x).Bengt
G
12

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 <Foundation/Foundation.h>
#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 <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__) */

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

Gunpoint answered 10/1, 2015 at 8:7 Comment(0)
T
9

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.

Tarsometatarsus answered 29/6, 2009 at 23:26 Comment(0)
S
1

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

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 = "<group>"; };

It is equivalent to rename .cpp to .mm

Seavir answered 10/2, 2012 at 11:34 Comment(0)
G
1

Also, you can call into Objective-C runtime to call the method.

Geography answered 6/8, 2013 at 6:45 Comment(0)
L
1

@DawidDrozd's answer above is excellent.

I would add one point. Recent versions of the Clang compiler complain about requiring a "bridging cast" if attempting to use his code.

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.

Solution 1) Cocoa provides CFBridgingRetain and CFBridgingRelease macro functions which presumably add and subtract one from the reference count of the Objective-C object. We should therefore be careful with multiple callbacks, to release the same number of times as we retain.

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

Solution 2) The alternative is to use the equivalent of a weak reference (ie. no change to the retain count), without any additional safety.

The Objective-C language provides the __bridge cast qualifier to do this (CFBridgingRetain and CFBridgingRelease seem to be thin Cocoa wrappers over the Objective-C language constructs __bridge_retained and release respectively, but Cocoa does not appear to have an equivalent for __bridge).

The required changes are:

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);
}
Lefthanded answered 26/7, 2018 at 1:45 Comment(4)
I have to admit that I am somewhat suspicious about whether Solution 1 provides any additional safety despite all of the retain/release theatrics. One point is that we are passing a copy of self into the closure, which could become outdated. The other point is that its not clear how this interacts with automatic reference counting and whether the compiler can figure out what is going on. In practice I didn't manage to create a situation where either version failed in a simple one-module toy example.Lefthanded
Does this not demonstrate calling c++ from obj-c, or is this a partial amend to @DawidDrozd ? I got confused.Radiopaque
@Kinchog Maybe the original post by DawidDrozd has been deleted. The idea is that c++ will be handed a std::function<void>void that it CAN call by Objective-C. This function will unwrap the Objective-C object and call the correct method. The problem is that this will likely mess up the Objective-C memory management. So the question this post answers is how to avoid this problem.Lefthanded
thanks - I found out that there are C++ headers for The functionality I wanted (dispatch) so I was able to avoid jumping between the two anyhow! Thanks anyway.Radiopaque
P
-1

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.

Pyramidon answered 29/6, 2009 at 23:8 Comment(1)
But the problem is that how can I call it... because if I include "EAGLview.h" in my C++ class I get thousands of errors.Acquittal

© 2022 - 2024 — McMap. All rights reserved.