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++해야하는지 알 수 없습니다.

EAGLViewC ++ 멤버 함수에 대한 개체에 대한 포인터를 제공하고 클래스 헤더 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 관용구 . 구현은 약간만 다릅니다. 간단히 말해서, MyClass의 인스턴스에 대한 (개인) void 포인터가있는 클래스 안에 래퍼 함수 ( "MyObject-C-Interface.h"에서 선언 됨)를 배치합니다.

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

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

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 ++로 컴파일하도록 지시하는 것입니다.

Compile Sources As to 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 ++에서 컴파일되지 않습니다.


조금 궁금합니다. Objective-C ++로 모든 것을 컴파일 할 때 C 스타일 캐스트 사용에 대한 경고 및 / 또는 유효한 C 스타일 코드에 대한 기타 C ++ 관련 경고가 표시됩니까?
dreamlax 2009-06-30

물론 C ++로 프로그래밍하고 있으므로 적절하게 동작해야합니다.하지만 일반적으로 클래스를 생성하지 않더라도 C ++가 C보다 더 나은 C입니다. 그것은 당신이 어리석은 일을 할 수있게 해주지 않을 것이고, 당신이 좋은 일을 할 수있게 해줄 것입니다 (더 나은 상수, 열거 형 등). 여전히 동일하게 캐스팅 할 수 있습니다 (예 : (CFFloat) x).
Peter N Lewis

10

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
}

이것이 효과가 있기를 바랍니다!


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

또한 Objective-C 런타임을 호출하여 메서드를 호출 할 수 있습니다.


1

위의 @DawidDrozd의 대답은 훌륭합니다.

나는 1 점을 더할 것이다. 최신 버전의 Clang 컴파일러는 자신의 코드를 사용하려고 할 때 "브리징 캐스트"가 필요하다고 불평합니다.

이것은 합리적으로 보입니다. 트램폴린을 사용하면 잠재적 인 버그가 생성됩니다. Objective-C 클래스는 참조 카운트이므로 해당 주소를 void *로 전달하면 콜백이 계속되는 동안 클래스가 가비지 수집되면 교수형 포인터가 발생할 위험이 있습니다. 유효한.

솔루션 1) Cocoa는 Objective-C 객체의 참조 카운트에서 하나를 더하고 빼는 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이 추가적인 안전을 제공하는지에 대해 다소 의심 스럽다는 것을 인정해야합니다. 한 가지 요점은의 복사본을 self클로저에 전달하고 있다는 것입니다. 다른 요점은 이것이 자동 참조 계산과 상호 작용하는 방법과 컴파일러가 무슨 일이 일어나고 있는지 파악할 수 있는지 여부가 명확하지 않다는 것입니다. 실제로 저는 간단한 단일 모듈 장난감 예제에서 두 버전 중 하나가 실패한 상황을 만들지 못했습니다.
QuesterZen

-1

Objectiv-C (Objective C ++)와 C ++를 혼합 할 수 있습니다. Objective C ++ 클래스에서 단순히 [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];C ++에서 호출 하고 호출 하는 C ++ 메서드를 작성합니다 .

나는 내 자신보다 먼저 시도한 적이 없지만 한 번 시도하고 결과를 우리와 공유하십시오.


2
하지만 문제는 어떻게 호출 할 수 있다는 것입니다. C ++ 클래스에 "EAGLview.h"를 포함하면 수천 개의 오류가 발생하기 때문입니다.
juvenis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.