【IOS开发基础系列 整理】OC与c++混编专题

1 objective-c和c++混合编程

1.1 OC调用C++类的方法

        在 Objective-C++中,可以用C++代码调用方法也可以从Objective-C调用方法。在这两种语言里对象都是指针,可以在任何地方使用。例 如,C++类可以使用Objective-C对象的指针作为数据成员,Objective-C类也可以有C++对象指针做实例变量。下例说明了这一点。

        注意:Xcode需要源文件以".mm"为扩展名,这样才能启动编译器的Objective-C++扩展。

1.1.1 简单示例

/* Hello.mm

* Compilewith: g++ -x objective-c++ -framework Foundation Hello.mm  -o hello

*/

#import <Foundation/Foundation.h>

class Hello {

    private: 

        id greeting_text;  // holds an NSString

    public:    

        Hello() {

            greeting_text = @"Hello, world!";

        }

        Hello(const char* initial_greeting_text) {

            greeting_text = [[NSString alloc] initWithUTF8String:initial_greeting_text];

        }

        void say_hello() {

            printf("%s\n", [greeting_text UTF8String]);

        }

};


@interface Greeting: NSObject {

    @private

        Hello *hello;

}

- (id) init;

- (void) dealloc;

- (void) sayGreeting;

- (void) sayGreeting:(Hello*)greeting;

@end


@implementationGreeting

- (id) init {

    if (self = [super init]) {

        hello = new Hello();

    }

    return self;

}

- (void) dealloc {

    delete hello;

    [super dealloc];

}

- (void) sayGreeting {

    hello->say_hello();

}

- (void) sayGreeting:(Hello*)greeting {

    greeting->say_hello();

}

@end


int main() {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    Greeting *greeting = [[Greeting alloc] init];

    [greeting sayGreeting];  // > Hello,  world!

    Hello *hello = new

    Hello("Bonjour, monde!");

    [greeting sayGreeting: hello];// > Bonjour,  monde!

    delete hello;

    [greeting release];

    [pool release];

    return 0;

}

        正如你可以在OC接口中声明C结构一样,你也可以在OC接口中声明C++类。跟C结构一样,OC接口中定义的C++类是全局范围的,不是OC类的内嵌类(这与标准C(尽管不是C++)提升嵌套结构定义为文件范围是一致的)。

        为了允许你基于语言变种条件化地编写代码,OC++编译器定义了__cplusplus和__OBJC__预处理器常量,分别指定C++和OC。    如前所述,OC++不允许C++类继承自OC对象,也不允许OC类继承自C++对象。

class Base {/* ... */ };

@interfaceObjCClass: Base ... @end // ERROR!

class Derived:public ObjCClass ... // ERROR!

        与 OC不同的是,C++对象是静态类型的,有运行时多态是特殊情况。两种语言的对象模型因此不能直接兼容。更根本的,OC和C++对象在内存中的布局是互不 相容的,也就是说,一般不可能创建一个对象实例从两种语言的角度来看都是有效的。因此,两种类型层次结构不能被混合。

        你可以在OC类内部声明C++类,编译器把这些类当作已声明在全局名称空间来对待。就像下面:

@interface Foo{

    class Bar { ... } // OK

}

@end


Bar *barPtr;// OK

        OC允许C结构作为实例变量,不管它是否声明在OC声明内部。

@interface Foo{

    struct CStruct { ... };

    struct CStruct bigIvar; // OK

} ...


@end


        Mac OS X 10.4以后,如果你设置fobjc- call-cxx-cdtors编译器标志,你就可以使用包含虚函数和有意义的用户自定义零参数构造函数、析构函数的C++类实例来做为实例变量 (gcc-4.2默认设置编译器标志fobjc-call-cpp-cdtors)。OC成员变量alloc完以后,alloc函数会按声明顺序调用构造器。构造器使用公共无参数恰当的构造函数。OC成员变量dealloc之前,dealloc方法按声明顺序反序调用析构函数。OC没有名称空间得概念,不能在C++名称空间内部声明OC,也不能在OC类里声明名称空间。OC类、协议、分类不能声明在C++ template里,C++ template也不能声明在OC接口、协议、分类的范围内。

        但是,OC类可以做C++ template的参数,C++ template参数也可以做OC消息表达式的接收者或参数(不能通过selector)。


1.1.2 OC++类的头文件定义

        你可能会想使用等价的Objective-C类型和函数将C++代码封装(wrap)起来。比方说,你有一个名为CppObject的C++类(CppObject.h):

#include <string>

class CppObject

{

      public:

          void ExampleMethod(conststd::string& str);

          // constructor, destructor, other members, etc.

};

        在Objectiv-C类允许定义C++类的成员变量,所以可以首先尝试定义一个ObjcObject封装类(ObjcObject.h):

#import 

#import "CppObject.h"

@interface ObjcObject : NSObject {

    CppObject wrapped;

}

- (void) exampleMethodWithString:(NSString*)str;

// other wrapped methods and properties

@end

        然后在ObjcObject.mm中实现这些方法。不过,此时会在两个头文件(ObjcObject.h&CppObject.h)中得到一个预处理和编译错误。问题出在#include和#import上。对于预处理器而言,它只做文本的替换操作。所以#include和#import本质上就是递归地复制和粘贴引用文件的内容。这个例子中,使用#import "ObjcObject.h"等价于插入如下代码:

// [首先是大量Foundation/Foundation.h中的代码]

// [无法包含],因为它仅存在于C++模式的include path中

class CppObject

{

    public:

          void ExampleMethod(conststd::string& str);

          // constructor, destructor, other members, etc.

};

@interface ObjcObject : NSObject {

    CppObject wrapped;

}

- (void)exampleMethodWithString:(NSString*)str;

// other wrapped methods and properties

@end

        因为class CppObject根本不是有效的Objective-C语法,所以编译器就被搞糊涂了。 错误通常是这样的:

Unknown type name'class'; did you mean 'Class'?

        正是因为Objective-C中没有class这个关键字.所以要与Objective-C兼容,Objective-C++类的头文件必须仅包含Objective-C代码,绝对没有C++的代码-这主要是影响类型定义(就像例中的CppObject类)。

1.1.3 简洁头文件——使用ivars

        之前的文章已经提到一些解决方案.其中最好的一个是PIMPL,它也适用于现在的情况。这里还有一个适用于clang的新方法,可以将C++代码从Objective-C中隔开,这就是class extensions中ivars的。

        Class extensions(不要同categories弄混)已经存在一段时间了:它们允许你在class的接口外的扩展部分定义在@implementation段前,而不是在公共头文件中。这个例子就可以声明在ObjcObject.mm中:

#import "ObjcObject.h"

@interface ObjcObject() // note the empty parentheses

- (void)methodWeDontWantInTheHeaderFile;

@end

@implementation ObjcObject

//etc.

        GCC也支持这个操作。不过clang还支持添加ivar块,也就是你还可以声明C++类型的实例变量,既可以在classextension中,也可以在@implementation开始的位置。本例中的ObjcObject.h可以被精简为:

#import <Foundation/Foundation.h>

@interface ObjcObject : NSObject

- (void)exampleMethodWithString:(NSString*)str;

//other wrapped methods and properties

@end


        去掉的部分都移到实现文件的class extension中(ObjcObject.mm):

#import "ObjcObject.h"

#import "CppObject.h"

@interface ObjcObject(){

  CppObject wrapped;

}

@end


@implementation ObjcObject

- (void)exampleMethodWithString:(NSString*)str

{

//NOTE: str为nil会建立一个空字串,而不是引用一个指向UTF8String空指针.

    std::string cpp_str([str UTF8String],[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);

    wrapped.ExampleMethod(cpp_str);

}

        如果我们不需要interface extension来声明额外的属性和方法,ivar块仍然可以放在@implementation开始位置:

#import "ObjcObject.h"

#import "CppObject.h"


@implementation ObjcObject{

    CppObject wrapped;

}


- (void)exampleMethodWithString:(NSString*)str

{

//NOTE: str为nil会建立一个空字串,而不是引用一个指向UTF8String空指针.

    std::string cpp_str([str UTF8String],[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);

    wrapped.ExampleMethod(cpp_str);

}

        定义的CppObject实例wrapped在ObjcObject创建时,CppObject的缺省建构函数会被调用,而在ObjcObject被调用dealloc析构时,ObjcObject的析构函数也会被调用。如果ObjcObject没有提供缺省的建构函数,编译就会失败。

1.1.4 管理被封装C++对象的生命周期

        解决方案是透过new关键字掌握建构过程,比如:

@interface ObjcObject(){

    CppObject *wrapped;//指针!会在alloc时初始为NULL.

}

@end


@implementation ObjcObject

- (id)initWithSize:(int)size

{

        self = [super init];

        if(self)

        {

            wrapped = new CppObject(size);

            if(!wrapped) self = nil;

        }

        return self;

}

//...

        如果是使用C++异常,也可以使用try {...} catch {...}把创建过程封装起来.相应地,还要显式地释放封闭对象:

- (void)dealloc

{

    deletewrapped;

    [super dealloc];//如果使用了ARC,这句就要略去

}

        作者接着提到了另一个方法,显示分配一块内存,然后在它的基础上调用new来创建对象。首先声明char wrapped_mem[sizeof(CppObject)];再使用wrapped = new(wrapped_mem) CppObject();创建了实例wrapped。释放时if (wrapped) wrapped->~CppObject();这样虽然可行,但不建议使用。


1.1.5 总结

        一定要确保封装的方法仅返回和使用C或Objective-C类型的返回值及参数。同时不要忘记C++中不存在nil,而NUL是不可用于解引用的。

1.2 在C++代码中使用Objective-C类

1.2.1 简单示例

        这个问题同样存在于头文件中。你不能因为引入Objective-C类型而污染了C++头文件,或无法被纯C++代码所引用。比方说,我们想封装的Objective-C类ABCWidget,在ABCWidget.h声明为:

#import <Foundation/Foundation.h>

@interface ABCWidget

- (void)init;

- (void)reticulate;

//etc.

@end

        这样的类定义在Objective-C++中是没有问题的,但在纯C++的代码是不允许的:

#import "ABCWidget.h"

namespace abc

{

    class Widget

    {

            ABCWidget *wrapped;

        public:

            Widget();

            ~Widget();

            void Reticulate();

    };

}

        一个纯粹的C++编译器在Foundation.h中的代码和ABCWidget声明位置出错。

1.2.2 永恒的PIMPL

        有没有这样的东西作为一类扩展C++,这样的把戏将无法正常工作。 另一方面,PIMPL,工作得很好,实际上是比较常用的纯C++了。在我们的例子中,我们减少到最低限度:C++类

        C++并没有之前提到的class extension,但是却有另一种较为常用的方式:PIMPL(Private Implementation,私有实现)。这里,将C++class的定义精简为:

namespace abc

{

    struct WidgetImpl;

    class Widget

    {

        WidgetImpl *impl;

        public:    

            Widget();

            ~Widget();

            void Reticulate();

    };

}

        然后在Widget.mm中:

#include "Widget.hpp"

#import "ABCWidget.h"

namespace abc

{

    struct WidgetImpl

    {

        ABCWidget *wrapped;

    };

    Widget::Widget():impl(newWidgetImpl)

    {

        impl->wrapped = [[ABCWidgetalloc] init];

    }

    Widget::~Widget()

    {

        if(impl)

            [impl->wrapped release];

        delete impl;

    }

    void Widget::Reticulate()

    {

        [impl->wrapped reticulate];

    }

}

        它的工作原理是——前置声明。声明这样的结构或类对象的指针成员变量、结构或类就足够了。需要注意的是封装的对象会在析构函数中释放。即便对于使用了ARC的项目,我还是建议你对这样的C++/Objective-C重引用的文件屏蔽掉它。不要让C++代码依赖于ARC。在XCode中可以针对个别文件屏蔽掉ARC。Target properties->Build phase页签,展开'CompileSources',为特定文件添加编译选项-fno-objc-arc。


1.2.3 C++中封装Objective-C类的捷径

        您可能已经注意到,PIMPL解决方案使用两个级别的间接引用。如果包装的目标类像本例中的一样简单,就可能会增大了复杂性。虽然Objective-C的类型一般不能使用在纯C++中,不过有一些在C中实际已经定义了。id类型就是其中之一,它的声明在头文件中。虽然会失去一些Objective-C的安全性,你还是可以把你的对象直接传到C++类中:

#include <objc/objc-runtime.h>

namespace abc

{

    class Widget

    {

        id/*ABCWidget* */wrapped;

        public:

            Widget();

            ~Widget();

            void Reticulate();

    };

}

        不建议向id对象直接发送消息。这样你会失去很多编译器的检查机制,特别是对于不同类中有着相同selector名字的不同方法时。所以:

#include "Widget.hpp"

#import "ABCWidget.h"

namespace abc

{

    Widget::Widget() : wrapped([[ABCWidgetalloc] init])

    {

    }

    Widget::~Widget()

    {

        [(ABCWidget*)impl release];

    }

    void Widget::Reticulate()

    {

        [(ABCWidget*)impl reticulate];

    }

}

        像这样的类型转换很容易在代码中隐藏错误,再尝试一个更好的方式。在头文件中:

#ifdef __OBJC__

@class ABCWidget;

#else

typedef struct objc_object ABCWidget;

#endif


namespace abc

{

    class Widget

    {

        ABCWidget *wrapped;

        public:

            Widget();

            ~Widget();

            void Reticulate();

    };

}

        如果这个头文件被一个mm文件引用,编译器可以充分识别到正确的类。

        如果是在纯C++模式中引用,ABCWidget*是一个等价的id类型:定义为typedef struct objc_object* id;。

        #ifdef块还可以被进一步放到一个可重用的宏中:

#ifdef __OBJC__

#define OBJC_CLASS(name) @class

name

#else

#define OBJC_CLASS(name) typedef

struct objc_object name

#endif

        现在,我们可以前置声明在头文件中一行就可以适用于所有4种语言:

OBJC_CLASS(ABCWidget);


1.3 C++词汇歧义和冲突

        OC头文件中定义了一些标识符,所有的OC程序必须包含的,这些标识符识id,Class,SEL,IMP和BOOL。

        OC方法内,编译器预声明了标识符self和super,就像C++中的关键字this。跟C++的this不同的是,self和super是上下文相关的;OC方法外他们还可以用于普通标识符。

        协议内方法的参数列表,有5个上下文相关的关键字(oneway,in,out,inout,bycopy)。这些在其他内容中不是关键字。

        从OC程序员的角度来看,C++增加了不少新的关键字。你仍然可以使用C++的关键字做OC selector的一部分,所以影响并不严重,但你不能使用他们命名OC类和实例变量。例如,尽管class是C++的关键字,但是你仍然能够使用NSObject的方法class:

[foo class];// OK

        然而,因为它是一个关键字,你不能用class做变量名称:

NSObject *class; // Error

        OC里类名和分类名有单独的命名空间。@interface foo和@interface(foo)能够同时存在在一个源代码中。OC++里,你也能用C++中的类名或结构名来命名你的分类。

        协议和template标识符使用语法相同但目的不同:

id<someProtocolName> foo;

TemplateType<SomeTypeName> bar;

        为了避免这种含糊之处,编译器不允许把id做template名称。

        最后,C++有一个语法歧义,当一个label后面跟了一个表达式表示一个全局名称时,就像下面:

label:::global_name = 3;

        第一个冒号后面需要空格。OC++有类似情况,也需要一个空格:

receiver selector: ::global_c++_name;


1.4 限制

        OC++没有为OC类增加C++的功能,也没有为C++类增加OC的功能。例如,你不能用OC语法调用C++对象,也不能为OC对象增加构造函数和析构函数,也不能将this和self互相替换使用。类的体系结构是独立的。C++类不能继承OC类,OC类也不能继承C++类。另外,多语言异常处理是不支持的。也就是说,一个OC抛出的异常不能被C++代码捕获,反过来C++代码抛出的异常不能被OC代码捕获。

摘自:http://ocen.iteye.com/blog/522028


2 参考链接

混合使用Objective-C,C++和Objective-C++

http://blog.csdn.net/horkychen/article/details/7935910

objective-c和c++混合编程

http://www.cnblogs.com/85538649/archive/2011/09/29/2195332.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容