从Windows(x86)到Linux(x64)的代码移植总结


由于最新的引擎要支持x64 Linux,算是实战了一把代码移植技术,这里就将移植相关的技术做一总结,和各位沟通交流,也方便作为手册随时查阅

这里总结的是从win32到 x64 Linux的移植,其中涉及到32到64移植和windows到Linux的移植过程,这里先说明一下。

首先就Windows和Linux之间的差异列举如下,开发环境基于VC 2005(Windows 7)和GCC(x64 Linux)

1、基本类型间差异

  有做过windows 32位到windows64位移植的朋友应该清楚,VC 64对VC 32的兼容性很高,除了指针的长度从32位提高到了64位,其他基本类型没有任何差别,这样使得windows中的32到64移植基本没有什么工作量,只要代码中没有指针到其他类型的强制转换,基本上代码拉过来直接编译一下就能用了。这里的32到64的变化有一个名词就叫LLP64(long long and pointer 64)。这样的话VC 64中要使用64 bit的整形,就需要使用long long类型。

  在GCC 64中,采用的则是另外一种模型,即LP64,其中基本类型long从32位变换为64位。在LP64模型中,牺牲掉了兼容性,这样导致代码中凡是涉及long的地方都要谨慎对待,尤其在编写32位代码过程中将long认为和int等同,则更有可能存在潜在问题。

  除此之外,wchar_t的长度也是不一样的,VC下是2字节,GCC下则是4字节。

2、数据对齐问题

  由于在x86架构下,32位和64位数据对齐可以说不是什么问题,只是VC和GCC之间使用的关键字不同,使用方法也不太一样,所以有必要简单提一下。

3、字节序问题

  由于这次是在x86和x86_64之间进行移植,都是little endian的,所以字节序问题不存在,如果有朋友需要将代码移植到其他平台,比如PowerPC,Sparc等,则需要重点关注了。

4、字符串编码问题

  针对char和wchar_t,就有窄字符编码和宽字符编码的问题。Windows中窄字符就是MBCS编码,即多字节编码,如中文windows就是GBK(这一系列GB2312,GBK,GB18030等),MBCS编码兼容ASCII码。宽字符编码就是windows宣传的Unicode编码,采用UTF16编码。而linux中的主流操作系统窄字符编码即UTF8(可配,系统默认一般为UTF8),而宽字符编码为UTF32。

5、OS相关API不一致问题

  Windows是非类Unix系的操作系统,不包含POSIX接口,只有自己的Windows API。包括多线程API,线程同步API,文件操作API,Socket API,管道API等等,不管是函数名和参数都是有很大差异的。

  有一点有必要独立拎出来提一下,那就是Windows的Crital secion是递归锁,而linux下默认则不是。

6、编译环境的不一致

  Windows下的编译环境,即这里的VC,她是一个集成开发环境,包含强大而智能的编译环境,开发者需要做的就是点一下Build命令即可,编译依赖等问题根本不是问题。

  而linux下则更多需要开发者动手自己操作,这也是linux常归于高手的一个原因,因为她不喜欢懒人。

  了解了这些差异,基本上能够预见移植过程中的常见问题,这也是知己知彼的道理,针对这些差异都有对应的一些解决方案,或者说是屏蔽方案,总的一个思想就是,通过封装统一平台差异,无法统一的就采用条件编译,针对不同平台编写功能表征一致的代码。

  一切以统一为先,毕竟跨平台开发最好的就是一份代码处处编译,这样才能减少多平台维护的成本,尽管这样会影响性能,但是对比来看性能的些许损失还是值得的。

好了,这里说说针对不同差异的一些解决办法

1、基本类型差异

针对类型差异,需要通过统一类型来进行屏蔽,简单来说就是通过typedef或者#define对不同平台间的类型进行统一抽象,相同类型统一为相同位长。

例如:

typedef sp_byte_t unsigned char
typedef sp_int8_t sp_byte_t
typedef sp_int16_t short
typedef sp_int32_t int

等等

这样可以程度避免由于基本类型位长不同而导致的一些列问题。对于平台相关的基本类型,比如long,尽量不用,这也是多一事不如少一事的道理,如果确实需要进行大数运算而涉及到64位整型,那么需要注意基本类型间转换而导致的溢出或者数据丢失,而且如果要使用类printf的函数,输出的指示符也要注意。

如果涉及到32位进程和64位进程间的通信,也要注意平台间类型的使用,否则由于位长的不一致,而导致数据错乱,比如size_t的使用,等等

这里顺带着把对其问题说说

在VC中使用#pragma pack可以指定结构体的对齐方式

例如:

#pragma pack(1)
struct s {
    char t1;
    char t2;
    int t3;
}
#pragma pack()

而在GCC中则需要使用__attribute__属性设置

例如:

struct s {
    char t1;
    char t2;
    int t3;
} __attribute__((packed))

2、字符串编码问题

这里说说我采用的一种策略。即linux模拟windows的字符串编码模式

这样就涉及分别统一窄字符和宽字符编码

需要注意如下几个问题:

① 确保源代码文件能够正常解析

由于vc中的默认源代码编码为GBK,使用GCC编译时,需要指定源代码编码名称,否则以GCC默认的UTF8解码会出现乱码或者报错,通过参数-finput-charset=GBK设置即可

② 确保编译后可执行文件中字符串编码正确

上面的参数只是告诉GCC如何正常解析源代码文件,当完成编译后,生成可执行文件时,GCC保存到可执行文件中的字符串编码默认是UTF8,所以这里也要修改为GBK,通过参数-fexec-charset=GBK

③ 确保运行时宽窄字符能够正常转换

这涉及到locale,因为C运行库中的mbstowcs和wcstombs是locale相关的,linux下默认的是UTF8,所以这里也要设置,否则运行时会出现转码错误。

可以在程序开始时设置环境变量LC_ALL=zh_CN.GBK即可

或者在运行中调用setlocale

④ 上面提到wchart_t在两系统的长度是不一样的

在windows中采用UTF16编码,在Linux中采用UTF32编码。一般没有什么问题,但是如果windows程序需要和linux程序进行交互的话,可能需要注意UTF32和UTF16之间的编码转换。

或者涉及到Java封装,比如我这次移植的内核,需要对C接口进行Java封装,涉及到wchar_t到Java的UTF16编码字符串(Java中采用unsigned short表示)转换,就需要注意着一点

通过上面的一系列配置,linux中的仿windows编码环境也就弄好了,这样能够减少windows代码移植的工作量。

当然还有其他的方式,比如环境无关的统一字符串编码等等,就留给今后探索吧

3、OS相关API不一致问题

这类问题是硬伤,需要一些的底层库进行跨平台封装,以统一特性。

可以自行开发,也可以使用开源库

这里推荐一些开源的库,用起来确实不错

① Boost.Thread

这是Boost封装的多线程库,包括一些有用的同步对象,目前已经作为标准放入了C++ 11中。

② Boost.filesystem

这是Boost提供的文件系统操作库,包括常见的删除文件/夹,复制文件/夹,创建文件夹等操作,只是该库采用异常处理处理错误信息,所以需要注意try catch

③ Boost.Asio

这是Boost提供的异步网络底层库,效率不错

④ ACE

重量级的网络库,封装了常见的网络通信相关的设计模式,如果需要开发大型的网络通信平台,可以考虑使用该库。

4、编译环境不一致问题

有了VC的出现,编译问题变得很简单,因为微软做了大量的底层工作,所以门槛很低,所以Windows下的编译问题可以忽略不计。

可是到了Linux下,编译环境的使用是需要用户了解相关的脚边知识的,所以无形中加大了Linux下大规模软件的构建门槛。

因为在Linux下需要做的更多,所以这里先说说Linux下编译环境相关的一些知识。

① make & makefile

这是基本的编译工具,很多高级的构建工具多基于此。

Make其实是基于依赖的强大构建系统,主要包括三要素:依赖,目标,策略

目标即想要生成的对象。

依赖即生成该对象所需要的其他模块。

策略即如何生成该对象的方法。

Make的使用说简单也简单,说复杂也复杂,这里难以展开,有兴趣的朋友可以搜索相关技术文章。

② CMake

这里就不提及automake & autoconf了,没用过也不太喜欢用。

CMake可以认为是automake的替代品,她通过内建的脚本语言描述构建策略,简化了对对底层命令的直接操作,而且抽象了构建模式,比如,要构建一个可执行程序,需要告诉她的只有编译该可执行文件所需要的源代码文件,她会帮你完成源代码分析,并构造出依赖树,最后调用适当的编译器完成构建。

同时,CMake还具有很高的自定义能力,强大的构建模式完全不影响你按需修改自己的构建策略。

最后还有一点,那就是CMake是跨平台的,不同平台使用同一CMake脚本,针对不同平台她会调用相关的编译器,当然如果你有多个编译器,也是可以自由指定的。

CMake是KDE的基础构建工具,不熟悉CMake,我想KDE应该了解吧^_^。

③ SCons

SCons是用python编写又一跨平台构建工具,她完全替换了make系统,而且构建脚本语言就是python语言,所以用起来确实很不错,这里也顺便推荐一下。考虑到SCons是基于python的脚本语言构建的,所以针对大型系统的构建效率如何就不太确定了,所以使用前最好先评估一下系统规模。


文章作者: 2356
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 2356 !