一.前言

Linux 拥有丰富各种源代码资源, 但是大部分代码在 Windows 平台情况是无法正常编译的。Windows 平台根本无法直接利用这些源代码资源。如果想要使用完整的代码,就要做移植工作。因为 C/C++ Library 的不同和其他的一些原因,移植 C/C++ 代码是一项困难的工作。本文将以一个实际的例子(Tar)来说明如何把 Linux 代码移植到 Windows 平台上。移植过程将尽量少修改代码, 以便代码的运行逻辑不会发生任何变动。保留绝大部分软件主要功能。

二.准备工作

Tar 是 Linux 平台下面一个打包工具。移植这样一个程序到 windows 平台需要做那些工作呢?

首先是一些准备工作,在 Windows 平台上面安装上 Cygwin 的最新版本,在 Cygwin 中安装好 GCC 等开发工具。 同样也需要一个 Windows 开发环境。可以使用最新版本 Visual Studio, Microsoft Visual Studio .NET 2003。从 www.gnu.org 上取得 Tar 的最新源代码,版本是 1.13。在 Cygwin 下面解开 tar-1.13.tar.gz. 源代码包。注意请不要在 Windows 下面使用 WINRAR 或者 WINZIP 来解压缩。 WINRAR 和 WINZIP 在解压缩某些 tar.gz 包的时候会有问题。使得解包之后的目录和文件出现异常。如果是源代码包将有可能不能在 Cygwin 下面正确编译。解开压缩包之后,进入 tar-1.13 目录,在当前的目录下面输入

./configure

命令,运行完毕之后,再次输入

make

命令。开始编译 tar 的 Cygwin 版本。

编译基本上不会有问题,进入 src 目录,可以看到新编译好的 Tar 程序 tar.exe。

Cygwin 是一个 API 层的 Linux 模拟环境。如果能够在 Cygwin 下面编译,运行。实际上也就是能在 Windows 下面编译和运行,只是需要有一层中间 API 模拟某些 Linux 特有的操作。简单的判断一个 Linux 程序能不能移植到 Windows 平台下面,就是看是否能在 Cygwin 下面编译源代码,并运行程序。

在 Cygwin 中编译 Tar 的源代码,判断能否移植只是其中一个原因。另外一个原因是移植代码过程中需要一个特殊的头文件 config.h。config.h 是移植过程中最重要的源代码文件。Config.h 文件并不是源代码本身的一部分。文件是在 Cygwin 下面运行”./configure” 命令时生成的。在 Cygwin 下运行”./Configure” 命令时,会根据 Cygwin 平台开发环境生成 config.h 文件。编译时也需要 config.h 文件对代码编译项进行控制。移植工作也以 config.h 文件为基础。

接下来就是构造 Windows 工程。先用 Visual Studio .NET 2003 创建一个空的工程(Project),命名为 WinTar。根据 Cygwin 中的编译输出信息,Tar 主要的代码在 Src 和 lib 两个目录中。把这两个目录复制到新工程里,并把代码加入到工程中。然后复制 Config.h 到 WinTar 工程目录下面。

准备工作基本上完成了,接着就是移植。移植过程可以分为 3 个部分。

三.第一个目标:使得 WinTar 能编译过(Compiler)

第一个目标的完成主要围绕 Config.h 来实现。Linux 下开发环境和 Windows 开发环境很大的不同是 C Library 头文件和各种类型的定义不同。而 Config.h 提供了完整编译开关来处理因为不同平台间开发环境不同带来的不同之处。现在需要手工去修改这个文件,以便 Tar 源代码能适应 Windows 平台。

首先调整各种 C Library 头文件(Header File)的包含问题。在 Config.h 中定义了很多类似 HAVE_XXXX_H。比如定义 HAVE_CONFIG_H 为 1 表示工程中可以使用 config.h。

define HAVE_MALLOC_H 1 表示可以在工程中使用 Malloc.h 头文件。通过调整这些定义值,可以去除一些 Windows 平台下面没有的头文件包含。也许其他地方还有很多头文件包含关系需要处理,但是这里的定义基本上解决了大部分的头文件包含问题。

/ Define if you have the header file. /
/ #undef HAVE_LINUX_FD_H /

/ Define if you have the header file. /

define HAVE_LOCALE_H 1

/ Define if you have the header file. /

define HAVE_MALLOC_H 1

/ Define if you have the header file. /

define HAVE_MEMORY_H 1

/ Define if you have the header file. /
/ #undef HAVE_NDIR_H /

第二步,调整各种数据类型的定义,可能在 linux 下面会有很多特殊的数据类型定义,Config.h 文件中也包含了一部分可以变动的数据类型定义项。这些定义一般都是基本数据类型的重定义。可以根据 Windows 平台下的数据类型定义情况进行修补。比如在 Cygwin 的开发环境中有个数据类型 mode_t, Visual Studio 的 C Library 中却 (作者 很土, 联系方法 jackforce at 163 dot com) 找不到这样数据类型。Tar 代码中使用了大量的 mode_t 数据类型. config.h 中提供了修改项来让开发人员自己修改 mode_t 的定义,并提示如果 mode_t 在 中没有定义的话,可以把他定义为 int 型。所以在 config.h 加上 #define mode_t int。这样 mode_t 没有定义的问题就解决了。其他的数据类型也是同样对待处理。

  • Define to `int’if doesn’t define. */

    define mode_t int

/ Define to `long’if doesn’t define. /
/ #undef off_t /

/ Define to `int’if doesn’t define. /

define pid_t int

第三步,调整各种函数定义。在 Config.h 中除了 HAVE_XXXXX_H 之外还有一种预定义,HAVE_XXXX。 这是一些可选用函数定义开关。#define HAVE_MEMSET 1 表示工程中可以使用 memset 函数。也就是说工程用到的类库中已经实现了这个函数。如果没有,那么就需要#undef HAVE_MEMSET,当然也可以自己提供这些函数。

/ Define if you have the memset function. /

define HAVE_MEMSET 1

/ Define if you have the mkdir function. /

define HAVE_MKDIR 1

/ Define if you have the mkfifo function. /

define HAVE_MKFIFO 1

/ Define if you have the munmap function. /

define HAVE_MUNMAP 1

最后,Config.h 文件中除了上面的头文件,函数,数据类型编译选项之外,还有其他一些东西,比如环境变量,其他编译选项。这些内容会根据不同的项目而有很大的不同。但是可以从 Config.h 基本看出移植的工作量有多大。

经过上面的调整之后,势必 (作者很土, 其他文章 请查看 vchelp 很土专栏) 因为 Windows 环境下没有某些头文件,比如 poll.h, 就会没有 poll 函数,没有 dirent.h 就会没有 dirent 结构体。而继续使得 WinTar 编译不过。这个时候就需要根据具体的编译错误信息进行细节修饰。当需要使用 Windows 下一些特殊的定义的时候请不要忘了在 Config.h 的最前面加入 #include .
关于细节修饰,举个例子来说明。比如有个选项 HAVE_INTTYPES_H

/ Define if exists, doesn’t clash with ,
and declares uintmax_t.
/

define HAVE_INTTYPES_H 1

通过分析代码可以发现,代码并不是需要一个完整的 inttypes.h 文件,而是为了一个 uintmax_t 的定义。在 Visual Stdio 的 C Library 中并没有 inttypes.h 这个文件,也没有 uintmax_t 这个定义。回溯 Cygwin 的 include 目录的 inttypes.h 文件,发现了 uintmax_t 的定义

typedef unsigned long long uintmax_t;

很简单的数据类型重定义。这么简单定义,完全可以从 Cygwin 的 Include 目录中单独拿出来做一个专用版本的 inttypes.h 加入到 WinTar 项目中。这样编译过程中 uintmax_t 没有定义的问题就解决了。解决这类问题的一般的做法也就是从 Cygwin 的 Include 目录里面拿出相关的头文件进行修改或者单独复制到 WinTar 的目录下面。[本文于 2003 年完成. 如需要转载 请联系 jackforce at 163 dot com ]修改或者复制代码的原则是不再引入更多的定义或者头文件, 仅取所需部分。其他类似的问题还有 direct 结构定义和相关函数。

在编译过程中, 很多错误是有由 lib 目录下的文件产生的, 但是 lib 目录下的文件不是完全都需要的。lib 目录只是一个对 Tar 的补充库。需要的代码才需要编译。 具体判断的方法一个是参考 Windows C Library 库的内容。如果同样的函数, 数据类型已经定义, 就不需要 Lib 目录中的相同数据类型的定义和函数实现了。还有一个方法是尽量去掉 lib 目录中的 C 文件, 只保留头文件, 并使得编译能够通过, 根据 link 的错误信息去检查那些 lib 中的 C 文件是需要的。

除了修改外围的各种头文件之外,还不要忘了修改工程的编译选项,特别是预定义选项。在 Tar 的移植过程就需要以下的预定义 HAVECONFIGH,POSIXSOURCE,MSDOS。HAVECONFIGH 表示程序编译需要 config.h 文件。为了方便期间,在 tar 移植过程中就放到工程的预编译选项中了。MSDOS,移植的是 Linux 下的控制台程序,而 Windows 平台最接近 Linux 控制台就是 DOS,特别是一些环境变量设置和全局常量的定义。Tar 的有些代码针对 MSDOS 环境已经做了一部分修正, 这点在移植过程中可以利用起来。还有一个可选项是CYGWIN。有些 Linux 程序会针对 Cygwin 平台做出代码上的特殊设定。当遇到这样的代码的时候,一定要加上__CYGWIN预定义项,能够大大减少移植需要的工作量。还有就是移植过程引入的各种 Cygwin 代码中也可能需要__CYGWIN定义(有时候是其他的定义,比如_POSIX_SOURCE,或者__INSIDE_CYGWIN)。

经过上述的几个步骤。第一个目标,代码能够编译通过基本上是不会有什么问题的。只要把握好二个修改代码的基本原则,第一。引入新的代码,而不修改原有的代码。在没有办法进行调试前修改源代码是不允许的,修改的不好就会引起最后代码运行逻辑的混乱,而且在代码能够运行之前是很难发现问题的。所以除非非常有把握,否则不要修改被移植工程的源代码。第二,引入新的代码之后,不能因为这次引入而需要再次引入新的代码。这样子,就进入死循环了。为了解决某个数据类型的定义,而引入了新的不能解释的数据类型。这样还不如不引入新的代码。所以引入新的代码,特别是很多头文件。引入之前一定要做修改,只保留工程本身需要的部分,去除那些不需要的代码。直到能编译通过为止。 三:第二个目标,使得代码能够链接过(Link)

完成了第一个目标之后,就会有大量的 link 错误。原因是前面引入了很多外部函数,外部全局常量只有定义而没有实体,于是就会产生 link 错误。现在需要的是为代码提供引入的函数实体,外部全局变量实体。一般都是函数 link(本文于 2003 年完成. 如需要转载 请联系 jackforce at 163.com) 不到的比较多。

要解决 link 错误就需要了解不同平台上面函数操作的区别,特别是某些概念的区别。这里最好的参考资料有两个。一个是 Windows Services for UNIX (SFU)的帮助文件,一个是 MSDN 中的一篇文章《UNIX Application Migration Guide》。SFU 是微软提供一个 Unix 兼容环境,有点像 Cygwin。在安装上 SFU 之后有一个帮助文件。其中有一部分就是 Unix,Linux 函数的说明,有些函数提供了信息说明可以用 Windows Library 中那些函数来替代。这点对于移植是很重要的(省事)。UNIX Application Migration Guide 应该不算文章而是有点像书了。它说明了很多 windows 和 Unix 系统(类 Unix 系统)中很多概念不同之处,针对这些不同的概念提供了很多相关的信息来说明如何进行模拟这些不同之处。比如 Unix 系统中 Signals 概念可以使用 Windows 环境中的 Event 来替代。SIGALRM 用 Windows Message 来替代等。

SFU 的帮助文件提供了一部分信息来说明 Windows 平台中哪些低阶函数(C 函数库)可以替代相关 Unix 函数。《UNIX Application Migration Guide》则提供了一种方法来转换 Unix 平台上的一些 OS 级的概念到 windows 上。实际上 Cygwin 下面也做了很多这样的转换。具体解决 link 问题的时候可以参考 Cygwin 本身的实现。

不过有些概念,比如安全权限方面的概念。在 Linux 平台和 windows 平台上面是完全不能互换的。而且 windows 平台中的权限函数操作 (本文于 2003 年完成. 如需要转载 请联系 jackforce@163.com) 的过于复杂。这样对于某些 linux 函数。比如 getuid 处理可以参考 Cygwin 的处理办法。什么也不做直接返回 0 (return 0)。当代码中遇到这些函数的时候可以从 Cygwin 的代码中复制一个 getuid 出来。放入工程中去。
利用这些资料,并通过相关的工具比如 sourceinsight 来搜索 Cygwin 本身的源代码,Link 问题并不难处理。只是有可能在处理 link 问题的过程中会回复到上面的问题,编译不过。这个时候的代码修改还是一定要注意不要引入太多的新的代码,免得问题越来越复杂。

四:代码运行正常

实际上当 link 问题解决之后,程序可以在 windows 环境中运行时,一切就尽在掌握了。如果不考虑做多平台的程序的话,这个时候就可以任意去修改程序了。不过在代码调试过程可能需要一个参照,看看正常的程序运行流程是怎么样的。刚刚移植过来的程序在很多地方并不能马上就能正常的运行。回到 Cygwin 中, 重新编译一个可以调试的版本(在 GCC 编译选项加上 - g3), 在需要的时候可以在 Cygwin 中调试程序。调试可以用 GDB 或者 Insight。如果习惯 Windows 平台下面编程,可以使用 Insight,这是一个 TCL/TK 脚本程序,它提供了一个 Windows 界面以方便用户调试程序,不过 Insight 最终还是调用 GDB。在这里具体调试就不细说明了。

五:多平台代码

移植后的代码 (本文于 2003 年完成. 如需要转载 请联系 jackforce@163.com) 如果需要在多个平台上面运行,就要在 lib 目录里面大做文章了。提供自己的函数库,并根据各个平台进行调整。Tar 的代码由 Config.h 和一些编译选项来控制如何在各个不同的平台上面做编译。Lib 则提供了很多 C Library 函数或者不同平台下面的其他函数的替代版本。这样 Tar 在编译过程中就不会因为某些平台下某些函数的缺失而编译不过。多平台支持,一般都是在代码中加上很多编译开关,在编译期间去分隔 Linux,Windows 或者其他平台下面的特殊代码。比如 utime.h 头文件的包含问题。因为文件在 Linux(gcc)下面和 Windows(cl)下所处的 C Library 目录不同。包含的处理办法就不一样。可能需要这样写才能完全正确的包含。

if HAVE_UTIME_H —— 如果有 utime.h 文件

ifdef WIN32 ——- 如果是 win32 环境

include ——- 包含 sys/utime.h

endif

ifdef LINUX —— 如果是 Linux 环境

include —— 包含 utime.h

endif

else —- 如果没有 utime.h 定义出需要的结构

struct utimbuf
{
long actime;
long modtime;
};

endif

在其他的代码中基本上也是这样的处理。根据编译环境的不同来编译不同的代码。 这样的 define 的区隔,主要就是为了区隔不同平台的不同细微区别。有的区别也许是某些常量没有定义,有些区别是某些函数不存在。如果代码中调用函数在某些平台下面不存在,就需要提供一个 lib 去提供这些函数。Tar 的 Lib 的作用也是如此。

基本上代码的移植是前难后易。前期首先要保证源代码本身的逻辑不能变动,所以在修改代码方面只能尽量修改外围的代码,而不是修改源代码本身。如果 link 过了之后,则就是一般的 Windows 下面的编程了,可以根据需求任意修改移植后的代码了。最难的地方可能就是 OS 级不同概念的替换了。C Library 虽然在各个平台上有不同之处,但是总是比较接近,不同的地方可以提供自己编写的代码来替换。但是 OS 级的概念,和平台相关性太大,一般不太容易替换。

六:扩展问题,待解决的问题

如果需要把移植过来的代码改成 DLL 或者 lib 给其他的工程调用。比如给其他的工程提供一个解包 Tar 文件的功能。如果不加修改,那么移植过来的代码有很多缺陷。

首先是多线程支持问题。如果代码中有很多全局变量,那么改成 DLL 或者 lib 之后就不能在多线程下面调用。

其次,DLL 接口表。移植后的代码入口是 main 函数,虽然整个工程里面有很多独立功能,但是这些独立功能的调用都是通过使用不同的参数来实现。如何输出接口表给其他工程使用,需要做些功夫。

三、控制 原始的控制台程序在下了运行参数之后,一般都是一头运行到底的,也有可能在中间有些要求输入某些信息的。这样的程序如何集成到其他的工程中并受到其他工程的控制?比如遇到某些错误要返回等等。在 Tar 代码中遇到错误就直接退出程序。显然这些地方就不合 DLL 设计要求。可能需要重新设计代码的结构。

四,输出信息。Tar 工程里面很多向控制台输出的信息。这些信息输出需要重新定向或者屏蔽。

第三第四部分可以参考 Linux 下面的 FrontEnd 程序,即只是为某个特殊的程序提供的一个 GUI 界面的程序。FrontEnd 程序就是控制了主程序的运行并重新定向输出信息到 GUI 界面上。

注 1. Cygwin,是 Windows 平台下面的一个 Linux 模拟环境。可以从 www.Cygwin.com 上下载全部内容。

注 2. Windows Services for UNIX (SFU)的 SDK 可以从微软网站上获得 http://www.microsoft.com/windows/sfu/

注 3. UNIX Application Migration Guide 可以从 MSDN 中取得,如果没有 MSDN 可以从微软 MSDN 网站上取得。 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnucmg/html/ucmglp.asp

注 4. Tar, Cygwin 下面有 Tar。但是只能在 Cygwin 下面运行 或者必须提供 Cygwin 的平台 DLL 才能在 windows 下面单独使用 Tar 程序。
注 5. CL 是微软的 C/C++ 编译器,包含在 Visual Studio 各个版本中

本文于 2003 年完成. 如需要转载 请联系 jackforce@163.com, 如果有看到部分干扰信息. 请原谅. 主要避免转载过程中作者信息丢失用. 不得以为之, 请各位原谅.

PS :

用一个例子简单说明了从 linux 平台移植到 windows 平台上的一些需要注意的问题和解决方法.

例子仅用来说明移植过程产生的问题用.
https://www.cnblogs.com/liangxiaofeng/p/3611576.html