Aufruf der Objective-C-Methode über die C ++ - Mitgliedsfunktion?

111

Ich habe eine class ( EAGLView), die C++problemlos eine Member-Funktion einer Klasse aufruft . Das Problem ist nun, dass ich diese C++Klasse a aufrufen muss, objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];was ich in der C++Syntax nicht tun kann .

Ich könnte diesen Objective-CAufruf in dieselbe Objective-CKlasse einschließen, die an erster Stelle die C ++ - Klasse genannt hat, aber dann muss ich diese Methode irgendwie aufrufen C++, und ich kann nicht herausfinden, wie es geht.

Ich habe versucht, EAGLViewder C ++ - Mitgliedsfunktion einen Zeiger auf ein Objekt zu geben und das " EAGLView.h" in meinen C++Klassenheader aufzunehmen, aber ich habe 3999 Fehler erhalten.

Also ... wie soll ich das machen? Ein Beispiel wäre schön. Ich habe nur reine CBeispiele dafür gefunden.

juvenis
quelle

Antworten:

204

Sie können C ++ mit Objective-C mischen, wenn Sie dies sorgfältig tun. Es gibt einige Einschränkungen, aber im Allgemeinen können sie gemischt werden. Wenn Sie sie getrennt halten möchten, können Sie eine Standard-C-Wrapper-Funktion einrichten, die dem Objective-C-Objekt eine verwendbare C-artige Schnittstelle aus Nicht-Objective-C-Code verleiht (wählen Sie bessere Namen für Ihre Dateien, ich habe diese Namen ausgewählt für Ausführlichkeit):

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

Die Wrapper-Funktion muss sich nicht in derselben .mDatei wie die Objective-C-Klasse befinden, aber die Datei, in der sie vorhanden ist, muss als Objective-C-Code kompiliert werden . Der Header, der die Wrapper-Funktion deklariert, muss sowohl im CPP- als auch im Objective-C-Code enthalten sein.

(HINWEIS: Wenn die Objective-C-Implementierungsdatei die Erweiterung ".m" erhält, wird sie nicht unter Xcode verknüpft. Die Erweiterung ".mm" weist Xcode an, eine Kombination aus Objective-C und C ++ zu erwarten, dh Objective-C ++. )


Sie können das Obige objektorientiert implementieren, indem Sie das PIMPL-Idiom verwenden . Die Implementierung ist nur geringfügig anders. Kurz gesagt, Sie platzieren die Wrapper-Funktionen (deklariert in "MyObject-C-Interface.h") in einer Klasse mit einem (privaten) Void-Zeiger auf eine Instanz von 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

Beachten Sie, dass die Wrapper-Methoden den void-Zeiger auf eine Instanz von MyClass nicht mehr benötigen. Es ist jetzt ein privates Mitglied von MyClassImpl. Die init-Methode wird verwendet, um eine MyClass-Instanz zu instanziieren.

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

Beachten Sie, dass MyClass mit einem Aufruf von MyClassImpl :: init instanziiert wird. Sie könnten MyClass im Konstruktor von MyClassImpl instanziieren, aber das ist im Allgemeinen keine gute Idee. Die MyClass-Instanz wird vom Destruktor von MyClassImpl zerstört. Wie bei der Implementierung im C-Stil verschieben sich die Wrapper-Methoden einfach auf die jeweiligen Methoden von 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." );
    }
}

Sie können jetzt über eine private Implementierung von MyClassImpl auf Aufrufe von MyClass zugreifen. Dieser Ansatz kann vorteilhaft sein, wenn Sie eine tragbare Anwendung entwickeln. Sie könnten einfach die Implementierung von MyClass gegen eine für die andere Plattform spezifische austauschen ... aber ehrlich gesagt ist es eher eine Frage des Geschmacks und der Bedürfnisse, ob dies eine bessere Implementierung ist.

Dreamlax
quelle
6
Hallo, ich habe es versucht, aber es wird ein Verknüpfungsfehler angezeigt, der besagt, dass Symbole nicht gefunden wurden. dh es kann das MyObjectDoSomethingWith nicht finden. irgendwelche Ideen?
3
Möglicherweise müssen Sie extern "C"vor demint MyObjectDoSomethingWith
dreamlax
2
habe das schon versucht, funktioniert nicht und es macht Sinn, weil extern "C" verwendet wird, wenn wir die C ++ - Funktion von C aus aufrufen wollen. In diesem Fall rufen wir eine C-Funktion von C ++ aus, nein?
6
Wie wird ObjectiveCObject in MyCPPClass.cpp instanziiert?
Raffi Khatchadourian
2
Awesome @dreamlax, das wird gerade kompiliert, aber ich weiß nicht, dass ich das "someMethod" nennen soll. Welche Parameter sollten hinzugefügt werden: int MyCPPClass :: someMethod (void * ObjectiveCObject, void * aParameter) ???
the_moon
15

Sie können Ihren Code als Objective-C ++ kompilieren. Am einfachsten ist es, Ihre .cpp in .mm umzubenennen. Es wird dann ordnungsgemäß kompiliert, wenn Sie einschließen EAGLView.h(Sie haben so viele Fehler erhalten, weil der C ++ - Compiler keines der Objective-C-spezifischen Schlüsselwörter verstanden hat), und Sie können Objective-C und C ++ (größtenteils) nach Belieben mischen mögen.

Jesse Beder
quelle
Erhalten Sie all diese Compilerfehler in dieser C ++ - Datei oder in einer anderen C ++ - Datei, die diesen C ++ - Header enthält?
Jesse Beder
Es scheint, dass ich die EAGLView.h nicht in die C ++ - Headerdatei aufnehmen kann, da dann aus irgendeinem Grund erwartet wird, dass der Objective C-Code C ++ ist und @ + andere Symbole nicht versteht
juvenis
12

Die einfachste Lösung besteht darin, Xcode anzuweisen, alles als Objective C ++ zu kompilieren.

Legen Sie Ihre Projekt- oder Zieleinstellungen für Compile Sources As für Objective C ++ fest und kompilieren Sie sie neu.

Dann können Sie C ++ oder Objective C überall verwenden, zum Beispiel:

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

Dies hat den gleichen Effekt wie das Umbenennen aller Quelldateien von .cpp oder .m in .mm.

Dies hat zwei kleine Nachteile: clang kann C ++ - Quellcode nicht analysieren; Einige relativ seltsame C-Codes werden unter C ++ nicht kompiliert.

Peter N Lewis
quelle
Ich bin nur ein bisschen neugierig, wenn Sie alles als Objective-C ++ kompilieren, erhalten Sie Warnungen bezüglich der Verwendung von Casts im C-Stil und / oder anderer C ++ - spezifischer Warnungen über gültigen Code im C-Stil?
Dreamlax
Sicher, Sie programmieren in C ++, sodass von Ihnen ein angemessenes Verhalten erwartet wird. In der Regel ist C ++ jedoch ein besseres C als C, auch wenn Sie niemals eine Klasse erstellen. Es lässt dich keine dummen Dinge tun, und es lässt dich nette Dinge tun (wie bessere Konstanten und Aufzählungen und so). Sie können immer noch genau das gleiche zaubern (z. B. (CFFloat) x).
Peter N Lewis
10

Schritt 1

Erstellen Sie eine objektive c-Datei (.m-Datei) und die entsprechende Header-Datei.

// Header-Datei (Wir nennen es "ObjCFunc.h")

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

// Entsprechende Objective C-Datei (Wir nennen es "ObjCFunc.m")

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

Schritt 2

Jetzt implementieren wir eine c ++ - Funktion, um die soeben erstellte objektive c-Funktion aufzurufen! Dazu definieren wir eine .mm-Datei und die zugehörige Header-Datei (".mm" -Datei ist hier zu verwenden, da wir sowohl Objective C- als auch C ++ - Codierung in der Datei verwenden können).

// Header-Datei (Wir nennen es "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__) */

// Entsprechende Ziel-C ++ - Datei (Wir nennen sie "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
}

Schritt 3

Aufruf der c ++ - Funktion (die tatsächlich die objektive c-Methode aufruft)

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

//Letzte Aufforderung

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

Hoffe das funktioniert!

Mishal Shah
quelle
9

Sie müssen dafür sorgen, dass Ihre C ++ - Datei als Objective-C ++ behandelt wird. Sie können dies in xcode tun, indem Sie foo.cpp in foo.mm umbenennen (.mm ist die obj-c ++ - Erweiterung). Dann wird, wie andere gesagt haben, die Standard-obj-c-Messaging-Syntax funktionieren.

olliej
quelle
1

Manchmal ist es nicht ratsam, .cpp in .mm umzubenennen, insbesondere wenn das Projekt plattformübergreifend ist. In diesem Fall für Xcode-Projekt öffne ich die Xcode-Projektdatei über TextEdit und habe eine Zeichenfolge gefunden, deren Inhaltsinteressendatei wie folgt aussehen sollte:

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

und ändern Sie dann den Dateityp von sourcecode.cpp.cpp in sourcecode.cpp.objcpp

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

Dies entspricht dem Umbenennen von .cpp in .mm

Jewgenij Logatschow
quelle
1

Sie können auch die Objective-C-Laufzeit aufrufen, um die Methode aufzurufen.

Maxthon Chan
quelle
1

@ DawidDrozds Antwort oben ist ausgezeichnet.

Ich würde einen Punkt hinzufügen. Neuere Versionen des Clang-Compilers beklagen, dass beim Versuch, seinen Code zu verwenden, eine "Überbrückungsbesetzung" erforderlich ist.

Dies erscheint vernünftig: Die Verwendung eines Trampolins führt zu einem potenziellen Fehler: Da Objective-C-Klassen als Referenz gezählt werden und wir ihre Adresse als ungültig * weitergeben, besteht die Gefahr, dass ein hängender Zeiger angezeigt wird, wenn die Klasse während des Rückrufs als Müll gesammelt wird aktiv.

Lösung 1) Cocoa bietet die Makrofunktionen CFBridgingRetain und CFBridgingRelease, mit denen vermutlich eine vom Referenzzähler des Objective-C-Objekts addiert und subtrahiert wird. Wir sollten daher bei mehreren Rückrufen vorsichtig sein, um die gleiche Anzahl von Malen freizugeben, die wir behalten.

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

Lösung 2) Die Alternative besteht darin, das Äquivalent einer schwachen Referenz (dh keine Änderung der Aufbewahrungszahl) ohne zusätzliche Sicherheit zu verwenden.

Die Objective-C-Sprache bietet hierfür das __bridge-Cast-Qualifikationsmerkmal (CFBridgingRetain und CFBridgingRelease scheinen dünne Cocoa-Wrapper über den Objective-C-Sprachkonstrukten __bridge_retained bzw. release zu sein, Cocoa scheint jedoch kein Äquivalent für __bridge zu haben).

Die erforderlichen Änderungen sind:

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);
}
QuesterZen
quelle
Ich muss zugeben, dass ich etwas misstrauisch bin, ob Lösung 1 trotz aller Theatrics zum Zurückhalten / Freigeben zusätzliche Sicherheit bietet. Ein Punkt ist, dass wir eine Kopie von selfin den Verschluss übergeben, die veraltet sein könnte. Der andere Punkt ist, dass nicht klar ist, wie dies mit der automatischen Referenzzählung interagiert und ob der Compiler herausfinden kann, was los ist. In der Praxis habe ich es nicht geschafft, eine Situation zu schaffen, in der eine der beiden Versionen in einem einfachen Ein-Modul-Spielzeugbeispiel fehlgeschlagen ist.
QuesterZen
-1

Sie können C ++ mit Objectiv-C (Objective C ++) mischen. Schreiben Sie eine C ++ - Methode in Ihre Objective C ++ - Klasse, die einfach aufruft[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; und von Ihrem C ++ aus .

Ich habe es noch nie zuvor selbst ausprobiert, aber probieren Sie es aus und teilen Sie uns die Ergebnisse mit.

hhafez
quelle
2
Aber das Problem ist, wie kann ich es nennen ... denn wenn ich "EAGLview.h" in meine C ++ - Klasse einbinde, bekomme ich Tausende von Fehlern.
Juvenis