设为首页 加入收藏

TOP

程序移植与宏定义(二)
2014-11-23 23:21:05 来源: 作者: 【 】 浏览:4
Tags:程序 移植 定义
了很好的分离。
有两点值得注意:
第一点,platform_specific.hpp中没有用到#elif,而是用了独立的#ifdef #endif 块。这样做的目的是为了支持下面这样的拓扑结构:
#ifdef WIN32
#include “win32_specific.hpp”
#endif
#ifdef WINCE
#include “wince_specific.hpp”
#endif
#ifdef UNIX
#include “unix_spefic.hpp”
#endif
#ifdef SOLARIS
#include “solaris_specific.hpp”
#endif
#ifdef AIX
#include “aix_specific.hpp”
#endif

WIN32和WINCE不冲突,WINCE是特殊的WIN32;Solaris和AIX是两种特殊的UNIX,和UNIX也不冲突。如果用了#elif就无法同时#include,但用上面这种拓扑结构就可以做到,而且可以把各个UNIX平台都一样的东西实现在unix_specific.hpp中,而把Solaris和AIX有差异的东西实现在solaris_specific.hpp和aix_specific.hpp 中,实现进一步的平台细分。
第二点,win32_specific.hpp、unix_specific.hpp等只能用来封装平台相关的API,不能包含过多的平台独立逻辑。
下面举一个反例:
在unix_specific.hpp 中:
int main()
{
// 做平台无关的事情
int h = dlopen(“library”, RTLD_LAZY);
// 继续做平台无关的事情
}
在win32_specific.hpp 中:
int main()
{
// 做平台无关的事情
HMODULE h = LoadLibrary(“library”);
// 继续做平台无关的事情
}

这样做是很不好的。有一部分平台无关代码会被拷贝粘贴,重复出现在了两个地方。拷贝粘贴是编程之大忌。所以一定要注意,那些封装函数只能是很简单的只有一两行的inline 函数,而且不能出现平台独立的代码。
采用这种源文件拓扑结构,可以极大地提高软件的可移植性,而且给编写第一个平台版本带来的麻烦也不大。如果你的开发策略是各个平台同步开发,那么这样做可以让各个平台以及跨平台模块的开发者毫不冲突地工作于不同的源代码文件;如果你的开发策略是先全力发布一个平台的版本,然后移植到另一个平台,那么用这样的源代码结构同样可以给你带来极大的好处:假设第一个版本是Windows 的,稍候发布Linux 版本,那么一开始只有main.
cpp(在这里代表所有的平台独立代码)和win32_specific.hpp。移植的时候只要照着win32_specific.hpp的实现,编写一个linux_specific.hpp 即可。
维护起来也很省心,以后出升级版本或者出patch/servicepack,都只需要在一棵代码树上工作,而没有很多合并修改分支的烦恼。而且还有一个好处是,如果一个bug只在某个平台出现而在其他平台没有,那么找bug基本上只要在那个平台对应的os_specific.hpp中看即可,这是分离关注焦点带来的好处。
正如我前面说过的,平台除了指操作系统,也可以指更广泛的概念,比如中间件或者你依赖的某个第三方库。只要你对平台的依赖是局部性的,而非全局性(比如对框架的依赖),那么这种方法都可适用。我在这里选择了用#ifdef和#include配合来选择性地包含和编译平台相关代码。这是通用性最好也最省事的做法,C和C++都支持,所有平台上的编译器都支持。当然,还有其他的办法,比如配合使用namespace 定义、using namespace导入语句、模板的实例化(把操作系统类型作为一个模板参数),也能做到。对预编译器和#号深恶痛绝
的朋友不妨可以试试。
这样的文件结构也可以用于makefile。编译时用make -e OS=YOURTARGETOS [其他参数]来选择性地为某个平台进行构建。其中makefile 应包含这样的内容:
include $(ROOT)/buildenv/default.inc #平台独立的构建信息
include $(ROOT)/buildenv/$(OS).inc #平台相关的构建信息,比如不同平台
#上不同编译器的参数定义

因为包含次序在后的宏定义可以覆盖前面的,所以default.inc中还可以为各平台的编译器提供缺省值(比如把编译器缺省定义成cc,有的平台可以覆盖成gcc或者xlC等等;优化参数在default.inc中缺省定义成-O3,在支持更高优化程度的平台.inc中覆盖成-O5,诸如此类)。宏除了覆盖的话,也可以连接。关于makefile的写法在此限于篇幅就不详述了。事实上还有自动工具(autoconf、autoheader、automake 等)同GNU make配套,可以生成平台相关的文件并进行平台相关构建(具体用法可以通过Google查找文档),但我觉得很多情况下杀鸡不需要用牛刀除了整体结构,还有很多细节需要注意。比如文件路径分隔符“/”和“\”的不同(boost::path很好地封装了这个不同),这个操作系统的文件系统是否区分大小写,Big Endian和Little Endian的区分,不同平台上字长的不同,以及不同平台/编译器的缺省对齐方式的不同,等等。另外,要注意一些C++ 编译器提供的API 其实扩展了ANSI或者ISO 的标准,比如SGI STL 中的hash_map、hash_set 和rope,还有某些C库提供的snprintf之类函数,这些API 其实不是跨平台的,应避免使用(比如S/390 上的C 库就不带snprintf 函数,绝大部分STL实现都没有hash_map、hash_set 和rope)。不过如果你觉得使用它们会带来很大方便,也可以用,只是你不得不在不支持这些API的平台的os_specific.hpp 中自己实现snprintf 或者hash_map、rope等等。篇幅所限,这些细节就不展开说了。

最后,必须提到,软件应尽可能地具有良好的逻辑和物理设计,这一点非常重要。移植到一个不同的平台,本质上是对软件做修改。设计得越好的软件修改起来越容易。糟糕的设计会导致软件逻辑不清、代码都纠缠在一起,做一点点改动都会牵一发而动全身。这样的软件是很难移植的。而设计得好的软件,对局部做改动不会影响到其余部分,而且一个改动只需要做一次,不需要做全局的查找且替换还担心遗漏一处就造成bug,这样的软件移植起来会很省心。

----------------------------------------------------------------------------------------
编写可移植C/C++程序的要点
1.分层设计,隔离平台相关的代码。就像可测试性一样,可移植性也要从设计抓起。一般来说,最上层和最下层都不具有良好的可移植性。最上层是GUI,大多数GUI都不是跨平台的,如Win32 SDK和MFC。最下层是操作系统A ...
1.分层设计,隔离平台相关的代码。就像可测试性一样,可移植性也要从设计抓起。一般来说,最上层和最下层都不具有良好的可移植性。最上层是GUI,大多数GUI都不是

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇顺序表的转置 下一篇C语言中sizeof与strlen区别2

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: