跨平台的代码最好是在写的时候就已兼顾到多平台,即编写和调试分别在两个平台上同时进行。如果是先在一个平台开发后再来做移植,工作量可能会大很多。这种移植可能会用到很多重构方法,假如你没有很好的单元测试流程,那么大规模的重构将很有可能引入 bug。

在两个平台同时开发并不困难。首先,你最好能找到在这些平台都可以使用的工具,vim+makefile 是个不错的选择,缺点是 gdb 的调试不是很方便。因此我通常会创建一个 “_IDE” 的目录,里面主要放一些工程文件(vs 和 xcode 的),使用这些 IDE 调试会方便不少。

跨平台开发选择一门跨平台的语言是必须的。像 java 或 python 这类可以自成一派的语言,几乎不用考虑不同平台的差异。这里主要讨论的是 C/C++,它本身是非常具有可移植性的,只是由于一些历史原因,它的标准还没有完全普及。庆幸的是,作为一门可以直接跟系统打交道的语言,差异大多在不同系统提供的 api 上。

1. 操作系统扩展的 C 标准函数

文件 I/O 操作是比较有代表性的,最初 C 的标准库文件寻址最大是 4G,现在已有超过 4G 的文件,所以文件寻址从32位变成了64位,windwos 上有了这一批 api 出现。

  1. _ftelli64
  2. _lseeki64

这些带下划线的都不可移植的。像这种功能不变,函数名有变的情况,通常我们直接用宏的方式重新命名。

  1. #ifdef WIN32
  2. #define _LSEEK _lseeki64
  3. #else
  4. #define _LSEEK lseek
  5. #endif

另一些函数是被增强了。比如微软有很多安全版本的字符串处理函数。如果确实需要,最好显示地调用,而不是采用安全模板重载 (Secure Template Overloads)。

2. 自己扩展 C 标准库

广泛使用的 C 标准是 C89,一些现在常用的函数并有包含其中,比如 bool 类型。于是多数人会自己定义一个 BOOL 类型的 TRUE 和 FALSE。

  1. #ifdef TRUE
  2. #undef TRUE
  3. #define TRUE 1
  4. #endif
  5. #ifdef FALSE
  6. #undef FALSE
  7. #define FALSE 0
  8. #endif

TRUE 和 FALSE 是非常容易使用到的名字,先判断是否已经定义。另外,这类定义最好不要被其它人可见,因为大部分系统 api 都以返回0表示成功,如果你返回 TRUE 很有可能被别人认为是返回0.

3. CPU 对 C 的影响

这种影响主要是 CPU 总线位数和对齐方式。
  int 这种 C 标准类型,在 16 位和 32 位上长度是不一样的,64 位在编译器上表示不同。解决这类问题,我们通常是给这些类型重新命名。

  1. #ifdef WIN32
  2. typdef __int64 int64_t
  3. #else
  4. typdef long long int64_t
  5. #endif

这些重定义一般都是放在 stdint.h 中。这个在*nix 世界中已成为标准,最近的 VS2010 也加入了这个头文件。

对齐方式情况比较复杂一些,但它通常不会影响计算,会影响到写入磁盘和内存里读数据。操作内存,只要不是把指针强制转换,一般不会有什么问题。而如果程序在一个小端对齐的环境中写的文件,再由大端对齐中读取,这样就会出现错误的结果。
  通常,我们会规定文件上的对齐方式,然后在程序中再做判断。比如:

  1. int flipEndian(int x)
  2. {
  3. #if defined (__ppc__) || defined (__ppc64__)
  4. int y = x;
  5. ... do somthing
  6. x = y;
  7. #endif
  8. return x;
  9. }

在写入和读取的地方都应用这些函数,就可以比较轻松的应付了。

4. 不同系统提供的 api 不同

api 是操作系统自己的规范,当有两种及其以上的规范存在时,我们可以选择其中的一种规范(自己再实现一个规范当然不错,但没必要再把问题复杂化)。
  比如我们需要让线程休眠几秒种,windows 和 bsd 上都有 sleep 函数,它们不仅写法有点差异,参数类型也不一样。这时我们只需要选择一种标准即可。

  1. #ifdef WIN32
  2. #include <Windows.h>
  3. #define sleep(t) Sleep((t)*1000)
  4. #else
  5. #include <unistd.h>
  6. #endif

我个人比较倾向于用 bsd 那一套,因为它们大多遵守 POSIX 规范,更通用一些。

有些 api 比较复杂,用宏定义做不了,这时可以用 inline 实现更为复杂的表达。比如下面的

  1. #ifdef WIN32
  2. typedef void* pthread_t;
  3. typedef void* pthread_attr_t;
  4. typedef void *(WINAPI* thread_proc_t)(void *);
  5. __inline int pthread_create(pthread_t * __restrict pHandle,
  6. const pthread_attr_t * __restrict attr,
  7. thread_proc_t threadStart,
  8. void* arg)
  9. {
  10. DWORD tid;
  11. *pHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)threadStart, arg, CREATE_SUSPENDED, &tid);
  12. return tid;
  13. }
  14. #else
  15. #define WINAPI
  16. #endif

这种移植是以牺牲少部份功能为代价的,要确保你用到的是两个平台功能的交集。

5. 分开编译移植

有时不同平台差异太大,上面这些小打小闹已经不能满足需要,是时候动点大手术了。
 通常,我们会声明一个共同的头文件,然后在不同平台有不同的实现。为了方便管理,通常会写在不同文件。比如同一个 util.h,然后创建 win 和 mac 的两个目录,不同平台的 util.c 的实现放在对应的目录中即可。
一些设计模式可以派上用场。比如代理模式能减少一些冗余代码,工厂模式可以让你创建对象时更轻松一点。针对不同的需求,封装粒度也会不同。

6. 第三方工具

boost 是一个非常好用的库。它的社区很活跃,质量上乘,而且也是跨平台的。cygwin 也是一个非常好用的产品,它在 windows 上模拟 unix 上的接口,几乎不用做任何修改就可以编译。缺点是必须带上一个额外的动态库。类似的产品还有很多。

跨平台开发还有很多细节也需要注意,比如源代码文件的编码。一般使用 utf-8 最好。很多 IDE 对 UTF-8 编码的识别不好,可以加上 BOM 来帮助识别。如果你直接用 VS 或 Xcode 写,默认方式下在另一编辑器里,中文字符都会显示为乱码,而 gvim 在这方面就好很多。另外,文件名最好用全小写。

上面总结的是底层代码,如果要移植 C/C++ 的界面,先找到一套跨平台的界面框架吧,否则这基本上是不可能变成的任务。

https://blog.csdn.net/ani_di/article/details/7531532?spm=1001.2101.3001.6650.3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3-7531532-blog-50814543.pc_relevant_multi_platform_whitelistv2