1、从 UNIX 到 WINDOWS NT 移植 C+WIN32 是 WINDOWS NT 平台上应用程序的主要编程接口(API),它支持安全性、国际化以及 32 位内存模式,本文着重讨论利用 WIN32 API 将 C+程序从 UNIX 平台移植到WINDOWS NT 平台上的一些要点和步骤。WINDOWS NT 是一个完全的 32 位系统,它具有所有的标准 UNIX 特性,例如网络通信、图形用户界面等等。这两个系统有许多相同或类似的基本概念,但也存在着很明显的不同点。NT 与 UNIX 之间跨平台移植的一些要点一个 UNIX 应用程序移植到 NT 平台上可能简单到只须重新编译并运行,也可能复杂
2、到需要整个应用程序的重新设计编码,工作量的大小取决于应用程序本身的平台相关性即可移植特性。同时,移植的效果也取决于该应用是否用到底层操作系统服务或调用。如果该应用只使用基本的 C 运行库,移植是很简单的,否则就会或多或少涉及到重新编码及环境重新设置问题。按照模块化和可移植编码机制设计出来的应用是易于移植的,因为整个程序被划分为逻辑块,硬件与操作系统相关部分将被隔离,这就大大方便了跨平台移植工作。1.关于预编译处理可以通过#ifdef 预编译指令来区别编译平台相关的程序码,见下例:#ifdef unixint dfile; /*file descriptor*/#endif#ifdef-MSC-
3、VERHANDLE dfile; /*file handle*/#endif2.关于 C 运行库在大多数情况下,UNIX 和 NT 平台的 C 库函数实现没有或只有很细微的差别,用户可以不必深究,但有一些区别是应当重视的。NT 平台下,有部分库函数带有前导下划线。例如 UNIX 平台下的函数strdup()在移植到 N 时应取代以-strdup()。当为读写打开一个二进制文件时,在 NT 平台下必须指明文件类型,从而在 UNIX环境中的下列调用:fd=fopen(filename,“w“);必须改为:fd=fopen(filename,“wb“);3.关于创建应用UNIX 环境下有各种版本的
4、make 实用程序来完成 Build 的功能。WINDOWS NT SK 有一个命令行方式的 nmake utility,UNIX 和 NT 环境下的 makefile 文件不尽相同,在移植过程中,部分命令语法、编译选项及路径名必须做相应改动。以下列出一些最常见的对 makefile 文件的变动:定义资源目录路径名的宏;定义头文件及包含文件路径名的宏;相应命令参数所作的一些改动;对文件名后缀的改动以匹配 NT 特性。4.关于文件系统UNIX 和 NT 都支持几种格式的文件系统,对其提供了一致的存取机制。两个系统都把文件视为顺序字符序列,并可以定位在该序列的任意位置。两个系统均提供了目录树式的层
5、次式文件定位方式,但也有一些区别:WINDOWS NT 中使用逻辑盘符的概念,而 UNIX 下只有一个根目录 root(/)。文件命令方式有所不同,WINDOWS NT 对文件名中的特殊字符有更为严格的规定,以下字符不允许出现在文件名中: /,“,:,|,以及 ASII 字符 0 到 32WINDOWS NT 的文件操作中文件名大小写不敏感,而 UNIX 下则是大小写敏感的。WINDOWS NT 使用反斜杠,而 UNIX 使用正斜杠作为路径分隔符(这个问题下文还要讨论。WINDOWS NT 的 NTFS 文件格式提供了文件存取控制表(ACL),这是对 UNIX 文件系统权限的能力扩充。5.关于
6、句柄WINDOWS NT 使用句柄(handle)来代替 UNIX 中的文件描述符,但在 NT 中句柄不局限于文件标识。WIN32 用句柄来存取、操纵几乎所有类型的对象,包括打开文件、进程、线程、信号量、事件、管道、通信设备等等。句柄可通过下列途径获得:显示创建一个对象打开一个已命名对象从父进程继承得到复制一个句柄用于另一个进程6.关于网络UNIX 网络一般指 TCP/IP 协议及各种工具,以及 socket 接口,WINDOWS NT 支持TCP/IP 及许多通用应用。WINDOWS NT 自身的 Windows Socket 接口基于Berkeley UNIX(BSD 4.3)Socket
7、s。它支持 UNIX 应用,且允许 NT 系统加入广域 Internet网。 使用 Windows Sockets 与使用 Berkeley Sockets 没什么不同,流方式和数据包方式都被支持,遵循创建 Socket、绑定地址、监听或初始化连接、发送或接收数据、关闭连接一个一般过程。但在 NT 环境下必须使用 WSAStartup(),WSAcleanup()来初始化 Windos Socket DLL 以及断开连接。API 函数 socket()返回一个 socket handle,而不是一个整数,因为 Windows Socke ts 使用句柄而不是文件描述符,故不能使用read()或
8、 write(),但可以使用 eadfile()和 Write file()。一个实例笔者近日参与了一个 C+应用程序从 UNIX 平台下向 WINDOWS NT 平台移植的项目,原先的 UNIX 版本是在 SunSparc 工作站的 UNIX 环境下运行通过的,我们在移植过程中采用 WINDOWS NT3. 51 环境下的 Visual C+4.2 版本。1.预编译处理我们的项目涉及到大量 UNIX 和 NT 系统之间,以及 NT 系统自身相互之间的通信和数据交换,所以我们希望通信部分的代码段能同时在 UNIX 和 NT 平台下运行,另外我们还需要一个具有跨平台特性的代码段。故针对不同的平台
9、采用相应的预编译指令是一个好方法。例如,在移植过程中,我们希望充分利用 WINDOWS 平台下VC+MFC的强大功能,同时保留 UNIX 环境下系统级的调用,故采用以下形式的预编译处理: #if defined(WIN32)#include#include#else#include#include#endif此外,因为 UNIX 和 NT 有着不同的 API 接口,故在使用这些 API 调用时也应使用预编译以满足跨平台特性。2.运行库不一致所作的调整因为原版本用的是 SunSparc UNIX 环境下的 gcc 编译器,而移植版本用的是NT 的VC+编译器,所用的 C 运行库也不同,故在编译过
10、程因不一致而导致的警告和错误信息,必须作相应修改,下面略举数例:例 1:原程序中有枚举型定义:enum BOOLFalse=0,True=1事实上,在 NT 环境下,头文件 windef.h 中已有下列定义:typedef int BOOL;在 afx.h 中也有以下定义:#define TRUE 1#define FALSE 0若保留原程序中的枚举型定义,则与 WINDOWS 自身的定义相冲突,故移植版本应去掉该定义,而在原程序中使用该枚举变量时用 TRUE 和 FALSE 取代 True 和 False。例 2:原程序中调用的部分 UNIX 环境下 C 库函数名称与 NT 环境下相关的函数
11、或实现有差异。如,原程序中有以下声明:extem double log2(double); /取以 2 为底的对数extem double rint(double); /截尾函数NT 平台上的 VC+并未提供类似于 log2()库函数,故在移植进程中应当以 log(x)/log2()来代替原先的 log2(x)调用。VC+提供的截尾函数不是 rint,而是 floor(),故应把上面的第二句声明改为:extem double floor(double);例 3:UNIX 环境上的 gcc 编译器有一个奇怪的特性,即通过对象的指针引用对象成员时,依然用“运算符而不是“-“运算符,这一特性在 NT
12、 环境下显然是不能照搬的。故在移植过程中相应的“均须改为“-“。3.字节序问题在使用指针运算为变量的某些字节寻址,或读写二进制数的应用程序时,要注意字节排序的不同,有两种字节排序技术:little endian(低地址字低字节序)big endian(低地址高字节序)不同的 CPU 上运行不同的操作系统,字节序也是不同的,参见下表:处理器 操作系统 字节排序Alpha 全部 little endianHP-PA NT little endianHP-PA UNIX big endianIntel x86 全部 Little endianMotorola 680x() 全部 big endian
13、MIPS NT little endianMIPS UNIX big endianPowerPC NT little endianPowerPC 非 NT big endianRS/6000 UNIX big endianSPARC UNIX big endianlittle endian 字节序为低地址低字节序,下面的结构表示了一个 16 位数据的两个字节。bits:07 06 05 04 03 02 01 00byte0bits:15 14 13 12 11 10 09 08byte 1big endim 则为低地址高字节序,如下所示:bits:15 14 13 12 11 10 09 0
14、8byte 0bits:07 06 05 04 03 02 01 00byte 1我们的项目中牵涉到许多客户机和服务器之间经由 TCP/IP 协议进行的网络通信。Inten et 定义的标准字节序为低地址高字节库,必须在网络传输之前将所有整型数据转换成网络字节序,还必须把网络上收到的数据转化为本机所要求的字节序,由于我们的移植工作同时牵涉到 UNIX 和 NT 平台及彼此之间的通信,故在作字节序处理时一定要小心,以免出现错误的数据传输和处理。Winsock 规范提供了完成本机和网络之间的字节序转换的实用服务函数,如下表所示:Winsock 函数 作用htonl 把 32 位值从主机字节序转为网
15、络字节序htons 把 16 位值从主机字节序转为网络字节序ntolhl 把 32 位值从网络字节序转为主机字节序ntohs 把 16 位值从网络字节序转为主机字节序4.文件系统尽管 UNIX 和 WINDOWS NT 都采用树形目录的文件系统管理方式,但二者还是有一些区别的,UNIX 下只有一个根目录 root,没有 NT 环境下逻辑盘符的概念。为了使移植后的程序能在不同驱动器符之间切换,就必须用到 NT 自身特性的一些系统调用,以下的函数调用可以获得 NT 环境下逻辑驱动器信息:DWORD GetLogicDrives(Void)该函数只返回一个 32 位的值,其中每一位代表某一个逻辑驱动
16、器存在,例如,如果系统有驱动器 A,第 0 位将被设置,若系统有驱动器 Z,第 25 位将被设置。此外,NT 的目录路径分隔符为“而 UNIX 为“/“,这为移植过程中的界面处理带来了一些麻烦,必须先对主机的操作系统作出判定,然后再对路径字串作相应的改动。幸运的是,NT 环境下的文件系统功能调用也承认“/“作为路径分隔符,只是在界面输出上要作一些改动,把路径名中的“/“重新换为“。当然,NT 和 UNIX 平台的文件系统中获得目录,创建、删除目录,同步及异步读写文件、拷贝、更名文件、打开和关闭文件、搜寻文件等操作均有自身的 API 调用接口,不尽相同,移植过程中要注意作相应改动,这里不再赘述。
17、5.字符集UNIX 平台下使用 ASCII 字符集,这样一个 8 位的单字节字符集(SBCS)是足够一些欧美国家的字符集所使用,然而一些非欧洲国家的字符集,如日语,它所有的字符不能用一个字节的编码全部表示出来。因此,我们移植进程中用到了多字节字符集(MBCS)和宽字节字符集(Unicd e)。一个多字节字符集的组成形式可以是一个单字节字符,也可以产生双字节字符,因此在一个多字节字串中包含有单字节字符和多字节字符,在使用过程中可以按单字节字符一样来处理,Microsoft 运行库会自动完成分析字串的类属文本映射。宽字节字符集(Unicode)是双字节字符码集,每一个宽字符总是用固定的 16位 2
18、进制数表示,它可以使那些用多种字符集编写的程序容易实现。由于国际市场的多样性,Microsoft 运行库为一些数据类型、例程和其他对象提供了 Mir osoft 专用的类属文本映射功能,这些映射对象在 TCHAR.H 头文件中定义,使用这些对象名书写类属代码,再根据#define 语句预定义的字符集类型(ASCII、MBCS 或 Unicode),将类属代码编译为指定字符集代码,这无疑大大方便了程序开发。6.WINDOWS NT 的安全机制我们在移植过程中,尽量包括了原先 UNIX 环境下的用户安全体系特性,如登录验证、用户个人目录等设置,由于 WINDOWS NT 和 UNIX 的安全特征有
19、所不同,故有几点在移植过程中须加以注意。例如,在用户登录时,我们用 NT 环境下的登录函数 LogonUser()取代了 UNIX 下的getpwna( ),但执行 LogonUser()的进程必须具有 NT 安全体系特有的 SE-TCB-NAME 特权,而一般情况下 T 用户是没有这个特权的(即使以系统管理员身份登录也不例外),所以必须事先在域管理工具中为运行该进程的用户授予该项特权,有些特权也可在进程执行过程中动态地授予和回收,可参看 LookupPrivetedge Value()和AdjustTokenPriviledges()函数。此外,为了达到类似于 UNIX 的用户个人配置,不同
20、的用户登录成功后应具有自己的用户目录,这一功能可通过以下几个系统调用来实现:LookupacountName();/寻找用户帐户名NetGetDCname(); /对特定域搜寻域控制器计算机名NetUserGetInfo();/在域控制器中寻找用户信息在作了以上调用后,便可从所得到的登录用户信息中找到用户在登录域的宿主目录,进而切换到该目录。7.环境设置因为我们移植的应用程序是一个很大的项目,同时涉及到窗口程序、控制台程序、静态连接库和动态连接库,故开发环境及编译选项的设置是非常重要的,例如,因为程序中用到多线程,故编译连接时必须指定/MDd 或/MTd 选项。又如因为我们用到多字符集,故必须预定义常量 WIN32 和 MBCS。此外,因为该应用程序是由好几个模块组成的,故编译连接单个模块时,必须指明外部的连接,如 DLL 或 LIB 文件。同时还要注意头文件、资源文件、调试文件、外部连接文件的路径一定要标识清楚。移植的过程中,一般是先建立一个和原先 UNIX 版本对应的工程文件,如窗口应用程序、控制台应用程序、动态连接库等,然后把原始文件加入这个工程项目中,按照上述的几点,耐心地逐步调试并排列。