您的位置:首页 > 运维架构 > Linux

Windows下程序向Linux下移植

2014-10-11 15:22 190 查看
一、问题的提出

在程序员中有这样一个说法,若一个程序不能移植到 Linux 下,那这个程序 将看不到未来。 由于 Linux 操作系统源码公开是的,开发库等辅助工具都是源码公开的,这 样就减少了程序的不可预知性,而且出现错误可以大家一起修正、完善,而 Windows 平台下所有的操作系统 Api 就给了个接口, 即使出现莫名奇妙的错误也 只能望着接口兴叹了。 再加上 Linux 操作系统本身和它上面的许多工具软件是免 费的,更是吸引了更多的公司和程序开发人员将程序开发转向 Linux。 在程序跨平台的移植过程中,将存在操作系统
API 的不同、文件名大小写识别 不同、路径分隔符不同、不同开发平台数据类型的不一致等较一般性的问题。对 于这些一般性的问题怎样很好的来解决呢?Linux 下的工程都是使用 makefile 文 件来管理的, 怎样编写出相应的 makefile 文件呢?这些问题都是本文后面将要阐 述的。

二、解决思路

本文撰写的目的是为了提供 Windows 平台上程序向 Linux 下移植所碰到的一 些典型问题及相应的解决方法,供要进行程序平台移植的同仁参考之用。 文中还描述了使用 pwlib 库时 makefile 文件的编写方法,对于使用了 pwlib 库进行开发的程序能快捷的建立 makefile 工程文件,避免了自己手动书写 makefile 的繁杂工作。 特别是<3.2.6 可以移植的数据类型>一节中对于不同开发平台数据类型的不一致 提出了一个简捷通用的解决方法, 不用修改源程序中任何代码即可在 Linux 下使
用 Windows 开发平台上的一些数据类型。

三、实践情况 3.1.Makefile 的编写

Linux 下一般都是使用 make 工具来管理和编译一个大的开发工程的所有源 文件,make 命令执行时,需要一个 Makefile 文件,以告诉 make 命令需要怎么 样的去编译和链接程序,makefile 关系到了整个工程的编译规则。一个工程中的 源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile 定义 了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件

需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚 本一样,其中也可以执行操作系统的命令。在 Windows 的一些 IDE 如 VC 中将 自动帮你生成相应的 makefile,所有这些都是透明的,但在 Linux 下你就不能不 自己写 makefile 了,会不会写 makefile,从一个侧面说明了一个人是否具备完成 大型工程的能力。 make 工具采用增量编译的方式, 每次只编译被改动过确实需要编译的源文件, 每次编译时 make 工具将自动判断那些源文件需要重新编译,当一个工程很大而
又只改动了很少的几个源文件,这将节省很多时间。 具体 makefile 文件的编写规则可以查看 make 的 man 和 info 文档(在 Linux 命令 行方式下输入:man make 或 info make)。makefile 文件的编写规则很多,重要的 是怎样使用最简单的方式写出我们自己需要的 makefile 文件。 网上也有很多介绍资料,网上有一篇很好的介绍 makefile 文件编写的文章: 下文将着重介绍使用 pwlib 开发库的工程的 makefile 的编写,但对于其它工程只 需将 common.mak
文件中对 pwlib 库进行编译的脚本去掉也可适用。 3.1.1 使用 pwlib 开发库的工程的 makefile 的编写 PWLib 是 Portable Windows Library 的缩写,翻译为轻便的 Windows 类 库.PWLib 采用 C++编写,设计初衷是为了能让 Openh323 在 Windows 和 Unix 的 X-Windows 下运行, 不过随着一步步的完善 PWLib 已经被跨平台的程序所广泛 采用。 查看 Pwlib 的主目录下/samples/hello_world/目录下例子程序的
makefile 文件 可以发现该 Makefile 文件内容如下:

# Simple makefile for the hello world program PROG = hello

SOURCES = hello.cxx ifndef PWLIBDIR PWLIBDIR=$(HOME)/pwlib endif include $(PWLIBDIR)/make/ptlib.mak # End of Makefile

实际上就是使用了 Pwlib 库的 ptlib.mak 文件,编译时需要的头文件,相应的 编译选项都在 ptlib.mak 文件中设置好了。 我们只需在该 makefile 文件所在目录下,命令行输入 make all 命令即可编译 出程序的 Release 版本和 Debug 版本,它们分别放在当前目录的 obj_linux_x86_r 和 obj_linux_x86_d 子目录下。 下面对该 makefile 中的内容进行解释:

? ?

PROG 变量为编译出来的程序名称。 SOURCES 变量存储的为本工程要进行编译和链接的源文件,当有多个

源文件时可以用空格隔开, 虽然文件名可以带上路径,但路径在 SOURCES 变量 中不起作用,实际编译时对于每个文件它将截掉最后一个”/”字符前面的所有内 容只保留文件名。 ? PWLIBDIR 为 pwlib 的安装目录, 需要设置该环境变量(若要系统每次重 启都自动设置好该环境变量则将该环境变量的设置放入/etc/profile 文件中), 若没 设置好则自动以”用户主目录/pwlib”作为 pwlib 的安装目录。 3.1.2 深入分析 ptlib.mak 文件 分析 ptlib.mak 文件,它的内容如下;
ifndef PWLIBDIR PWLIBDIR=$(HOME)/pwlib endif include $(PWLIBDIR)/make/unix.mak include $(PWLIBDIR)/make/common.mak 也就是 ptlib.mak 包含了另两个文件 unix.mak 和 common.mak 文件,其中 unix.mak 为定义编译选项变量的文件,编译规则放在 common.mak 文件中。 通过对这两个文件的分析,归纳出一些我们写 makefile 文件要用到的一些变 量,列出如下:
? ? ? ? STDCCFLAGS:所有头文件的 include 目录编译选项、预编译宏定义选 LDFLAGS:共享库、静态库搜索目录设置放入该变量中。 LDLIBS:链接时要用的库的设置放入该变量中。 VPATH_CXX:*.cxx 源文件的搜索路径放入该变量中,多个目录以空 项、警告选项、优化选项都放在该变量中,各编译选项之间用空格分开。

格分开,编译时将首先在 makefile 所在目录查找相应源文件,没找到则按照 VPATH_CXX 中的路径设置进行查找。 ? VPATH_C: *.c 源文件的搜索路径放入该变量中, 多个目录以空格分开, 文件查找顺序和 VPATH_CXX 变量的类似。 3.1.3 加入新的编译规则 common.mak 文件中只对*.c 和*.cxx 的源文件定义了编译规则,而一般 windows 下程序大多都使用了.cpp 来作为 C++源文件的后缀。 怎样加入对于.cpp 后缀的源文件的编译规则呢,这需要修改 pwlib
的 common.mak 文件,具体步骤如下:

1.加入对于.cpp 文件的搜索目录设置 在 vpath %.cxx $(VPATH_CXX)语句后面加入如下语句: vpath %.cpp $(VPATH_CXX) 2.加入对于.cpp 文件的编译规则 在$(OBJDIR)/%.o : %.cxx 语句的前面加入如下语句: $(OBJDIR)/%.o : %.cpp @if [ ! -d $(OBJDIR) ] ; then mkdir -p $(OBJDIR) ; fi $(CPLUS) $(STDCCFLAGS) $(OPTCCFLAGS) $(CFLAGS)
$(STDCXXFLAGS) -x c++ -c $< -o $@ 3.加入对于.cpp 文件的.o 文件(目标代码文件)的命名规则 在 SRC_OBJS := $(SRC_OBJS:.cxx=.o)语句后面加入如下语句 SRC_OBJS := $(SRC_OBJS:.cpp=.o) 4.加入对于.cpp 文件的.dep 文件(依赖文件)的命名规则 在 SRC_DEPS := $(SRC_DEPS:.cxx=.dep)语句后面加入如下语句 SRC_DEPS := $(SRC_DEPS:.cpp=.dep)
5.加入对于.cpp 文件生成.dep 文件的生成规则,加入如下语句: 在$(DEPDIR)/%.dep : %.cxx 语句前面加入如下语句 $
4000
(DEPDIR)/%.dep : %.cpp @if [ ! -d $(DEPDIR) ] ; then mkdir -p $(DEPDIR) ; fi @printf %s $(OBJDIR) > $@ $(CPLUS) $(STDCCFLAGS:-g=) -M $< >> $@ 3.1.4 一个 makefile 范例

3.2.程序的移植

进行程序移植的过程中碰到的问题较多,但大都主要集中在文件名大小写、 路径分隔符、数据类型等方面。 3.2.1Linux 和 Windows 操作系统 API 差异 Windows 下基于 MFC 的 API、 基于消息的 API、 基于注册表的 API 等在 Linux 下都是没有的,由于文件系统的差异,和文件系统相关的 API 也是不可以移植 的。 解决方法:程序中不使用上面所列的不可移植的操作系统 API,通过使用开 源库如 PWLIB 或 ACE 中的可移植的类来实现所需的功能。 如:

SYSTEMTIME pTime;

GetLocalTime(&pTime);

//为 windows 独有的 API

sprintf(sTemp,"[%02.2d-%02.2d-%02.2d]%02.2d:%02.2d:%02.2d %s(%d)", pTime.wYear,pTime.wMonth,pTime.wDay, pTime.wHour,pTime.wMinute,pTime.wSecond,file,lineNum);

改为: 使用 pwlib 的 PTime 来实现

PTime curTime; //pwlib 中可以跨平台使用的时间类 sprintf(sTemp,"[%02.2d-%02.2d-%02.2d]%02.2d:%02.2d:%02.2d %s(%d)", curTime.GetYear(), curTime.GetMonth(), curTime.GetDay(), curTime.GetHour(), curTime.GetMinute(), curTime.GetSecond(), file, lineNum);

3.2.2 开发库函数的差异 一些函数在 Windows 操作系统的 VC 开发库中有, 但 Linux 下的 GLIB C 开 发库中没有或是名字不一样。 Windows 下有而 Linux 下开发库没有的函数,例如: itoa(int, char*, int)、

ltoa(long, char *, int)、ultoa(unsigned long, char *, int)等。

解决方法 1:通过编写相应的代码来实现该函数。 解决方法 2:使用 Linux 下含有类似功能的函数来替换,如 itoa()、ltoa()等系列 的函数都可以通过 sprintf()或 snprintf()函数来替换。 替换例子 1:

ltoa( confHistb.conflong, caTemp, 10 );

可以替换为: #ifdef WIN32 //windows ltoa( confHistb.conflong, caTemp, 10 ); #else //linux sprintf(caTemp, "%d", confHistb.conflong);

#endif 或直接用 sprintf(caTemp, "%d", confHistb.conflong);替换即可。 例 2: Windows 下 Sleep()函数对应的 Linux 下函数为 sleep()和 usleep(), 要特 别注意的是 Windows 下 Sleep()为休眠多少毫秒, 而 sleep()和 usleep()分别为休眠 多少秒和微妙, 所以替换的时候不仅要注意函数的名称不同还要注意单位的不一 致。 例 3:Windows 下 stricmp()函数在 Linux 下对应的为
strcasecmp()函数,可以 通过宏定义来区分不同平台的代码,也可以在 WINTYPES.H 文件中加入如下语 句: #define stricmp strcasecmp 通过宏替换来实现。 3.2.3Linux 下对文件名大小写敏感 Windows 下由于操作系统对文件名大小写不明感,#include 语句中文件名的 大小写均可以,而 Linux 操作系统是对文件名大小写敏感的,#include 语句中的 文件名必须和原文件名大小写一模一样才能找到。 解决方法:#include 语句中文件名和原文件名大小写不一致的全部要修改为
一致。

3.2.4Linux 下路径中各目录的分隔符只能为”/” Windows 下路径的分隔符使用”/”和“/“均可,而 Linux 下只能使用”/“来 作为路径中个目录的分隔符。 解决方法:#include 语句中路径的分隔符全部使用“/”。 数据类型 3.2.5 程序里不能使用 Windows 特有的数据类型 例如: FAR PASCAL、 HWND、 HMENU、 HFONT 等, 因为这些类型在 Linux 下无法找到替代它们的类型,必然导致程序的不可移植。 3.2.6 可以移植的数据类型

有些数据类型是可以通过类型定义来实现的, 如 CHAR、 LONG、 INT、 INT32、 FLOAT、BOOL、VOID、UCHAR、CONST、WINAPI、CALLBACK 等,这些类型 在 Windows 下的 VC 开发库中定义了,但在 Linux 下没有。 解决方法:可以通过创建一个 WINTYPES.H 的头文件,将这些类型定义放 在该文件里。 编译时加上“-include PATH/WTYPES.H”编译选项即可不用在代码中加入 任何“#include”语句而使用 WINTYPES.H 中的类型,这里的
PATH 为 WINTYPES.H 文件所在的路径。示例代码如下: typedef float FLOAT; typedef char CHAR; #define VOID #define WINAPI void __attribute__((stdcall))

#define CALLBACK __attribute__((stdcall)) 3.2.7 一些宏定义 Windows 下有而 Linux 下没有 有些宏定义如:

#define MAKEWORD(a, b) #define MAKELONG(a, b) #define LOWORD(l) #define HIWORD(l) #define LOBYTE(w) #define HIBYTE(w) ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8)) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16)) ((WORD)(l)) ((WORD)(((DWORD)(l) >> 16)
& 0xFFFF)) ((BYTE)(w)) ((BYTE)(((WORD)(w) >> 8) & 0xFF))

等在 Windows 下有,而 Linux 下没有。 解决方法:在使用到这类宏定义时将相应的宏定义放入 WINTYPES.H

文件中即可。

3.2.8 同名但结构不同的数据类型

Winows 下 struct in_addr 结构定义如下:

struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un;

};

而 Linux 下 struct in_addr 结构定义为:

struct in_addr { __u32 s_addr; }; 解决方法: 因在使用这种类型的时候不同操作系统下面的代码不一样,要使用宏 定义将不同操作系统下的代码分开。 例: #ifdef WIN32 ipAdd.S_un.S_addr = address_ip; #else ipAdd.s_addr = address_ip; #endif 宏定义 WIN32 为 Windows 下 VC 编译器自带的一个宏定义,该宏定义在 Linux 下不存在所以在 Windows 下和 Linux 下使用的代码是不同的。
3.2.9Windows 下的头文件 Linux 下名字不同 有些头文件在 Windows 下和 Linux 下名字不一样, 如 strstrea.h 在 Linux 下对 应的文件名为 sstream 或 strstream。 解决方法:当发现以后写头文件在 Linux 下找不到时,查看下是否 Linux 相 应头文件的名字不一样,是否开发库的不同版本头文件不一样,有些开发库如 STL 开发库由于不断升级会淘汰一些头文件而使用其它的头文件来进行替代。 所以 #include <strstrea.h> 修改为:
#ifdef WIN32 //windows #include <strstrea.h> #else //linux #include <sstream> #include <strstream> #endif

3.2.10Windows 和 Linux 下编译器对语言理解的差异 由于编译器的差异也导致要将不同平台下的代码要使用宏定义来区分开,如: for(int i=0; i< iSize; i++)

语句定义的变量 i 在 Windows 下该变量将在 for 语句执行完后仍有效,而在 Linux 下变量 i 只在 for 语句内部有效出了 for 语句的范围后就失效了。 解决方法:这种情况是由于编译器对语言语义上的理解不同导致的,只要看 下编译的错误信息就可以很快解决, 要注意的时要使用宏定义来包含不同平台之 间的代码。

3.2.11Linux 下编译器检查比 Windows 下 VC 的编译器检查更严格 Linux 下的编译器检查比 Windows 下更严格, 特别是类型转换检查方面, 如: char strTime=“2004/01/02 14:00:00”; PTime starttime = strTime; 在 windows 下编译可以通过,但由于 PTime 类只有 PTime(const PString & str )构

造函数,而 strTime

为 char[]类型,虽然在 Windows 下可以编译通过但在 Linux

下编译通不过。 解决方法:增加强制类型转换即可。 char strTime=“2004/01/02 14:00:00”; PTime starttime = Pstring(strTime); 这方面的代码编译错误只要看下编译的错误信息也可以很快就解决。由于是 Linux 下编译器检查比 Windows 下严格,所以只要能保证在 Linux 下编译通过 Windows 下肯定也能编译通过不用使用宏定义来包含不同平台之间的代码。

四、效果评价

以上所列移植的问题是在进行 zxms80 项目 的 CSS(会议调度模块)移植时碰 到的,CSS 模块采用 pwlib 的 ptlib,mak 文件来创建 makefile 文件,采用了前面 所列的解决方法来解决碰到的问题,整个移植过程花了一个月左右。(CSS 代码 大概 40000 行左右,使用了 Pwlib 库、ACE+TAO 库、Libodbc++库) 通过借用 pwlib 的 ptlib.mak 文件可以快捷的创建自己的 makefile 文件, 创建 出来的 makefile 简单易读。 Windows
下程序往 Linux 下移植主要就是会碰到上面列出来的一些问题,文 中为每类问题都进行了举例和提供了相应的解决方案希望对要进行程序平台移 植的同仁会有所裨益。

五、推广建议

并不是任何程序都可以轻松进行移植的,只有在设计、开发初期考虑到程序的可 移植性,使用了可移植的开发库来进行开发,尽量避免使用和平台相关的代码, 这样的程序才能快速、方便的进行移植。 文中描述的移植中碰到的问题和解决方法对于 Windows 平台下 C/C++程序向 Linux 平台移植均适用,特别是对于使用了 pwlib 库来进行开发的程序提出了快 捷的建立 makefile 工程文件的方法,并对 makefile 文件的关键部分进行了解释, 最后给出了一个 makefile 文件的完整范例。即使是没有使用
pwlib 开发库也可以 使用 pwlib 的相应 make 文件来构建自己的 makefile 文件,只是需要将相应编译 pwlib 库的那部分脚本(common.mak 文件中)屏蔽掉就可用于创建任何工程的 makefile 文件。 只要使用了可移植的开发库来开发大部分代码,移植过程还是比较顺利的,主要 是一些如文件名大小写、 路径分隔符使用不对等小问题的重复修正,若是使用了 很多和 Windows Api 相关的代码如访问注册表、 文件操作的 Api 则要费些功夫来 重写这部分代码了。 通过对程序进行移植操作,
一定更能深刻体会写代码时为什么要注意可移植性了, 不能一味的为了方便使用简单而不可移植的方法来实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows linux c