跳至主要内容

【转】移植Linux的应用软件到Windows

一.前言 

  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在中没有定义的话,可以把他定义为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基本看出移植的工作量有多大。 

  经过上面的调整之后,势必因为Windows环境下没有某些头文件,比如poll.h,就会没有poll函数,没有dirent.h 就会没有dirent 结构体。而继续使得WinTar编译不过。这个时候就需要根据具体的编译错误信息进行细节修饰。当需要使用Windows下一些特殊的定义的时候请不要忘了在Config.h的最前面加入#include . 

  关于细节修饰,举个例子来说明。比如有个选项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__)。 

  经过上述的几个步骤。第一个目标,代码能够编译通过基本上是不会有什么问题的。只要把握好二个修改代码的基本原则,第一。引入新的代码,而不修改原有的代码。在没有办法进行调试前修改源代码是不允许的,修改的不好就会引起最后代码运行逻辑的混乱,而且在代码能够运行之前是很难发现问题的。所以除非非常有把握,否则不要修改被移植工程的源代码。第二,引入新的代码之后,不能因为这次引入而需要再次引入新的代码。这样子,就进入死循环了。为了解决某个数据类型的定义,而引入了新的不能解释的数据类型。这样还不如不引入新的代码。所以引入新的代码,特别是很多头文件。引入之前一定要做修改,只保留工程本身需要的部分,去除那些不需要的代码。直到能编译通过为止。 三:第二个目标,使得代码能够链接过(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在编译过程中就不会因为某些平台下某些函数的缺失而编译不过。多平台支持,一般都是在代码中加上很多编译开关,在编译期间去分隔LinuxWindows或者其他平台下面的特殊代码。比如utime.h头文件的包含问题。因为文件在Linux(gcc)下面和Windows(cl)下所处的C Library目录不同。包含的处理办法就不一样。可能需要这样写才能完全正确的包含。 

   

#if HAVE_UTIME_H &#61663;---- 如果有utime.h 文件 
# ifdef WIN32 &#61663;-----如果是win32环境  
# include <sys/utime.h> &#61663;-----包含sys/utime.h 
# endif 
# ifdef Linux &#61663;---- 如果是Linux环境 
# include <utime.h> &#61663;---- 包含utime.h  
# endif 
#else &#61663;--- 如果没有utime.h定义出需要的结构  
struct utimbuf 

long actime; 
long modtime; 
}; 

评论

此博客中的热门博文

【转】VxWorks中的地址映射

在运用嵌入式系统VxWorks和MPC860进行通信系统设计开发时,会遇到一个映射地址不能访问的问题。 缺省情况下,VxWorks系统已经进行了如下地址的映射:   memory地址、bcsr(Board Control and Status)地址、PC_BASE_ADRS(PCMCIA)地址、Internal Memory地址、rom(Flach memory)地址等,但是当你的硬件开发中要加上别的外设时,如(falsh、dsp、FPGA等),对这些外设的访问也是通过地址形式进行读写,如果你没有加相应的地址映射,那么是无法访问这些外设的。   和VxWorks缺省地址映射类似,你也可以进行相应的地址映射。   如下是地址映射原理及实现:   1、 地址映射结构 在Tornado\target\h\vmLib.h文件中 typedef struct phys_mem_desc { void *virtualAddr; void *physicalAddr; UINT len; UINT initialStateMask; /* mask parameter to vmStateSet */ UINT initialState; /* state parameter to vmStateSet */ } PHYS_MEM_DESC; virtualAddr:你要映射的虚拟地址 physicalAddr:硬件设计时定义的实际物理地址 len;要进行映射的地址长度 initialStateMask:可以初始化的地址状态: 有如下状态: #define VM_STATE_MASK_VALID 0x03 #define VM_STATE_MASK_WRITABLE 0x0c #define VM_STATE_MASK_CACHEABLE 0x30 #define VM_STATE_MASK_MEM_COHERENCY 0x40 #define VM_STATE_MASK_GUARDED 0x80 不同的CPU芯片类型还有其特殊状态 initialState:实际初始化的地址状态: 有如下状态: #define VM_STATE_VALID 0x01 #define VM_STATE_VALID_NOT 0x00 #define VM_STATE_WRITA

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如下内容: Config ARM_CS8900    tristate "CS8900 support" depends on NET_ETHERNET && A

【转】多迷人Gtkmm啊

前边已经说过用glade设计界面然后动态装载,接下来再来看看怎么改变程序的皮肤(主题)     首先从 http://art.gnome.org/themes/gtk2 下载喜欢的主题,从压缩包里提取gtk-2.0文件夹让它和我们下边代码生成的可执行文件放在同一个目录下,这里我下载的的 http://art.gnome.org/download/themes/gtk2/1317/GTK2-CillopMidnite.tar.gz     然后用glade设计界面,命名为main.glade,一会让它和我们下边代码生成的可执行程序放在同一个目录下边     然后开始写代码如下: //main.cc #include <gtkmm.h> #include <libglademm/xml.h> int main(int argc, char *argv[]) {     Gtk::Main kit(argc,argv);         Gtk::Window *pWnd;        gtk_rc_parse("E:\\theme-viewer\\themes\\gtk-2.0\\gtkrc");       Glib::RefPtr<Gnome::Glade::Xml> refXml;     try     {         refXml = Gnome::Glade::Xml::create("main.glade");     }     catch(const Gnome::Glade::XmlError& ex)     {         Gtk::MessageDialog dialog("Load glade file failed!", false,       \                                   Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);         dialog.run();               return 1;     }         refXml->get_widget("main", pWnd);     if(pW