C ++ üye işlevinden Objective-C yöntemi çağırılıyor mu?


111

Sorunsuz bir sınıfın EAGLViewüye işlevini çağıran bir class ( ) var C++. Şimdi sorun şu ki , sözdiziminde yapamadığım o C++sınıfta a çağırmam gerekiyor .objective-C function [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];C++

Bu Objective-Cçağrıyı Objective-C, ilk başta C ++ sınıfı olarak adlandırılan aynı sınıfa sarabilirim , ancak daha sonra bir şekilde bu yöntemi çağırmam gerekiyor C++ve nasıl yapılacağını çözemiyorum.

EAGLViewC ++ üye işlevine nesneye bir işaretçi vermeye ve sınıf başlığıma " EAGLView.h" eklemeye çalıştım C++ancak 3999 hata aldım ..

Peki .. bunu nasıl yapmalıyım? Bir örnek güzel olurdu .. Bunu Cyapmanın sadece saf örneklerini buldum .

Yanıtlar:


204

Dikkatlice yaparsanız C ++ ile Objective-C'yi karıştırabilirsiniz. Birkaç uyarı var, ancak genel olarak konuşursak karıştırılabilirler. Bunları ayrı tutmak istiyorsanız, Objective-C nesnesine Objective-C olmayan koddan kullanılabilir bir C tarzı arabirim veren standart bir C sarmalayıcı işlevi ayarlayabilirsiniz (dosyalarınız için daha iyi adlar seçin, bu adları ben seçtim ayrıntı için):

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

Sarmalayıcı işlevinin Objective-C sınıfı ile aynı dosyada olması gerekmez.m , ancak içinde bulunduğu dosyanın Objective-C kodu olarak derlenmesi gerekir . Sarmalayıcı işlevini bildiren başlığın hem CPP hem de Objective-C koduna dahil edilmesi gerekir.

(NOT: Objective-C uygulama dosyasına ".m" uzantısı verilirse, Xcode altına bağlanmaz. ".Mm" uzantısı Xcode'a Objective-C ve C ++, yani Objective-C ++ kombinasyonunu beklemesini söyler. )


PIMPL deyimini kullanarak yukarıdakileri Nesne Yönelimli bir şekilde uygulayabilirsiniz. . Uygulama sadece biraz farklıdır. Kısacası, sarmalayıcı işlevlerini ("MyObject-C-Interface.h" de bildirilen) bir sınıfın içine bir MyClass örneğine (özel) void gösterici ile yerleştirirsiniz.

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

Sarmalayıcı yöntemlerinin artık MyClass'ın bir örneği için void işaretçisi gerektirmediğine dikkat edin; artık MyClassImpl'in özel bir üyesidir. İnit yöntemi, bir MyClass örneğini başlatmak için kullanılır;

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'ın MyClassImpl :: init çağrısıyla başlatıldığına dikkat edin. MyClass'ı MyClassImpl'in kurucusunda başlatabilirsiniz, ancak bu genellikle iyi bir fikir değildir. MyClass örneği, MyClassImpl'ın yıkıcısından yok edildi. C-stili uygulamada olduğu gibi, sarmalayıcı yöntemleri basitçe MyClass'ın ilgili yöntemlerini erteler.

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

Artık, MyClassImpl özel uygulaması aracılığıyla MyClass çağrılarına erişebilirsiniz. Taşınabilir bir uygulama geliştiriyorsanız, bu yaklaşım avantajlı olabilir; MyClass'ın uygulanmasını diğer platforma özgü biriyle değiştirebilirsiniz ... ama dürüst olmak gerekirse, bunun daha iyi bir uygulama olup olmadığı daha çok bir zevk ve ihtiyaç meselesidir.


6
Merhaba, denedim ama sembol (ler) bulunamadı diyen bağlantı hatası alıyorum. yani MyObjectDoSomethingWith'i bulamıyor. herhangi bir fikir?

3
extern "C"int MyObjectDoSomethingWith
dreamlax'tan

2
zaten denedim, işe yaramıyor ve mantıklı çünkü C'den C ++ işlevini çağırmak istediğimizde extern "C" kullanılıyor, bu durumda C ++ 'dan bir C işlevi çağırıyoruz, değil mi?

6
Ayrıca, objectCObject, MyCPPClass.cpp'de nasıl örneklenir?
Raffi Khatchadourian

2
Müthiş @ dreamlax, şu anda derleniyor ama buna "someMethod" demeyi bilmiyorum. Eklenmesi gereken parametreler: int MyCPPClass :: someMethod (void * aimCObject, void * aParameter) ???
the_moon

15

Kodunuzu Objective-C ++ olarak derleyebilirsiniz - en basit yol .cpp dosyanızı .mm olarak yeniden adlandırmaktır. Daha sonra dahil ederseniz EAGLView.h(C ++ derleyicisi Objective-C'ye özgü anahtar sözcüklerin hiçbirini anlamadığı için çok fazla hata alıyordunuz) ve (çoğunlukla) Objective-C ve C ++ 'yı karıştırabilirsiniz ancak siz sevmek.


Bu C ++ dosyasında tüm bu derleyici hatalarını mı alıyorsunuz yoksa bu C ++ başlığını içeren başka bir C ++ dosyasında mı?
Jesse Beder

Görünüşe göre EAGLView.h'yi C ++ üstbilgi dosyasına ekleyemiyorum çünkü o zaman herhangi bir nedenle Objective C kodunun C ++ olmasını ve @ + diğer sembolleri anlamasını
bekler

12

En kolay çözüm, Xcode'a her şeyi Objective C ++ olarak derlemesini söylemektir.

Compile Sources As to Objective C ++ için proje veya hedef ayarlarınızı belirleyin ve yeniden derleyin.

Daha sonra C ++ veya Objective C'yi her yerde kullanabilirsiniz, örneğin:

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

Bu, tüm kaynak dosyalarınızı .cpp veya .m'den .mm'ye yeniden adlandırmakla aynı etkiye sahiptir.

Bunun iki küçük dezavantajı vardır: clang, C ++ kaynak kodunu analiz edemez; bazı nispeten garip C kodları C ++ altında derlenmez.


Biraz merak ediyorum, her şeyi Objective-C ++ olarak derlediğinizde, C-stili yayınların ve / veya diğer C ++ - geçerli C-stili kod hakkında özel uyarıların kullanımıyla ilgili uyarılar alıyor musunuz?
dreamlax

Elbette, C ++ ile programlama yapıyorsunuz, bu nedenle uygun şekilde davranmanız beklenir - ancak genel bir kural olarak, C ++, C'den daha iyi bir C'dir, hiç bir sınıf oluşturmasanız bile. Aptalca şeyler yapmanıza izin vermez ve güzel şeyler yapmanızı sağlar (daha iyi sabitler ve numaralandırmalar gibi). Hala aynı şekilde yayınlayabilirsiniz (örneğin (CFFloat) x).
Peter N Lewis

10

Aşama 1

Hedef c dosyası (.m dosyası) ve buna karşılık gelen başlık dosyası oluşturun.

// Başlık dosyası (Biz buna "ObjCFunc.h" diyoruz)

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

// İlgili Amaç C dosyası (Biz buna "ObjCFunc.m" diyoruz)

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

Adım 2

Şimdi, az önce oluşturduğumuz hedef c işlevini çağırmak için bir c ++ işlevi uygulayacağız! Bunun için bir .mm dosyası ve ona karşılık gelen başlık dosyasını tanımlayacağız (". Mm" dosyası burada kullanılacak çünkü dosyada hem Objective C hem de C ++ kodlamasını kullanabileceğiz)

// Başlık dosyası (Biz buna "ObjCCall.h" diyoruz)

#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__) */

// İlgili Amaç C ++ dosyası (Biz buna "ObjCCall.mm" diyoruz)

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

Aşama 3

C ++ işlevini çağırma (aslında amaç c yöntemini çağırır)

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

//Son çağrı

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

Umarım bu işe yarar!


9

C ++ dosyanızın Objective-C ++ olarak değerlendirilmesini sağlamalısınız. Bunu xcode'da foo.cpp'yi foo.mm olarak yeniden adlandırarak yapabilirsiniz (.mm, obj-c ++ uzantısıdır). O zaman diğerlerinin söylediği gibi standart obj-c mesajlaşma sözdizimi işe yarayacaktır.


1

Bazen .cpp'yi .mm olarak yeniden adlandırmak iyi bir fikir değildir, özellikle de proje çapraz platform olduğunda. Bu durumda xcode projesi için TextEdit ile xcode proje dosyasını açtım, ilgi dosyasını içeren string buldum, şöyle olmalı:

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

ve o andan itibaren dosya türünü değiştirmek sourcecode.cpp.cpp için sourcecode.cpp.objcpp

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

.Cpp'yi .mm olarak yeniden adlandırmaya eşdeğerdir


1

Ayrıca, yöntemi çağırmak için Objective-C çalışma zamanını da çağırabilirsiniz.


1

@ DawidDrozd'un yukarıdaki cevabı mükemmel.

Bir nokta eklerim. Clang derleyicisinin son sürümleri, kodunu kullanmaya çalışırken bir "köprüleme döküm" gerektirdiğinden şikayetçi.

Bu makul görünüyor: bir trambolin kullanmak potansiyel bir hata yaratıyor: Objective-C sınıfları referans olarak sayıldığından, adreslerini bir boşluk * olarak geçirirsek, geri çağırma hala devam ederken sınıf çöp olarak toplanmışsa asılı bir işaretçiye sahip olma riskimiz var aktif.

Çözüm 1) Cocoa, muhtemelen Objective-C nesnesinin referans sayısından bir ekleyip çıkaran CFBridgingRetain ve CFBridgingRelease makro fonksiyonları sağlar. Bu nedenle, sakladığımız kadarıyla aynı sayıda geri aramayı serbest bırakmak için birden çok geri aramaya dikkat etmeliyiz.

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

Çözüm 2) Alternatif, herhangi bir ek güvenlik olmaksızın, zayıf bir referansın eşdeğerini kullanmaktır (yani, tutma sayısında değişiklik olmaması).

Objective-C dili, bunu yapmak için __bridge cast niteleyicisini sağlar (CFBridgingRetain ve CFBridgingRelease, sırasıyla __bridge_retained ve release Objective-C dil yapıları üzerinde ince Kakao sarmalayıcıları gibi görünmektedir, ancak Cocoa'nın __bridge için bir eşdeğeri yok gibi görünmektedir).

Gerekli değişiklikler şunlardır:

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üm tutma / bırakma teatriklerine rağmen Çözüm 1'in ek güvenlik sağlayıp sağlamadığı konusunda biraz şüphelendiğimi itiraf etmeliyim. Bir nokta, selfkapanışa, modası geçmiş olabilecek bir kopyasını geçirmemizdir . Diğer nokta, bunun otomatik referans sayma ile nasıl etkileşime girdiği ve derleyicinin neler olup bittiğini anlayıp anlayamayacağı net değildir. Pratikte, basit bir tek modül oyuncak örneğinde her iki sürümün de başarısız olduğu bir durum yaratmayı başaramadım.
QuesterZen

-1

C ++ 'ı Objectiv-C (Objective C ++) ile karıştırabilirsiniz. Objective C ++ sınıfınızda, [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];onu C ++ ' nızdan çağıran ve çağıran bir C ++ yöntemi yazın .

Kendimden önce denemedim ama bir şans ver ve sonuçlarını bizimle paylaş.


2
Ama sorun şu ki, onu nasıl adlandırabilirim ... çünkü C ++ sınıfıma "EAGLview.h" eklersem binlerce hata alıyorum.
juvenis
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.