C ++メンバー関数からObjective-Cメソッドを呼び出す?


111

問題なくクラスのEAGLViewメンバー関数を呼び出すクラス()がありますC++。さて、問題は、構文ではできないC++クラスa を呼び出す必要があることです。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これを行う純粋な例を見つけただけです。

回答:


204

注意深く行う場合は、C ++とObjective-Cを混在させることができます。いくつかの警告がありますが、一般的に言えば、それらは混合することができます。それらを別々に保持したい場合は、Objective-Cオブジェクトに非Objective-Cコードからの使用可能な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」拡張子は、Objective-CとC ++の組み合わせ、つまりObjective-C ++を期待するようにXcodeに指示します。 )


PIMPLイディオムを使用することにより、上記をオブジェクト指向の方法で実装できます。実装はわずかに異なります。つまり、ラッパー関数( "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

ラッパーメソッドが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

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

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
また、objectiveCObjectはMyCPPClass.cppでどのようにインスタンス化されますか?
Raffi Khatchadourian 2012年

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 ++ファイルにありますか?
Jesse Beder、

Objective CのコードはC ++で、理解していないことを私はC ++ヘッダファイルにEAGLView.hを含めることはできませんいくつかの理由を期待ため、それので思える@ +他のシンボル
juvenis

12

最も簡単な解決策は、XcodeにすべてをObjective C ++としてコンパイルするように指示することです。

目的のC ++にソースをコンパイルするプロジェクトまたはターゲット設定を設定し、再コンパイルします。

次に、C ++またはObjective Cをどこでも使用できます。次に例を示します。

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

これは、すべてのソースファイルの名前を.cppまたは.mから.mmに変更するのと同じ効果があります。

これには2つの小さな欠点があります。clangはC ++ソースコードを分析できません。比較的奇妙なCコードの中には、C ++でコンパイルできないものがあります。


Objective-C ++としてすべてをコンパイルすると、Cスタイルのキャストの使用に関する警告や、有効なCスタイルのコードに関するその他のC ++固有の警告が表示されますか?
dreamlax 2009年

もちろん、あなたはC ++でプログラミングしているので、適切に動作することが期待されます。ただし、原則として、クラスを作成したことがなくても、C ++はCよりも優れたCです。それはあなたが愚かなことをすることを許さず、それはあなたが良いことをすることを可能にします(より良い定数や列挙型などのような)。それでも同じようにキャストできます(例:(CFFloat)x)。
ピーターNルイス

10

ステップ1

目的のcファイル(.mファイル)とそれに対応するヘッダーファイルを作成します。

//ヘッダーファイル(「ObjCFunc.h」と呼びます)

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

//対応する目的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ファイルとそれに対応するヘッダーファイルを定義します(ファイルでObjective CとC ++の両方のコーディングを使用できるため、ここでは「.mm」ファイルを使用します)。

//ヘッダーファイル(「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__) */

//対応する目的の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
}

これがうまくいくことを願っています!


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.cppsourcecode.cpp.objcpp

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

これは、.cppを.mmに名前変更することと同じです。



1

上記の@DawidDrozdの答えは素晴らしいです。

1点追加します。Clangコンパイラの最近のバージョンでは、彼のコードを使用しようとすると「ブリッジキャスト」が必要になるという不満があります。

これは妥当なようです:トランポリンを使用すると潜在的なバグが発生します:Objective-Cクラスは参照カウントされるため、アドレスをvoid *として渡すと、コールバックがまだ行われている間にクラスがガベージコレクションされている場合、ポインターがハングする危険がありますアクティブ。

解決策1)Cocoaは、おそらくObjective-Cオブジェクトの参照カウントから1を加算および減算するCFBridgingRetainおよびCFBridgingReleaseマクロ関数を提供します。したがって、保持するのと同じ回数を解放するように、複数のコールバックに注意する必要があります。

// 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ラッパーのようですが、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が追加の安全性を提供するかどうかについて、私は多少疑わしいことを認めなければなりません。1つのポイントは、のコピーをselfクロージャーに渡しているため、古くなる可能性があるということです。もう1つのポイントは、これが自動参照カウントとどのように相互作用するか、およびコンパイラーが何が行われているのかを理解できるかどうかが明確でないことです。実際には、単純な1モジュールのおもちゃの例でどちらかのバージョンが失敗する状況を作り出すことができませんでした。
QuesterZen 2018

-1

C ++とObjectiv-C(Objective C ++)を混在させることができます。C ++ [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];から呼び出すだけのObjective C ++クラスでC ++メソッドを記述します。

私は自分の前で試したことはありませんが、試してみて、結果を私たちと共有してください。


2
しかし、問題はそれをどのように呼び出すことができるかです... C ++クラスに "EAGLview.h"を含めると、何千ものエラーが発生するためです。
ジュベニス2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.