最近我们在开发一款游戏,包含四块:c++服务器,ios客户端,android客户端以及c++的客户端。C++客户端用于集成测试以及压力测试。我们希望达到最大限度的重用。C++是自然的选择。我们需要把c++的源代码以库的形式重用在ios和andriod上。这样网络通信和model部分只要维护一套c++代码,ios和android只要写UI和线程。后续将分篇讲述开发中碰到的问题和解决方案。
今天讲述ios重用c++库。
这里有两个难点:
- 我们用到了大量的第三方库,主要有:
l zmq socket通讯
l protobuf 协议
l cryptopp 加密鉴权
这些库的源代码有的比较旧;有的有好几个来源,不知该选择哪个;脚本都需要改动才能在你的平台下运行,但文档并没有提及。
- c++与obj-c的混合编码
一般人可能觉得这很简单,只要把整个工程的.m和.cpp文件全部改为.mm就可以了。如果你写一个简单的helloword程序,这样确实没问题。前面我们说过,我们要以库的形式重用c++,就有一点tricky了。
言归正传,以下阐述过程中遇到的问题和解决方案。
- 编译protobuf
下载c++的代码库,最新版本是2.5.0。按照
http://www.giraffe-games.com/using-protobuf-protocol-buffers-on-iphone-ios/
step by step就可以了。其中有一步执行脚本:
https://github.com/dinote/protobuf-mobile-build/blob/master/ios-config.sh
这个脚本比较新。只要把其中的
export SDKVER="6.0"
替换成你的sdk版本,比如我替换成6.1。
提醒一点,由于以上编译出来的是适用于移动终端的lite版本,因此请在.proto文件的头部加上这一行,否则链接失败。
option optimize_for = LITE_RUNTIME;
lite版本的体量小很多,但它的代价是不能在程序中使用DebugString()等方便调试的函数了。
不要用for objc的代码库。它编译后生成包含c++和objc wrapper的单一库。它的最新版本是2.2.0。由于这个编译出来的库是现成的(我同事编译的),我曾经尝试用2.2.0的protoc来编译.proto文件,生成.h和.cc文件,并在objc程序中以c++的方式调用,结果链接失败。
- 编译cryptopp
这个库资料不完整并且不正确。首先容易弄错的是下载了不正确的库。请下载
https://github.com/yep/CryptoPP-for-iOS
这里你需要改动的是文件:external / scripts / build-cryptopp.sh
export DEV_ROOT="/Applications/Xcode.app/Contents/Developer/Platforms/${PLATFORM}.platform/Developer"
export TOOL_ROOT="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain" //加这一行
export SDK_ROOT="${DEV_ROOT}/SDKs/${PLATFORM}${SDK_VERSION}.sdk"
BUILD_PATH="${WORK_PATH}/objs/${PLATFORM}${SDK_VERSION}-${ARCH}.sdk"
export CC="${DEV_ROOT}/usr/bin/gcc -arch ${ARCH}"
export LD=${DEV_ROOT}/usr/bin/ld
if [ "${ARCH}" == "i386" ]; then
export CXX=${DEV_ROOT}/usr/bin/g++
else
export CXX=${TOOL_ROOT}/usr/bin/clang //改这一行
不要下载在google搜索中排名靠前的
https://github.com/3ign0n/CryptoPP-for-iOS
前者比后者略新,错误少点,改起来就没那么费劲了。我因为下载了这个库,浪费了大量时间。
编译出来的库很大,有100+M,没有关系。链接程序只会选择用到的部分,生成的.app只会增加很小的体量。
- 编译zeromq
我同事以前编译的,生成两个库:libzmq.a和libzmqobjc.a。只需要用前者就行。
- c++和objc混合编程
这里的关键是要保持代码的隔离。工程中混合的需要改名为.mm的文件越少越好。
Objc调用c++相对简单,只要在Objc的类中包含c++的成员对象,并且调用该对象的成员函数就可以了。例如我的一个NSOperation子类中只包含这几行代码
- (id)init
{
self = [super init];
msgDispatcher = new MsgDispatcher();
return self;
}
- (void)main
{
@autoreleasepool
{
msgDispatcher->dispatch();
}
}
其中msgDispatcher类在c++编译成的库中。
c++调用Objc就很有讲究了,因为这部分代码是不能包含Objc的头文件和成员变量的。为了防止c++对Objc的依赖,按照正常的逻辑,声明一个c++虚类,这个类位于需要重用的c++编译成的库中。由Objc继承并实现它的虚函数。遗憾的是,objc与c++之间不能互相继承,双方向都不行。接下来你会想到用什么?对,cast。以下是代码示例。
MsgHandlerImpl.h
class MsgHandlerImpl
{
public:
MsgHandlerImpl ( void );
~MsgHandlerImpl( void );
void onCheckin ();
private:
void * viewControllerAdapter;
};
MsgHandlerImpl::MsgHandlerImpl()
{
viewControllerAdapter = (void *)CFBridgingRetain([[ViewControllerAdapter alloc] init]);
}
MsgHandlerImpl.cpp
MsgHandlerImpl::~MsgHandlerImpl()
{
}
void MsgHandlerImpl::onCheckin()
{
[(ViewControllerAdapter *)CFBridgingRelease(viewControllerAdapter) onCheckin];
}
这里关键中的关键就是CFBridgingRetain和CFBridgingRelease。作用类似于cast。
千万不要用(__bridge void *)作强制类型转换。
viewControllerAdapter = (__bridge void *) [[ViewControllerAdapter alloc] init];
- 其它问题
以下都是我走了很多弯路之后得来的。
5.1. 文件类型
问题:
编译时提示大量系统头文件内部的问题
解决方案:
多半是你没有把.m文件或者.cpp文件改为.mm文件。虽然你的.m文件没有直接包含c++代码,但只要是include c++的头文件,或者import的objc头文件中间接include c++的头文件,都需要改后缀名为.mm文件,或者在file inspector中改file type为object c++ source。后者更为方便。
5.2. std链接
问题:
编译时提示大量std::string的问题
解决方案:
In main project -> Build Settings scroll and find out the options, C++ Language Dialect and C++ Standard Library. Select options "Compiler Default" for both of them.
5.3. Zmq包含的头文件找不到
问题:
‘algorithm’ it not found
解决方案:
header search path中添加/usr/include/c++/4.2.1