Wywołanie metody Objective-C z funkcji członka C++?

Mam klasę (EAGLView), która bez problemów wywołuje funkcję członka klasy C++. Problem w tym, że muszę zadzwonić do C++ klasy A objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; czego nie mogę zrobić w składni C++.

Mógłbym zawinąć to Objective-C wywołanie do tej samej klasy Objective-C, która w pierwszej kolejności nazywała klasę C++, ale potem muszę jakoś wywołać tę metodę z C++, i nie mogę wymyślić, jak to zrobić.

Próbowałem dać wskaźnik do EAGLView obiekt do członka C++ funkcja i zawierać "EAGLView.h " w moim C++ nagłówku klasy, ale mam 3999 błędów..

Więc.. jak mam to zrobić? Dobry przykład.. Znalazłem tylko czyste C przykłady tego.

Author: Drew Dormann, 2009-06-30

9 answers

Możesz mieszać C++ z Objective-C, jeśli zrobisz to ostrożnie. Istnieje kilka zastrzeżeń, ale ogólnie można je mieszać. Jeśli chcesz zachować je oddzielnie, możesz skonfigurować standardową funkcję wrappera C, która daje obiektowi Objective-C użyteczny interfejs w stylu C z kodu innego niż Objective-C (wybierz lepsze nazwy dla swoich plików, wybrałem te nazwy dla 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);
}

Funkcja wrapper nie potrzebuje być w tym samym pliku .m co Klasa Objective-C, ale plik, w którym istnieje musi być skompilowany jako kod Objective-C. Nagłówek deklarujący funkcję wrapper musi być zawarty zarówno w CPP, jak i Objective-C kod.

(UWAGA: Jeśli plik implementacji Objective-C ma rozszerzenie ".m " nie będzie link pod Xcode. Rozszerzenie". mm" mówi Xcode, aby spodziewał się kombinacji Objective-C i C++, czyli Objective-C++.)


Możesz zaimplementować powyższe w sposób zorientowany obiektowo za pomocą idiomu PIMPL. Realizacja jest tylko nieco inna. Krótko mówiąc, umieszczasz funkcje wrappera (zadeklarowane w " MyObject-C-Interface.h") wewnątrz klasy z a (prywatny) wskaźnik void do instancji 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

Zauważ, że metody wrappera nie wymagają już wskaźnika void do instancji Myclassimpl; jest ona teraz prywatnym członkiem MyClassImpl. Metoda init jest używana do tworzenia instancji instancji 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

Zauważ, że MyClass jest tworzony z wywołaniem do MyClassImpl:: init. Możesz utworzyć instancję MyClass w konstruktorze MyClassImpl, ale ogólnie nie jest to dobry pomysł. Instancja MyClass jest destrukowana z destruktora MyClassImpl. Podobnie jak w przypadku implementacji w stylu C, metody wrappera po prostu ulegają odpowiednim metodom 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." );
    }
}

Uzyskujesz teraz dostęp do połączeń Myclassimpl poprzez prywatną implementację MyClassImpl. Takie podejście może być korzystne, jeśli tworzysz przenośną aplikację; możesz po prostu zamienić implementację MyClass na inną specyficzną dla drugiej platformy ... ale szczerze mówiąc, to, czy jest to lepsze wdrożenie, jest bardziej kwestią gustu i potrzeb.

 185
Author: dreamlax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-17 13:43:40

Możesz skompilować swój kod jako Objective-C++ - najprostszym sposobem jest zmiana nazwy twojego .cpp jako. mm. skompiluje się poprawnie, jeśli dodasz EAGLView.h (dostajesz tyle błędów, ponieważ kompilator C++ nie zrozumiał żadnego ze słów kluczowych specyficznych dla Objective-C), i możesz (w większości) mieszać Objective-C i C++, jak tylko chcesz.

 15
Author: Jesse Beder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-29 23:15:01

Najprostszym rozwiązaniem jest po prostu powiedzieć Xcode, aby skompilował wszystko jako Objective C++.

Ustaw swój projekt lub ustawienia docelowe dla źródeł kompilacji jako Objective C++ i przekompiluj.

Wtedy możesz używać C++ lub Objective C wszędzie, na przykład:

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

Ma to taki sam wpływ jak zmiana nazwy wszystkich plików źródłowych z .cpp lub .m do. mm.

Są dwa Pomniejsze minusy: clang nie może analizować kodu źródłowego C++; jakiś stosunkowo dziwny kod C nie skompilować Pod C++.

 11
Author: Peter N Lewis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-30 03:28:27

Musisz sprawić, aby Twój plik C++ był traktowany jako Objective-C++. Możesz to zrobić w xcode zmieniając nazwę foo.cpp do foo.mm (. mm jest rozszerzeniem obj-c++). Wtedy, jak powiedzieli inni, będzie działać standardowa składnia komunikatów obj-C.

 8
Author: olliej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-29 23:26:24

Krok 1

Utwórz plik objective c (.plik m) i jest to odpowiadający mu plik nagłówkowy.

/ / plik nagłówkowy (nazywamy go " ObjCFunc.h")

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

// odpowiedni plik Objective C (nazywamy go " ObjCFunc.m")

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

Krok 2

Teraz zaimplementujemy funkcję c++, aby wywołać funkcję objective c, którą właśnie stworzyliśmy! W tym celu zdefiniujemy plik. mm i odpowiadający mu plik nagłówkowy (plik". mm " ma być tutaj użyty, ponieważ będzie mógł używać zarówno Objective C, jak i C++ kodowania w pliku)

/ / plik nagłówkowy(nazywamy go " 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__) */

//odpowiedni plik Objective C++ (nazywamy go "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
}

Krok 3

Wywołanie funkcji c++ (która w rzeczywistości wywołuje metodę objective 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__

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

Mam nadzieję, że to zadziała!

 8
Author: Mishal Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-10 08:07:54

Czasami zmiana nazwy .cpp do. mm nie jest dobrym pomysłem, zwłaszcza gdy projekt jest wieloplatformowy. W tym przypadku dla projektu xcode otwieram plik projektu Xcode przez TextEdit, znalazłem ciąg, który interesuje zawartość pliku, powinno być tak:

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

A następnie zmień typ pliku z sourcecode.cpp.kod źródłowy cpp do .cpp.objcpp

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

Jest odpowiednikiem rename .cpp do. mm

 1
Author: Yevgeniy Logachev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-10 11:34:48

Można również wywołać do runtime Objective-C, aby wywołać metodę.

 1
Author: Maxthon Chan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-06 06:45:53

@ DawidDrozd odpowiedź powyżej jest doskonała.

Dodam jeszcze jeden punkt. Najnowsze wersje kompilatora Clang narzekają na konieczność "pomostowego odlewu", jeśli próbuje użyć jego kodu.

Wydaje się to rozsądne: użycie trampoliny tworzy potencjalny błąd: ponieważ Klasy Objective-C są liczone jako reference, jeśli podamy ich adres jako void *, ryzykujemy zawieszeniem wskaźnika, jeśli klasa jest zbierana śmieci, podczas gdy callback jest nadal aktywny.

Rozwiązanie 1) Cocoa dostarcza makra cfbridgingretain i cfbridgingrelease, które przypuszczalnie dodają i odejmują jedną z wartości odniesienia obiektu Objective-C. Dlatego powinniśmy być ostrożni z wielokrotnymi wywołaniami zwrotnymi, aby zwolnić tyle samo razy, ile zatrzymujemy.

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

Rozwiązanie 2) alternatywą jest użycie odpowiednika słabego odniesienia (tj. bez zmiany liczby zatrzymanych), bez dodatkowego bezpieczeństwa.

Język Objective-C zapewnia kwalifikator _ _ bridge cast aby to zrobić (cfbridgingretain i cfbridgingrelease wydają się być cienkimi opakowaniami Cocoa nad konstrukcjami języka Objective-C odpowiednio_ _ bridge _ retained i release, ale Cocoa nie wydaje się mieć odpowiednika dla _ _ bridge).

Wymagane zmiany to:

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);
}
 0
Author: QuesterZen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-26 01:45:32

Można mieszać C++ z Objectiv - C (Objective C++). Napisz metodę C++ w swojej klasie Objective C++, która po prostu wywoła [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; i wywoła ją z twojego C++.

Nie próbowałem tego wcześniej, ale spróbuj i podziel się z nami wynikami.
 -1
Author: hhafez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-29 23:08:47