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
网友评论