说起Wine,稍微资深一点的Linux 用户应该都听过,但是真要说起Wine到底是怎么回事,可能大多数人不见得说得清。这篇文章会简单的介绍Wine的工作原理,以及如何开始Wine 的开发。

所以如果您属于以下三类读者之一:

*想参与Wine开发,但是不知如何开始的。

*不认同Wine技术,仅仅想大致了解Wine是如何工作的。

*只是想能够愉快的用上最新版本Wine的。


希望在看完本文后,能够有一些收获。


Wine是什么


Wine是 “Wine Is Not an Emulator”的递归缩写,如同“GNU”一样(GNU’s Not Unix),字面意思就是Wine不是一个模拟器。这里的模拟器主要是指Wine并不是一个虚拟机,而是一个Windows API实现兼容层。


这么说可能不太好理解,大家可以把Windows应用程序类比成Android应用程序,而Wine 的角色就和Android很像了,将操作系统提供的各种功能封装成API,并让应用程序在隔离的环境内运行。至于API长成啥样,隔离的力度如何,这些都是实现相关的,和本质并不冲突。另一方面,Wine其实又是一个模拟器,不过模拟的对象不是硬件CPU,而是Windows 的行为。


Wine原理介绍


本节内容较为枯燥,需要对操作系统有一定的了解。如果只是想编译、运行,对原理不敢兴趣的同学,可以跳过,不影响后面的阅读。


Wine的目的是运行Windows上的可执行程序(PE,portable executable)。我们知道,可执行程序的本质其实就是按照某一规则排列的机器码,而机器码是指令集相关的。得益于常见的PC机一般是x86/x64的,因此Windows应用程序从指令集的角度看,是完全可以在 x86/x64的Linux机器上直接运行,而不需要硬件层模拟的。


但是为了能够直接加载运行PE文件,需要满足一些ABI兼容。最基本的,Windows PE程序,会假定自己被加载到地址0x400000处,因此Wine实现自己的loader时,需要保证将PE镜像加载到同样的位置。对于静态链接的程序,需要做的事情可能不是太多,但是对于动态链接的程序,Wine需要模仿Windows loader的行为,加载依赖的库,并进行相应的重定位工作。


为了最大程序上减少对二进制层面的依赖,Wine决定实现至少GDI32,KERNEL32,USER32三个动态库,因为其他库都是建立在这三个库的基础之上的。所以理论上来说,除此之外的其他动态库是可以直接使用Windows上面现有的库的,但由于各种原因,Wine还是倾向于尽量实现所有的API。


我们把Wine自己实现的API库称作builtin,把Windows上现成的库称作native。当Wine在加载builtin动态库的同时,还会在内存中建立PE header,用来模仿Windows上的内存布局。更加详细的实现,有机会可以写一篇Wine loader相关的文章来介绍Wine本身、PE 程序和动态库是如何被加载的。


除开loader的功能外,Wine还需要解决进程间通信(IPC)的问题。Wine的实现方式是将所有跨进程的对象和机制,比如GDI对象,比如信号量,全部实现在Wine server中。同时 Wine允许系统运行多个Wine server的实例。


这样存在于同一个Wine server中的对象自然是可以相互通信,好像在同一个空间内;而不同 Wine server下的对象,是相互隔离的,这种架构使得不同容器之间的程序相互没有影响。Wine server的具体实现是通过unix socket,实现了一套LPC机制,完成和API层的交互。


有了以上这些基础,Wine实现起各种功能就可以按部就班了,只需理解Windows下API的行为和含义,然后再重新实现一遍就行了。听起来虽然简单,实际难度不小,特别是一些未公开的行为,必须对整体有相当的了解后才能下手。甚至一些差异,比如UI相关的内容,由于 Windows窗口系统和X在设计哲学上的不同,实现上需要有所舍取。目前Wine支持的平台不仅包括Linux,还包括BSD、Mac OS X和Android。


环境


下面的开发环境都以Deepin为例进行说明。


首先获取代码。Wine官方代码仓库地址为git://source.winehq.org/git/wine.git。如果你想方便打包给别人使用,又不太想折腾打包的一些细节,可以用各个发现版自己维护的Wine。


比如Debian维护的Wine仓库地址为:https://salsa.debian.org/wine-team/wine.git


这里以官方的Wine为例,git clone git://source.winehq.org/git/wine.git。然后安装开发的依赖。为了简单起见,我们只编译32位的Wine,因为64位的Wine只支持64位的PE程序,而目前Windows上仍有大量的程序只提供了32位的版本。


Wine开发系列之——入门_java


接着运行脚本,./configure --with-gnutls --without-hal --without-oss,根据不同的 Wine版本,此时可能会提示不同的feature支持情况。我们可以根据需求,对上面的依赖库和传入的参数进行调整,具体可以查看configure.ac的内容。


Wine的源码比较大,编译有些耗时,可以根据CPU情况增加并行参数,比如make -j8,进行编译。编译完成后,运行 ./wine --version可以查看版本号。如果想安装到系统,可以运行sudo make install,但是注意,安装后可能会修改一些文件的默认打开方式。


使用


运行 ./wine winecfg可以对默认容器进行设置,默认的容器位于home 目录下的.wine,环境变量WINEPREFIX用来修改当前的容器路径。


比如有一个叫demo.exe 的可执行文件,我们想测试能否正常运行。


可以运行WINEPREFIX=~/.demo_exe ./wine demo.exe,home目录下的demo_exe 就会作为其容器目录。


开发


编译过后的Wine源码目录结构如下:


Wine开发系列之——入门_java_02

*目录dlls按照模块存放了所有API的实现。

*目录loader是和Wine启动、加载相关的代码。

*目录programs存放了外部程序的代码,比如注册表管理工具regedit。

*目录server顾名思义,是Wine server的实现。


接下来需要做的就和普通开发没什么两样了。比如说我们发现某个应用存在字体相关的 BUG,可以首先根据经验判断在Windows上,该程序是如何实现的,然后查看对应的实现。例如GDI相关的字体实现,位于dlls/gdi32/font.c和dlls/gdi32/freetype.c。修改完代码后,在所在模块的目录,比如上例就是dlls/gdi32下重新make就可以快速验证了。


对于复杂的问题,不太好直接定位的,可以通过输出日志的方式来调试,环境变量 WINEDEBUG指定了需要输出的日志。更加详细的说明可以查看这篇文章。


有时我们可能需要把复杂的情况简单化,这时候难免会写一些小的demo程序来重现问题。如果不想到Windows上面编译,可以使用mingw直接在Deepin下编译出exe文件。方法很简单,首先安装mingw,sudo apt install mingw-w64,接着正常利用Windows API实现程序,最后利用mingw编译工具链生成文件即可,Makefile示例:


Wine开发系列之——入门_java_03


上面的例子,定义了UNICODE,所以使用的UNICODE 版本的API,入口函数为wmain,-lgdi32 表示需要链接库gdi32。生成出来的hello.exe,可以同时在Windows和Deepin下运行。


其他


本文介绍的内容只涉及到Wine开发的基础,Wine本身还有很多东西值得去探索。比如Wine 是如何使用driver机制让接口和实现分离的,再比如Wine是如何使用纯C实现COM 机制的。虽然Wine的出现已经有一些年头了,但是目前的开发仍然比较活跃,感兴趣的同学可以加入进来,为Linux生态添砖加瓦,让大家能用到更多的优质应用,也算是曲线救国了:)

Wine开发系列之——入门_java_04