Wine 是一个令人神往而且目标远大的开放源代码项目,它尝试去解决在 Linux 上运行 Windows 可执行文件的复杂问题。尽管 Wine 不是一个新项目,但是,人们对 Linux 桌面的期望以及对 Linux 应用程序的需求日益增加,使得它现在仍具有重要意义。本文对 Wine 进行了介绍,并提出了几种获得 Wine 内部操作经验的途径。
Wine 项目起始于 1993 年,它的根源可以追溯到 90 年代早期出现的用于 UNIX 的 DOS 和 Windows 模拟器。Wine 项目最初是将 16 位的应用程序移植到 Linux,而几年之后,已可以在 Linux 上运行 Microsoft Word 和 Excel。现在它有一百多万行代码。
人们一直认为,在桌面上采用 Linux 的主要障碍是应用程序不足。商用桌面应用程序供应商还不能确定他们是否应该投入时间和精力将他们的 Windows 应用程序移植到 Linux,他们基本上是在等待 Linux 大规模应用于桌面。另一方面,Linux 需要应用程序才能大规模应用于桌面。这是一个经典的先有鸡还是先有蛋的问题,而 Wine 通过在 Linux 上运行现有的 Windows 应用程序而解决了这一问题。
Wine 项目实际是一个二合一的项目。它们提供了一个名字叫做 Winelib 的开发工具包,用于将应用程序从 Windows 移植到 Linux(和 Unix);它们还提供了一个程序加载器,让 Windows 二进制文件可以在 Unix 和类 Unix 系统中运行。本文讨论的主要是后者;在 参考资料中有关于 Winelib 的更多资料的链接。
据 WineHQ的说法,“WINE 代表 Wine Is Not an Emulator(即,Wine 不是一个仿真器)。 更确切地说,Wine 是 X 和 UNIX 之上对 Windows API 的一个开放源代码实现。您可以认为它是一个 Window 兼容层。Wine 不需要 Microsoft Windows,因为它是由 100% 非 Microsoft 代码构成的另一个实现。但是它可以使用本机系统 DLL,只要这些 DLL 可用。而且它可以让您在 Linux 或者其他类 UNIX 操作系统之上运行大部分 Windows 软件。”
|
Wine 程序加载器让运行于 x86 上的 Linux 和其他类 Unix 操作系统可以加载并运行 Windows x86 可执行文件 -- 不过那只是它要解决的问题的一部分。因为 Windows 可执行文件总是会链接到其他的库,而这些库是 Windows 操作系统的一部分(如本文稍后图 1 中的 Dependency Walker 截图中可以看到),Wine 还最大可能限度地实现了那些 Windows 内部构件,即 Linux 上通常所指的 Win32 API5。
虽然 Windows 和 Linux 有很大的不同,但是就基本的层次而言,与任何现代操作系统一样,还是有很多类似之处的 -- 比较明显的包括,对文件和目录的支持,对同时运行多个程序的支持,类似的用户界面以及对多媒体的支持。
图中显示了 callDLL.exe 的依赖,这个可执行文件可以由本文提供的源代码编译得到。对一般的用户而言,依赖的复杂度可能是惊人的,但对任何一个系统程序员来说都不是这样,他们充分了解,哪怕是运行一个最简单的程序,操作系统也必须要做很多事情。
考 虑图 1 所示的可执行文件的第一个依赖 -- 对 USER32.DLL 的依赖。在 Windows 中,一个 DLL 就是一个动态链接库(dynamically linked library),类似于 Linux 中的一个共享对象(一个 .so 文件)。USER32.DLL 文件通常会由操作系统提供,位于 C:\WINDOWS\system32 或者 C:\WINNT\system32 目录下。这个文件中包含了 Windows API 中用于用户界面的函数实现。
我们的可执行文件调用 USER32.DLL 中的一些函数,USER32.DLL 然后去调用 NTDLL.DLL 中的其他函数,如此继续。这些函数大部分已经由 Microsoft 文档化 -- 但是还有很多没有被文档化。文档的缺乏对 Wine 来说是一个极大的障碍,本文稍后将更详细地讨论这一问题。
现在让我们来看我们的可执行文件的第二个依赖 -- 对 SIMPLEDLL.DLL 的依赖。这个 DLL 是在编译本文所附的源代码时创建的。这个 DLL 中实现了一些特别简单的函数;它作为常见于安装 CD 上的各种二进制文件的例子被包括进来。
如我们的例子所示,一般的 Windows 可执行文件有两种类型的依赖:一种是对操作系统提供的二进制文件的依赖,另一种是对作为应用程序一部分的二进制文件的依赖。
还需要特别注意的是,DLL 以难于管理而闻名,即使是在产生它们的 Windows 操作系统中也是如此(参见 参考资料)。值得一提的是,Wine 团队成功地创建了一个可以在 Linux 上运行很多商用 Windows 应用程序的框架。
当 前,开放源代码的 Wine 项目有一个健壮的平台来运行 Windows 二进制文件以及对 Win32 API 的部分实现。这个项目仍然处于最初的测试阶段(alpha),有很多部分还没有完成。尽管 Wine 提供了一些工具来帮助进行配置、安装以及运行应用程序,但是它们大部分都是面向程序员的,要让非技术用户也可以使用这些工具,还有很多事情需要去做。过去 的 Corel 以及现在的 CodeWeavers 为此提供了很多帮助。
既然我们已经理解了 Wine 的基本原理,让我们更详细地来研究 Wine 能够做什么。本文中,我们讨论的是 Wine 在纯 Linux 上的安装,没有任何 Windows 分区。
- Windows 可执行文件:
Wine 完全支持 Windows 可执行文件( .exe 和 DLL)的二进制加载。
- DLL:
Wine 有几百个 Windows DLL 的内部实现 -- 不过,其中没有多少是完全的实现。例如,包含有用户界面相关函数的 userd32.dll 在开放源代码的 Wine 中实现了 92%。
- COM:
这是一种几乎被所有的大型 Windows 应用程序所使用的 Windows 技术,它支持诸如将一个 Excel 电子数据表嵌入到 Word 文档中等功能。这一技术得到了 Wine 的很好的支持。
- 注册表:
这是另一个几乎任何一个 Windows 应用程序都会使用的关键技术,Wine 实现了大约 90% 的 Windows 注册表管理 API。
- 核心功能:
核心系统功能也得到了特别好的支持。如前面提到的,尽管 Linux 和 Windows 之间存在区别,但是基本的层次上还有很多类似之处,因此与进程、线程、文件和消息队列相关的核心系统 API 得到了近乎完美的支持。
- 音频和视频:
Wine 支持 Windows 音频和视频文件的运行(还可以使用 Windows 媒体播放器)。
- 打印:
也得到了支持,可以从一个在 Wine 中运行的 Windows 应用程序进行打印。
- ODBC:
Wine 支持那些需要通过 ODBC 访问数据库的 Windows 应用程序。
- 调试:
Wine 有一个非常健壮和强大的内置调试器,除了支持标准的调试功能外,它还为调试运行于 Linux 上的 Windows 二进制程序进行了定制。它是 Wine 为其开发者提供的最重要工具之一。Wine 还有一个设计完备的追踪和记录日志的模块,可以帮助调试。我们之所以强调这一点,原因在于,尽管框架是健壮的,但是,当在 Wine 中安装和运行 Windows 应用程序时有很多不确定因素,在使用开放源代码的 Wine 时迟早会停下来进行调试。
不 幸的是,很多用户发现 Wine 难于使用。诚然,在 Linux 上使用类似于 MS Office 这样的应用程序可能是困难的;在这里我们来看一些原因,为什么确实是这样。在开始之前,我们应该指出,CodeWeavers 提供的商用 Wine 已经解决了大部分此类问题(参阅 参考资料以获得链接)。假以时日,这些问题将可能在开放源代码的 Wine 中同样得到解决。
- 使用命令行
与大部分 Linux 中的应用程序一样,Wine 必须在命令行中安装。用户必须回到命令行中来在 Wine 下运行应用程序。例如,要运行Internet Explorer,用户通常需要在 shell 中输入 wine IEXPLORE.EXE
。尽管经验丰富的用户喜欢这样做,但初学者会发现这比较困难,而且不太可能懂得起别名等快捷方式。
- 配置
CDROM 和驱动器映射等工作必须手工完成。这对初学者来说也是个问题。
- 取消对隐藏文件的隐藏
如果挂载 CDROM 时没有使用专门的 unhide
选项,那么安装 CD 上的一些文件可能会无法找到,从而导致安装失败。
- 调整配置
Wine 安装很少能直接使用(对它来说也就是从 tarfile 解开)。通常需要进行全面的调整;例如,一个程序的安装可能与您在配置文件中设置的 Windows 版本不相容;而修改那个版本又可能会影响一些已安装的应用程序。对很多 DLL 来说也是如此。为此,Wine 提供了一个巧妙的修复,让您可以设置特定于应用程序的属性。不过,一般的用户可能不会像我们一样满意。
- 额外的安装步骤
大部分应用程序的安装过程都不是标准的。例如,当我们安装 Internet Explorer 6 时,必须跳过一些步骤,比如 DCOM98 的定位与安装。对用户来说,这类事情可能是最大的障碍。
注意,这些并不是病症,而是举例说明了是哪些种类的事情导致对一般用户来说 Wine 看起来复杂而且困难。
看完 Wine 的可用性问题后,现在让我们来总结在尝试使用 Wine 时一些常见的技术问题。
- 缺少 DLL
这可能是最常见的问题:很多安装由于缺少 DLL 而失败。人们应该熟练使用调试器来决定下一步如何去做。(稍后将深入讨论调试器)。
- DLL 版本问题
一些安装程序在开始之前会检查现有的系统 DLL。Wine 解决这一问题的方法是,创建假 DLL 以满足安装程序的需要。不过,有一些安装程序会更进一步并深入检查 DLL 以获得它们的版本。这对假的 DLL 来说要求太高了,会导致安装失败。
- DLL 加载次序
Wine 有对很多 Windows DLL 的实现,而且如果可用,它还可以使用原始的 Windows DLL。如果两种 DLL 都可用,好像显然应该选择总是使用 Windows 自己的 DLL,但实际上 Windows DLL 有时会包含不能被满足的依赖。要确定是更应该使用 Wine 的 DLL 还是应该使用本机 DLL,惟一的方法是,基于各个应用程序反复进行试验。
- DLL 中的函数
当 一个 Wine DLL 没有实现 Windows 中相应的 DLL 的全部功能时,应用程序可能会遭遇函数调用失败。由于 DLL 是动态加载的,可能没有办法事先知道会发生这样的事情。这是一个复杂的问题,有一些可做的工作,但最终实际来说它只是取决于应用程序的代码如何编写。
有一些因素会减轻这些问题。一方面,您将会一个一个地遇到这些问 题,而不是一次遇到全部问题,这样处理起来要容易些。另外,您遇到的那些问题可能其他人曾遇到过并已经解决(而且解决方案已经公布出来)。Wine 用户组非常活跃,会提供许多帮助,每周一次的 Wine 时事通讯(参阅 参考资料)是极好的信息资源。
在 Wine 中,很多 Win32 API5 的函数是残缺不全的。最常见的原因是,相当多的 Win32 API 并没有被文档化。这就意味着一个特别的应用程序可能会调用某个函数,而完全没有关于此函数的可用资料。例如,我们在运行一个简单的 RPC 程序时发现了 RtlAnsiCharToUnicodeChar 这个函数。在 MSDN 上的搜索结果显示没有关于这个函数的资料,而且没有关于所有 RtlXXXX 类别函数的资料。因此,如果它们在 Wine 中的实现对一些应用程序来说至关重要,那么人们可能只有去猜测它们的行为了。
CodeWeavers 为 Wine 做了很多工作。多年来他们为 Wine 项目贡献了很多代码,他们出售商用版本的 Wine,其改进的用户界面解决了我们在本文中提出的很多问题。
例 如,CodeWeavers 的二进制安装文件会在用户的开始菜单中添加一个 Crossover 条目;安装后,绝大多数 Crossover 相关的任务可以通过开始菜单条目来完成。在开放源代码的 Wine 中,所有这些任务 -- 安装、程序执行以及其他任务 -- 都必须在命令行中执行。此外,CodeWeavers Crossover 将会尝试去为新安装的软件包配置一个合理的默认值,如果需要的话会在安装完成后自动重新引导,并以其他形式减轻用户的负担。
CodeWeavers 使用开放源代码的 Wine 作为他们的 Crossover 产品的基础,所以,除非遇到上面我们讨论过的可用性问题,否则,在其中一个产品中能运行的应用程序,在另一个产品中同样也能运行。要深入了解 CodeWeavers 和 Crossover,以及要获得可以在 Wine 上运行的应用程序列表,请参阅在 参考资料中列出的链接。
由 于 Wine 支持 Windows 可执行文件的运行,您会想当然地认为可以使用程序的安装程序从头安装,这是正常的。不幸的是,几乎不会那样。对 Windows 安装过程的理解将有助于解释原因。下面非常简单地描述了 Windows 安装程序通常要做的事情的(不必是这个次序):
- 将文件拷贝到一些目录。
- 注册 DLL,并将其他应用程序相关的信息添加到注册表中。
- 在安装过程中,有时会检查 DLL 的版本(如前面所提到的)。
- 修改 INI 和一些其他配置文件。
因而,Wine 会遇到两种类型的问题,必须按顺序解决:
- 安装过程中的问题。
- 执行过程中的问题。
在 调试 Wine 安装的过程中,如果您同时有一个可用的 Windows 系统的话会非常有帮助。那样,您可以对 Windows 安装使用追踪器以确切断定哪些文件被拷贝,哪些注册表条目被添加或更新,哪个 INI 文件被修改,等等。记录安装步骤的顺序并与失败的 Wine 安装相比较,是故障诊断的好向导。
如果您正在使用 Red Hat 或者 SUSE,最简单的方法是从 CD 安装 Wine。不过,如果那些 CD 比较老,您可能需要通过源文件安装,因为 Wine 项目经常更新。如果通过源文件安装,您会发现 Wine 用户指南(参阅 参考资料以获得链接)是一份价值无法估量的资料。简化的安装过程如下:
- 解开源文件后,切换到
tools
目录下以用户身份运行 ./tools/wineinstall。
- 在 tools 目录下运行
winecheck
脚本来检查安装。您可能不会获得 100% 的成功,但只要没有关键问题就行。
- Wine 的所有配置都保存在
~/.wine/config
文件中。这个文件很容易理解:它描述了您希望将 Linux 文件系统的哪部分看作是 Windows C 驱动器,以及 DLL 的加载次序等其他的细节。
您应该可以快速进行了。例如,要安装 WinZip 8.1,您可以下载安装程序并在命令行中运行 wine winzip81.exe
。
快速浏览一下图 2 可以了解很多内容:您可以看到 WinZip 在运行,它的文件浏览器组件显示出熟悉的 Windows 驱动器 C、软盘驱动器 A、一个 CD-ROM M 以及另外的 Z 驱动器。您可以猜到,所有这些都映射在我们上面提到的 ~/.wine/config
文件中。下面是文件中与图 2 所示驱动器有关的片断:
清单 1. Wine 的配置文件
[Drive A]
"Path" = "/mnt/floppy"
"Type" = "floppy"
"Label" = "FLOPPY1"
"Device" = "auto"
[Drive C]
"Path" = "/home/aditya/aug14/3/c"
"Type" = "hd"
"Label" = "fake_windows"
"Filesystem" = "win2k"
"Codepage" = "0"
[Drive Z]
"Path"="/home/aditya/downloads/wine/"
"Type" = "hd"
"Label" = "wine src"
"Filesystem" = "win95"
[Drive M]
"Path" = "/mnt/cdrom"
"Type" = "cdrom"
"Label" = "CD-ROM1"
"Filesystem" = "win95"
"Device" = "auto"
|
从这里您可以看到,图 2 中的 M 驱动器实际上是 /mnt/cdrom;C 驱动器是 /home/aditya/aug14/3/c;等等。
技术上讲,使 Windows 可执行文件在 Wine 中运行并不是移植,但是经常被认为是移植。这也是测试一个应用程序是否适合使用 Winelib 进行移植的好办法。要获得关于使用 Wine Winelib 库编译来移植源代码的资料,请参阅 参考资料中关于 Winelib 用户指南的链接。
我 们已经整理了一个非常简单的应用程序,来说明 Windows 程序如何在 Wine 中运行。程序创建了一个窗口;从一个 DLL 中加载了两个函数,有一些对话框。我们将继续使用非常简单的例子;作为实际应用中的例子,我们非常鼓励您用 MS Office 等大型应用程序进行同样的尝试。
同时,这个例子可以通过让您体验代码的运行来入门。
示例 DLL 的源代码在 DLLCode 目录中,使用 DLL 的源代码在 callDll 目录中。要运行只需要将两者(DLL 文件和可执行文件)存放于同一目录下,并运行 callDll.exe
。winetests 文件夹中的 README.TXT 文件描述了如何在 Linux 上 Wine 中执行那个二进制程序。
如果您愿意,可以随意打开项目文件并进行修改。
DLLCode 项目正确编译后,您可以在 Debug 文件夹中看到 DLLSample.dll 文件。这个文件是 callDll 项目所需要的。为了您的方便,DLLSample.dll 已经存放于 callDll 中正确的位置。您可以回顾图 1 中举例说明的 callDll 中产生的可执行文件的依赖;您可以使用 Dependency Walker 来查看其他程序的依赖(参阅 参考资料)。
下面是对运行于 Windows XP 上的和运行于 Red Hat 的 GNOME 中的消息框(Message Box)视觉上的对比:
对应于此的 C 代码(callDll.cpp 中第 60 行):
MessageBox(NULL, "Wine test ending...", "", MB_OK);
这提出了一些有趣的观察:
- 最明显的观察之一是,双方都支持消息框的使用;这是将 Windows 调用有可能映射为 Linux 中相应部分的因素之一。如果您在 Wine 源代码中搜索 MessageBox,您可以找到说明文件,文件中详细说明了哪些函数已经被实现,哪些被去除掉。
- 大小、字体、颜色和标题栏以及其他可视元素继承自底层操作系统设置的默认值。
- 从本文中所包括的追踪文件中,您可以看到这个函数是在 user32.dll 中实现的。如果您在文件中搜索字符串“Wine test ending”,您将更深入地理解到,当您在做一些与在消息框中显示字符串同样简单的事情时,幕后发生了多少事情。
既 然我们已经看过了一个 Win32 函数,让我们来看一下,当 simpleDLL.dll 中实现的我们自己的函数之一被调用时,幕后发生了什么。我们将展示追踪记录中的片断来说明何时 simpleDll.dll 被加载,哪些函数被导出,在哪一点 getTitle() 函数被调用(按 README 文件中的步骤生成完全的追踪记录):
清单 2. 解析出我们的 DLL 的路径并检查 DLL 是否存在
0009:trace:module:MODULE_GetLoadOrder looking for
C:\documents\article-sample\simpleDLL.dll
|
清单 3. 这一行展示我们的 DLL 所导出的函数
Module name is simpleDLL.dll, 3 functions, 3 names
Ord RVA Addr Name
1 0000100a 0x1000100a add
2 0000100f 0x1000100f getSize
3 00001014 0x10001014 getTitle
|
清单 4. 这里展示了 getTitle 被调用的位置
0009:CALL simpleDLL.getTitle((0x40580000,00000002,00000040):
returning 405b7210
) ret=004010d4
0009:RET simpleDLL.getTitle() retval=1002901c ret=004010d4
|
分析一个更大的应用程序或多或少与此相似,不过您当然会得到多得多的细节,会遇到没有被实现的函数,等等。故障诊断通常大概是提供缺少的 DLL 和调整配置;偶尔,您将需要去实现或修复一个函数。
如果您正在寻找将现有的 Windows 应用程序转移到 Linux 的方法,开放源代码的 Wine 和来自 CodeWeavers 的商用产品都是极好的选择。应用程序可以运行于 Wine 之上,或者使用 Winelib 工具包进行移植。
Wine 还为那些希望介入开放源代码软件的人们提供了一个独一无二的机会。从特别困难的到适合初学者的,有大量的各种复杂程度的项目--而且 Wine 社区非常活跃并提供非常多的支持。