Gọi phương thức Objective-C từ hàm thành viên C ++?


111

Tôi có một lớp ( EAGLView) gọi một hàm thành viên của một C++lớp mà không gặp vấn đề gì. Bây giờ, vấn đề là tôi cần gọi trong C++lớp objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];đó a mà tôi không thể thực hiện theo C++cú pháp.

Tôi có thể gói Objective-Clời gọi này vào cùng một Objective-Clớp mà ngay từ đầu được gọi là lớp C ++, nhưng sau đó tôi cần phải gọi phương thức đó từ bằng cách nào đó C++và tôi không thể tìm ra cách thực hiện.

Tôi đã cố gắng cung cấp một con trỏ để EAGLViewphản đối hàm thành viên C ++ và bao gồm " EAGLView.h" trong C++tiêu đề lớp của mình nhưng tôi đã gặp lỗi 3999 ..

Vậy .. tôi nên làm thế nào đây? Một ví dụ sẽ rất hay .. Tôi chỉ tìm thấy Cnhững ví dụ thuần túy về việc làm này.

Câu trả lời:


204

Bạn có thể trộn C ++ với Objective-C nếu làm cẩn thận. Có một vài lưu ý nhưng nói chung chúng có thể bị trộn lẫn. Nếu bạn muốn giữ chúng riêng biệt, bạn có thể thiết lập một hàm trình bao bọc C tiêu chuẩn để cung cấp cho đối tượng Objective-C một giao diện kiểu C có thể sử dụng được từ mã không phải Objective-C (chọn tên tốt hơn cho tệp của bạn, tôi đã chọn những tên này cho độ dài):

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

Hàm trình bao bọc không cần phải ở cùng .mtệp với lớp Objective-C, nhưng tệp mà nó tồn tại cần được biên dịch dưới dạng mã Objective-C . Tiêu đề khai báo hàm trình bao bọc cần được bao gồm trong cả mã CPP và Objective-C.

(LƯU Ý: nếu tệp triển khai Objective-C có phần mở rộng là ".m", nó sẽ không liên kết theo Xcode. Phần mở rộng ".mm" cho Xcode biết mong đợi sự kết hợp giữa Objective-C và C ++, tức là Objective-C ++). )


Bạn có thể thực hiện những điều trên theo cách Hướng đối tượng bằng cách sử dụng thành ngữ PIMPL . Cách thực hiện chỉ khác một chút. Tóm lại, bạn đặt các hàm wrapper (được khai báo trong "MyObject-C-Interface.h") bên trong một lớp có con trỏ void (riêng tư) tới một phiên bản của 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

Lưu ý rằng các phương thức trình bao bọc không còn yêu cầu con trỏ void đến một thể hiện của MyClass; nó hiện là thành viên riêng của MyClassImpl. Phương thức init được sử dụng để khởi tạo một cá thể 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

Lưu ý rằng MyClass được khởi tạo bằng một lệnh gọi đến MyClassImpl :: init. Bạn có thể khởi tạo MyClass trong phương thức khởi tạo của MyClassImpl, nhưng đó thường không phải là một ý kiến ​​hay. Cá thể MyClass bị hủy từ trình hủy của MyClassImpl. Như với việc triển khai kiểu C, các phương thức wrapper chỉ đơn giản là trì hoãn các phương thức tương ứng của 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." );
    }
}

Giờ đây, bạn truy cập các cuộc gọi tới MyClass thông qua triển khai riêng của MyClassImpl. Cách tiếp cận này có thể có lợi nếu bạn đang phát triển một ứng dụng di động; bạn có thể chỉ cần hoán đổi việc triển khai MyClass với một ứng dụng cụ thể cho nền tảng khác ... nhưng thành thật mà nói, liệu đây có phải là cách triển khai tốt hơn hay không là vấn đề sở thích và nhu cầu.


6
Xin chào, tôi đã thử nhưng gặp lỗi liên kết cho biết không tìm thấy (các) biểu tượng. tức là nó không thể tìm thấy MyObjectDoSomethingWith. bất kỳ ý tưởng?

3
Bạn có thể cần phải thêm extern "C"trướcint MyObjectDoSomethingWith
dreamlax

2
đã thử nhưng không hoạt động và nó có ý nghĩa vì extern "C" được sử dụng khi chúng ta muốn gọi hàm C ++ từ C, trong trường hợp này chúng ta đang gọi hàm C từ C ++, phải không?

6
Ngoài ra, làm cách nào để mục tiêu được khởi tạo trong MyCPPClass.cpp?
Raffi Khatchadourian 23/12/12

2
@Dreamlax tuyệt vời, hiện đang được biên dịch nhưng tôi không biết gọi đó là "someMethod". Tham số nào nên được thêm vào: int MyCPPClass :: someMethod (void * objCObject, void * aParameter) ???
the_moon

15

Bạn có thể biên dịch mã của mình dưới dạng Objective-C ++ - cách đơn giản nhất là đổi tên .cpp của bạn thành .mm. Sau đó, nó sẽ biên dịch đúng cách nếu bạn bao gồm EAGLView.h(bạn gặp rất nhiều lỗi vì trình biên dịch C ++ không hiểu bất kỳ từ khóa cụ thể Objective-C nào) và bạn có thể (đối với hầu hết các phần) trộn Objective-C và C ++, tuy nhiên bạn giống.


Bạn đang nhận được tất cả những lỗi biên dịch trong này ++ tập tin C, hoặc là họ ở một số khác C ++ tập tin điều đó xảy ra để bao gồm C ++ tiêu đề này?
Jesse Beder

Dường như tôi không thể bao gồm các EAGLView.h trong C ++ file header vì sau đó nó đối với một số lý do hy vọng rằng mã C Mục tiêu là C ++, và không hiểu @ + biểu tượng khác
juvenis

12

Giải pháp đơn giản nhất là chỉ cần yêu cầu Xcode biên dịch mọi thứ dưới dạng Objective C ++.

Đặt dự án hoặc cài đặt mục tiêu của bạn cho Compile Sources As to Objective C ++ và biên dịch lại.

Sau đó, bạn có thể sử dụng C ++ hoặc Objective C ở mọi nơi, ví dụ:

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

Điều này có ảnh hưởng tương tự như đổi tên tất cả các tệp nguồn của bạn từ .cpp hoặc .m thành .mm.

Có hai nhược điểm nhỏ cho điều này: clang không thể phân tích mã nguồn C ++; một số mã C tương đối kỳ lạ không biên dịch theo C ++.


Tôi chỉ hơi tò mò, khi bạn biên dịch mọi thứ dưới dạng Objective-C ++, bạn có nhận được cảnh báo về việc sử dụng phôi kiểu C và / hoặc C ++ khác - cảnh báo cụ thể về mã kiểu C hợp lệ không?
dreamlax

Chắc chắn, bạn đang lập trình bằng C ++ nên bạn sẽ phải cư xử phù hợp - nhưng theo quy tắc chung, C ++ là C tốt hơn C, ngay cả khi bạn không bao giờ tạo một lớp. Nó sẽ không cho phép bạn làm những điều ngu ngốc, và nó cho phép bạn làm những điều tốt đẹp (như hằng số tốt hơn và enum, v.v.). Bạn vẫn có thể ép kiểu giống nhau (ví dụ: (CFFloat) x).
Peter N Lewis

10

Bước 1

Tạo tệp c mục tiêu (tệp .m) và tệp tiêu đề tương ứng.

// Tệp tiêu đề (Chúng tôi gọi nó là "ObjCFunc.h")

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

// Tệp Objective C tương ứng (Chúng tôi gọi nó là "ObjCFunc.m")

#import <Foundation/Foundation.h>
#include "ObjCFunc.h"
@implementation myClass
//Your objective c code here....
-(void)hello:(int)num1
{
NSLog(@"Hello!!!!!!");
}
@end

Bước 2

Bây giờ chúng ta sẽ thực hiện một hàm c ++ để gọi hàm c mục tiêu mà chúng ta vừa tạo! Vì vậy, chúng tôi sẽ xác định tệp .mm và tệp tiêu đề tương ứng của nó (tệp ". Mm" sẽ được sử dụng ở đây vì chúng tôi sẽ có thể sử dụng cả mã Objective C và C ++ trong tệp)

// Tệp tiêu đề (Chúng tôi gọi nó là "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__) */

// Tệp Objective C ++ tương ứng (Chúng tôi gọi nó là "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
}

Bước 3

Gọi hàm c ++ (thực sự gọi phương thức c mục tiêu)

#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__

//Cuộc gọi cuôi cung

#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
}

Hy vọng điều này hiệu quả!


9

Bạn cần làm cho tệp C ++ của mình được coi là Objective-C ++. Bạn có thể thực hiện việc này trong xcode bằng cách đổi tên foo.cpp thành foo.mm (.mm là phần mở rộng obj-c ++). Sau đó, như những người khác đã nói cú pháp nhắn tin obj-c tiêu chuẩn sẽ hoạt động.


1

Đôi khi đổi tên .cpp thành .mm không phải là ý kiến ​​hay, đặc biệt khi dự án ở dạng crossplatform. Trong trường hợp này đối với dự án xcode, tôi mở tệp dự án xcode thông qua TextEdit, tìm thấy chuỗi chứa tệp quan tâm, nó sẽ giống như:

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

và sau đó thay đổi loại tệp từ sourcecode.cpp.cpp thành sourcecode.cpp.objcpp

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

Nó tương đương với đổi tên .cpp thành .mm


1

Ngoài ra, bạn có thể gọi vào Objective-C runtime để gọi phương thức.


1

Câu trả lời của @ DawidDrozd ở trên là tuyệt vời.

Tôi sẽ thêm một điểm. Các phiên bản gần đây của trình biên dịch Clang phàn nàn về việc yêu cầu một "dàn diễn viên bắc cầu" nếu cố gắng sử dụng mã của anh ta.

Điều này có vẻ hợp lý: việc sử dụng tấm bạt lò xo sẽ ​​tạo ra một lỗi tiềm ẩn: vì các lớp Objective-C được tính là tham chiếu, nếu chúng ta chuyển địa chỉ của chúng xung quanh dưới dạng khoảng trống *, chúng ta có nguy cơ gặp phải con trỏ treo nếu lớp được thu thập rác trong khi lệnh gọi lại vẫn còn đang hoạt động.

Giải pháp 1) Cacao cung cấp các hàm macro CFBridgingRetain và CFBridgingRelease, các hàm này có lẽ sẽ cộng và trừ một hàm từ số tham chiếu của đối tượng Objective-C. Do đó, chúng tôi nên cẩn thận với nhiều lần gọi lại, để giải phóng cùng một số lần chúng tôi giữ lại.

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

Giải pháp 2) Giải pháp thay thế là sử dụng tham chiếu tương đương với tham chiếu yếu (tức là không thay đổi số lượng giữ lại), mà không có bất kỳ an toàn bổ sung nào.

Ngôn ngữ Objective-C cung cấp bộ định tính truyền __bridge để thực hiện việc này (CFBridgingRetain và CFBridgingRelease dường như là các lớp bọc Cacao mỏng trên các cấu trúc và bản phát hành của ngôn ngữ Objective-C tương ứng, nhưng Cocoa dường như không có hàm tương đương cho __bridge).

Các thay đổi bắt buộc là:

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

Tôi phải thừa nhận rằng tôi hơi nghi ngờ về việc liệu Giải pháp 1 có cung cấp bất kỳ sự an toàn bổ sung nào mặc dù tất cả các sân khấu giữ lại / phát hành. Có một điểm là chúng tôi đang chuyển một bản sao của selfvào phần đóng, có thể trở nên lỗi thời. Điểm khác là không rõ điều này tương tác như thế nào với việc đếm tham chiếu tự động và liệu trình biên dịch có thể tìm ra điều gì đang xảy ra hay không. Trong thực tế, tôi đã không quản lý để tạo ra tình huống mà một trong hai phiên bản bị lỗi trong ví dụ đồ chơi một mô-đun đơn giản.
QuesterZen,

-1

Bạn có thể trộn C ++ vào với Objectiv-C (Objective C ++). Viết một phương thức C ++ trong lớp Objective C ++ của bạn mà chỉ cần gọi [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];và gọi nó từ C ++ của bạn.

Tôi chưa thử nó trước khi tự mình thử, nhưng hãy thử và chia sẻ kết quả với chúng tôi.


2
Nhưng vấn đề là tôi có thể gọi nó như thế nào ... bởi vì nếu tôi bao gồm "EAGLview.h" trong lớp C ++ của mình, tôi sẽ gặp hàng nghìn lỗi.
juvenis
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.