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