一.前言
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 <linux/fd.h> header file. */
/* #undef HAVE_LINUX_FD_H */
/* Define if you have the <locale.h> header file. */
#define HAVE_LOCALE_H 1
/* Define if you have the <malloc.h> header file. */
#define HAVE_MALLOC_H 1
/* Define if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1
/* Define if you have the <ndir.h> 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在<sys/types.h>中沒有定義的話,可以把他定義為int型。所以在config.h加上#define mode_t int。這樣mode_t沒有定義的問題就解決了。其他的數據類型也是同樣對待處理。
* Define to `int' if <sys/types.h> doesn't define. */
#define mode_t int
/* Define to `long' if <sys/types.h> doesn't define. */
/* #undef off_t */
/* Define to `int' if <sys/types.h> 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 <Windows.h>.
關於細節修飾,舉個例子來說明。比如有個選項HAVE_INTTYPES_H
/* Define if <inttypes.h> exists, doesn't clash with <sys/types.h>,
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的移植過程就需要以下的預定義HAVE_CONFIG_H,_POSIX_SOURCE,MSDOS。HAVE_CONFIG_H 表示程序編譯需要config.h文件。為了方便期間,在tar移植過程中就放到工程的預編譯選項中了。MSDOS,移植的是Linux下的控制台程序,而Windows平台最接近Linux控制台就是DOS,特別是一些環境變量設置和全局常量的定義。Tar的有些代碼針對MSDOS環境已經做了一部分修正,這點在移植過程中可以利用起來。還有一個可選項是__CYGWIN__。有些Linux程序會針對Cygwin平台做出代碼上的特殊設定。當遇到這樣的代碼的時候,一定要加上__CYGWIN__預定義項,能夠大大減少移植需要的工作量。還有就是移植過程引入的各種Cygwin代碼中也可能需要__CYGWIN__定義(有時候是其他的定義,比如_POSIX_SOURCE,或者__INSIDE_CYGWIN__)。
經過上述的幾個步驟。第一個目標,代碼能夠編譯通過基本上是不會有什麼問題的。只要把握好二個修改代碼的基本原則,第一。引入新的代碼,而不修改原有的代碼。在沒有辦法進行調試前修改源代碼是不允許的,修改的不好就會引起最後代碼運行邏輯的混亂,而且在代碼能夠運行之前是很難發