跳至主要内容

【转】移植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; 
}; 

评论

此博客中的热门博文

【转】AMBA、AHB、APB总线简介

AMBA 简介 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大。数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用。在基于IP复用的SoC设计中,片上总线设计是最关键的问题。为此,业界出现了很多片上总线标准。其中,由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。   AMBA 片上总线        AMBA 2.0 规范包括四个部分:AHB、ASB、APB和Test Methodology。AHB的相互连接采用了传统的带有主模块和从模块的共享总线,接口与互连功能分离,这对芯片上模块之间的互连具有重要意义。AMBA已不仅是一种总线,更是一种带有接口模块的互连体系。下面将简要介绍比较重要的AHB和APB总线。 基于 AMBA 的片上系统        一个典型的基于AMBA总线的系统框图如图3所示。        大多数挂在总线上的模块(包括处理器)只是单一属性的功能模块:主模块或者从模块。主模块是向从模块发出读写操作的模块,如CPU,DSP等;从模块是接受命令并做出反应的模块,如片上的RAM,AHB/APB 桥等。另外,还有一些模块同时具有两种属性,例如直接存储器存取(DMA)在被编程时是从模块,但在系统读传输数据时必须是主模块。如果总线上存在多个主模块,就需要仲裁器来决定如何控制各种主模块对总线的访问。虽然仲裁规范是AMBA总线规范中的一部分,但具体使用的算法由RTL设计工程师决定,其中两个最常用的算法是固定优先级算法和循环制算法。AHB总线上最多可以有16个主模块和任意多个从模块,如果主模块数目大于16,则需再加一层结构(具体参阅ARM公司推出的Multi-layer AHB规范)。APB 桥既是APB总线上唯一的主模块,也是AHB系统总线上的从模块。其主要功能是锁存来自AHB系统总...

【转】VxWorks入门

1.VxWorks开发方式:交叉开发,即将开发分为主机(host)和目标机(target)两部分。 类似于dos下C语言程序的开发。 合并开发的优点:简单 缺点:资源消耗量大,CPU支持,非标准体系的支持 host (Tornado) target(vxWork) 小程序模块 vxWorks实际采用开发模式 Tornado提供:编辑,编译,调试,性能分析工具,是vxWorks的开发工具 vxWorks:面向对象可以剪裁的实际运行操作系统 2.vxWorks启动方式 <1>Rom方式 (vxWork_rom) vxWorks直接烧入rom <2>Rom引导方式(bootrom+vxWorks) 其中bootrom烧入rom,vxWorks可以通过从串口,网口,硬盘,flash等下载!这里的bootrom不是开发环境中的bootable,在开发环境里bootable指的是vxWorks,downloadable指application 3.调试 <1>attachs/20060907_164540_564.rar 用来在多任务调试时将调试对象绑定到某个任务 <2>任务级调试(attachs/20060907_164540_564.rar taskName) 单个任务的调试不会影响到其他任务的运行,主要用来调用户的应用程序。 全局断点:在调另一任务或本任务时,系统运行本任务断点,则停下。各任务要配合使用。 任务断点:调本任务时,系统运行到本任务断点,则停下。如果没有attachs/20060907_164540_564.rar到本任务,不起作用。 一次性断点:跑到一次之后自动删除。 <3>系统级调试(attachs/20060907_164540_564.rar system) 把所有task和系统core、中断看成一个整体,可用于调试系统和中断。对中断调试,如果不是系统级调试,无论是那种断点都不起作用 !wdbAgent不在调试范围内,当任务级调试时工作在中断方式,系统级调试工作在轮询方式。 !可是使用命令行方式的调试,参看crossWind教程。 4.调度 优先级调度(无条件) 时间片:同优先级,如果时间片没有打开,任务采取先到先运行,运行完毕在交出cpu,如果打开,则轮流使用cpu。 !死循环使比它...

【转】C++/CLI程序进程之间的通讯

 现在,把大型软件项目分解为一些相交互的小程序似乎变得越来越普遍,程序各部分之间的通讯可使用某种类型的通讯协议,这些程序可能运行在不同的机器上、不同的操作系统中、以不同的语言编写,但也有可能只在同一台机器上,实际上,这些程序可看成是同一程序中的不同线程。而本文主要讨论C++/CLI程序间的通讯,当然,在此是讨论进程间通讯,而不是网络通讯。    简介   试想一个包含数据库查询功能的应用,通常有一个被称为服务端的程序,等待另一个被称为客户端程序发送请求,当接收到请求时,服务端执行相应功能,并把结果(或者错误信息)返回给客户端。在许多情况中,有着多个客户端,所有的请求都会在同一时间发送到同一服务端,这就要求服务端程序要更加高级、完善。   在某些针对此任务的环境中,服务端程序可能只是众多程序中的一个程序,其他可能也是服务端或者客户端程序,实际上,如果我们的数据库服务端需要访问不存在于本机的文件,那么它就可能成为其他某个文件服务器的一个客户端。一个程序中可能会有一个服务线程及一个或多个客户线程,因此,我们需小心使用客户端及服务端这个术语,虽然它们表达了近似的抽象含义,但在具体实现上却大不相同。从一般的观点来看,客户端即为服务端所提供服务的"消费者",而服务端也能成为其他某些服务的客户端。    服务端套接字   让我们从一个具体有代表性的服务端程序开始(请看例1),此程序等待客户端发送一对整数,把它们相加之后返回结果给客户端。   例1: using namespace System; using namespace System::IO; using namespace System::Net; using namespace System::Net::Sockets; int main(array<String^>^ argv) { if (argv->Length != 1) { Console::WriteLine("Usage: Server port"); Environment::Exit(1); } int port = 0; try { port = Int32::Parse(argv[0]); } catch (FormatException^ e) { Console::Wri...