簡體   English   中英

從C ++成員函數調用Objective-C方法?

[英]Calling Objective-C method from C++ member function?

我有一個類( EAGLView ),它可以EAGLView調用C++類的成員函數。 現在,問題是我需要在C++類中調用一個objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; 這是我在C++語法中無法做到的。

我可以將這個Objective-C調用包裝到同一個Objective-C類中,該類首先稱為C ++類,但是我需要以某種方式從C++調用該方法,我無法弄清楚如何去做。

我試圖將一個指向EAGLView對象的指針指向C ++成員函數,並在我的C++類頭中包含“ EAGLView.h ”,但是我得到了3999個錯誤。

那么......我該怎么做? 一個例子很好..我只發現了這樣做的純C例子。

如果仔細操作,可以將C ++與Objective-C混合使用。 有一些警告,但一般來說,他們可以混合。 如果你想將它們分開,你可以設置一個標准的C包裝函數,它為Objective-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);
}

包裝器函數不需要與Objective-C類在同一個.m文件中,但它所存在的文件需要編譯為Objective-C代碼 聲明包裝函數的標頭需要包含在CPP和Objective-C代碼中。

(注意:如果Objective-C實現文件的擴展名為“.m”,它將不會在Xcode下鏈接。“。mm”擴展名告訴Xcode期望Objective-C和C ++的組合,即Objective-C ++。 )


您可以使用PIMPL慣用法以Object-Orientented方式實現上述內容。 實施只是略有不同。 簡而言之,您將包裝函數(在“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的實現替換為另一個特定於另一個平台......但老實說,這是否是一個更好的實現更多的是品味和需求的問題。

您可以將代碼編譯為Objective-C ++ - 最簡單的方法是將.cpp重命名為.mm。 如果你包含EAGLView.h那么它將被正確編譯(因為C ++編譯器不理解任何Objective-C特定關鍵字,你得到了很多錯誤),並且你可以(大部分)混合使用Objective-C和但是你喜歡C ++。

最簡單的解決方案是簡單地告訴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 ++下編譯。

步驟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
}

希望這個有效!

您需要將C ++文件視為Objective-C ++。 您可以通過將foo.cpp重命名為foo.mm(.mm是obj-c ++擴展名)在xcode中執行此操作。 然后正如其他人所說的標准obj-c消息語法將起作用。

此外,您可以調用Objective-C運行時來調用該方法。

有時將.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

@ DawidDrozd的答案非常好。

我想補充一點。 最近版本的Clang編譯器抱怨在嘗試使用他的代碼時需要“橋接演員”。

這似乎是合理的:使用蹦床會產生一個潛在的錯誤:因為Objective-C類是引用計數的,如果我們將它們的地址作為void *傳遞,如果在回調仍然是垃圾回收的情況下我們冒着掛起指針的風險活性。

解決方案1)Cocoa提供CFBridgingRetain和CFBridgingRelease宏函數,這些函數可能在Objective-C對象的引用計數中加1和減1。 因此,我們應該小心多次回調,以釋放與我們保留相同的次數。

// 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);
}

您可以將C ++與Objectiv-C(Objective C ++)混合使用。 在Objective C ++類中編寫一個C ++方法, [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];調用[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; 並從你的C ++中調用它。

我沒有在我自己之前嘗試過,但請試一試,並與我們分享結果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM