移植 Gothello
不熟悉的软件一直是 Windows 用户不愿切换到 Linux 的常见借口。为了消除这种理由,人们正在进行大量工作,不是将封闭的 Microsoft 软件引入 Linux,而是将开源 Linux 软件的优势带给 Windows。今天,大多数流行的 Linux 桌面应用程序都有跨平台版本,包括 GIMP 图像编辑器、StarOffice/OpenOffice 办公套件、Mahogany 电子邮件客户端、Amaya WYSIWYG Web 编辑器以及许多 Web 浏览器,包括 Mozilla 和 Opera。这些都可以在 Linux 和 Windows 上运行,有些甚至支持 Macintosh。

在 AI 对弈中,Robin(黑方)被 Windows 版本的 Linux Gothello 打败。
流行的服务器应用程序(如 Apache 和 MySQL)也已被移植到 Windows,但大多数程序员认为移植桌面图形用户界面 (GUI) 软件是最艰巨的任务。有一些技巧可以提供帮助,但我们将展示完成这项任务并不那么困难。在本文中,我们将把游戏 Othello 的 Linux 版本 Gothello 移植到 Windows。在此过程中,我们将了解一些关于编程 GTK+(也称为 GTK)的知识。我们对 Gothello 的代码更改将以不破坏现有 Linux 代码的方式进行。
Othello 是一种介于跳棋和井字棋之间的流行游戏。 Othello 于 1971 年在日本发布,是 1888 年在英格兰发明的游戏 Reversi 的变体。尽管现在有许多开源软件版本的 Othello 可用于 Linux,但到目前为止,没有一个可以在 Windows 下编译。
在 Freshmeat.net 上快速搜索 Linux 应用程序,可以找到许多仅限 Linux 的开源版本 Othello:Darwersi、Desdemona、Gothello、GReversi、QtHello、Rhino 和 xreversi。选择基于具有跨平台支持的 GUI 的软件可以使移植更容易。GTK+ 和 Qt 都已为 Windows 做好准备,但 X11 没有(从我们的列表中排除了 xreversi)。幸运的是,大多数现代 Linux 软件都基于这两个可移植的 GUI 库,它们分别是 GNOME 和 KDE 的基础。对于我们的第一个 Linux 到 Windows 2000 移植项目,我们选择了用 C 编写的基于 GTK+ 的 Gothello。
Gothello 的作者 Osku Salerma 是赫尔辛基大学的计算机科学专业的学生,Linus Torvalds 在创建 Linux 时也曾在那里学习。Salerma 说:“Gothello 的出现是因为我对游戏树感兴趣。AI 是使用带有 Alpha-Beta 剪枝和迭代加深的 Negamax 搜索实现的。”Gothello 使用 AI 在游戏中提供虚拟对手。Salerma 计划在一年左右毕业后从事 UNIX 系统编程工作。
GTK+ (GIMP 工具包) 是一个流行的库,用于在 C 或 C++ 中创建图形用户界面。在 LGPL 下,您可以使用 GTK+ 开发开源软件、自由软件或商业软件。虽然最初是为开发 GIMP 而编写的,但 GTK+ 被用于大量软件项目,包括 GNOME。GTK+ 构建在 GDK(GIMP 绘图工具包)之上,GDK 是平台特定窗口 API(如 Xlib 和 Win32)的可移植包装器。GTK+ 的主要作者是 Peter Mattis、Spencer Kimball 和 Josh MacDonald。
Tor Lillqvist 是芬兰 Tellabs 的一名工程师,他在 1997 年将 GTK+ 和 GIMP 移植到 Windows。Lillqvist 说他移植 GIMP 是为了好玩,这样他就可以在 Windows 中将其与他的 Minolta 幻灯片扫描仪一起使用。虽然他 Windows 端口的当前版本 1.3.0 来自 2000 年 12 月,但有一个活跃的开发者列表,Lillqvist 积极参与其中。“我仍然在尽可能地抽出时间做这件事,”Lillqvist 说。“我是一个三个月大的女婴的父亲。”
在尝试在 Windows 上构建 Gothello 之前,我们需要下载并安装必要的 GTK+ Windows 库。GTK+ 主网站上提供了 GTK+ for Win32 的链接。下载并解压缩 glib、libiconv、gtk+ 和 extralibs 文件。lib 文件是预构建的,因此无需重新制作它们。文件的放置位置是个人喜好问题,但我们喜欢 /code/lib/gtk。我们将下载 Gothello 并将其解压到 /code/oss/gothello 中。请注意,Windows(除了 DOS 命令提示符外)在所有文件路径中都支持正斜杠。避免仅限 Windows 的反斜杠可以避免在 Linux 之间来回切换时出现问题。
有很多关于 GTK+ 编程的书籍。我们有 Havoc Pennington 撰写的 GTK+/Gnome 应用程序开发(New Riders,ISBN 0-7357-0078-8)。但是,并非必须购买书籍,因为 GTK+ 网站提供了 Tony Gale 维护的不错的在线教程。我们将使用他的 “Hello World” 代码示例来测试我们是否正确安装了 GTK+ [清单 1,可在 ftp.linuxjournal.com/pub/lj/listings/issue93/5574.tgz 获取]。注释详尽的代码很好地介绍了 GTK+ 的工作原理。
开源纯粹主义者可能更喜欢选择开源 Windows C++ 编译器,这当然是可行的。Dev-C++ 是一个功能齐全的开源 Windows 集成开发环境 (IDE),我们过去曾成功使用过它。此编译器包含 MinGW 或 Cygwin (gcc) 编译器以及 Insight 或 GDB 调试器。但是,大多数 Windows C++ 程序员使用流行的 Microsoft Visual C++ 编译器。使用哪个 Windows 编译器并不重要,但我们将选择 Visual C++,以便我们可以介绍一些经常困扰经验不足的 VC++ 程序员的陷阱。如果您是习惯于使用 gcc 的 Linux 开源项目负责人,那么这些 VC++ 技巧可以在与 Windows VC++ 程序员一起移植代码时为您节省大量麻烦。
虽然 VC++ 可以使用 Makefiles,但项目文件的顺利处理是其最受欢迎的功能之一。项目文件由 VC++ 自动生成。典型的 VC++ 程序员不知道如何编写 Makefile。项目文件由一个工作区文件(扩展名 .dsw)和一个或多个项目文件(扩展名 .dsp)组成。这些文本文件由 {Project}{Settings} 选项卡式对话框编写,不打算手动编辑——有无数种设置组合。不知道在这里要查找什么是用户不熟悉 VC++ 的最困难的方面之一。一些设置至关重要。请注意,仅仅因为项目可以构建并不意味着设置正确。
现在我们来谈谈设置复杂的 VC++ 项目的核心。请仔细听,因为即使您自己可能永远不会使用 VC++,您几乎肯定也必须告诉您的团队中任何进行 Windows 移植的成员如何正确设置它。VC++ 程序员通常的做法是在项目中设置库的绝对路径,或者配置他们的 VC++ 副本以隐式搜索他们安装库的位置。这将导致项目文件对于除进行移植的人以外的任何人都会失败——这很不酷。
以下是如何在 VC++ 中从 {Project}{Settings} 设置项目中的相对路径
{C/C++}{Preprocessor}{Additional include directories}: ../../lib/gtk/src/gtk+,../../lib/gtk/src/glib, ../../lib/gtk/src/gtk+/gdk {C/C++}{Use runtime library}: Multi-threaded {Link}{Input}{Object/library modules}: glib-1.3.lib gdk-1.3.lib gtk-1.3.lib (added before the others here) {Link}{Input}{Additional library path}: ../../lib/gtk/src/glib,../../lib/gtk/src/gtk+ /gdk,../../lib/gtk/src/gtk+/gtk
在输入这些设置之前,请确保选择了“所有配置”,否则在您构建 Debug 版本后,您将不得不使用 Release 设置再次执行此操作。由于 GUI 应用程序通常是多线程的,因此始终谨慎地启用它。
我们的 hello_gtk 项目现在将构建,但它不会运行。Windows 错误消息抱怨我们尚未安装 GTK+ dll 文件(动态链接库)。将这些文件复制到 Windows system32 文件夹,或者更好的是,为它们创建一个新文件夹并设置 Windows {控制面板}{系统}{高级}{环境变量}{路径}。(注意:路径更改在不重新启动 VC++ 的情况下不会在 VC++ 调试器中生效。)
在完成所有这些工作以正确配置 VC++ 和 Windows 之后,Gothello 移植工作本身并没有那么困难。第一步是处理来自 Windows 下不存在的缺少 UNIX 包含文件的构建错误,例如 <sys/time.h> 和 <unistd.h>。幸运的是,这些缺少的文件中包含的大多数函数和类型定义都可以在 Windows 的某个地方找到。找到正确的 Windows 头文件有点像寻彩蛋游戏。有些文件有文档记录并且很容易找到,而另一些则没有。事实证明,<winsock.h> 是查找未记录的、与 UNIX 兼容的 Windows 调用的好地方。Berkeley 套接字包含许多常见的 UNIX 类型,并且作为 Windows 套接字的一部分实现。

构建和运行 GTK+ “Hello World” 测试应用程序证明 GTK+ 已正确安装。
对 Gothello 的 timer.h 包含的代码修订
#ifdef _WIN32 #include <winsock.h> #else #include <sys/time.h> #include <unistd.h> #endif
Windows 编译器隐式定义系统变量 _WIN32。使用该名称作为条件是使代码自动感知它是在 Windows 还是在其他操作系统下编译的标准方法。这使我们可以在需要时保留良好的 Linux 代码,同时替换特定于 Windows 的代码。
修复 Gothello 的 timer.c 有点困难,因为我们遇到一个在 Windows 中任何地方都未实现的 UNIX 函数
#ifdef _WIN32 #include <sys/timeb.h> #include <sys/types.h> #include <winsock.h> void gettimeofday(struct timeval* t,void* timezone) { struct _timeb timebuffer; _ftime( &timebuffer ); t->tv_sec=timebuffer.time; t->tv_usec=1000*timebuffer.millitm; } #endif
手头有一本 David Curry 撰写的 在 UNIX 系统上使用 C(O'Reilly & Associates,ISBN 0-937175-23-4)的书很有用,它可以告诉我们 gettimeofday() 函数应该做什么。
对 Gothello 的 child.c 包含的代码修订
#ifdef _WIN32 #include <stddef.h> #include <io.h> #include <stdlib.h> #include <winsock.h> #include <stdio.h> #else #include <sys/time.h> #include <unistd.h> #endif
使 child.c 编译并不难。当编译器抱怨 size_t、timeval、fd_set、FD_ZERO、FD_SET、tv_sec、tv_usec、select、NULL 和 _exit 时,我们只需要跟踪相关的 Windows 包含文件。
对 Gothello 的 gtk_main.c 包含的代码修订
#ifdef _WIN32 #include <io.h> #include <fcntl.h> #include <process.h> #else #include <unistd.h> #endif
直到 gtk_main.c 的主体,我们才开始出汗。Windows 没有 fork(),这可能是移植 Linux 应用程序的一个大问题。使用 fork(),Linux 会在内存中创建正在运行的程序的克隆,然后可以分支以执行同步任务。Windows 具有功能较弱的 spawn() 函数,但更喜欢使用线程进行多任务处理。我们将重新连接 Gothello 的 main(),使其认为 Windows 线程是子进程。Gothello 使用管道在父进程和子进程之间进行通信,这是一种标准方法。管道也适用于 Windows 线程。
详细解释此线程(Windows)和 fork(Linux)代码将过于冗长,但该设计基于一个简单的想法,即创建两个线程函数,其中包含子进程和父进程在 fork 之后将执行的代码。注意不要将 gtk_main() 消息泵循环函数放入这些线程中;这样做会阻止 Windows 消息队列并锁定应用程序。如果您正在设计一个考虑移植的 Linux 项目,那么从一开始就使用线程可以为您节省一些麻烦。Linux 线程与 Windows 中发现的线程类似。
如果您按相反的顺序阅读函数,则清单 2 中的代码 [可在 ftp.linuxjournal.com/pub/lj/listings/issue93/5574.tgz 获取] 更容易理解。read_thread() 中的代码需要一些特别的解释。在 Gothello init_all() 内部,我们进行了以下小的修改,注释掉了 Windows 中的 gdk_input_add()
#ifndef _WIN32 gdk_input_add(to_parent[0], GDK_INPUT_READ, read_from_child, NULL); #endif
当我们第一次尝试在 Windows 下运行 Gothello 时,程序锁死了。GTK+ 的 Windows 端口似乎对 gdk_input_add() 不满意。线程比消息循环中的多任务处理更好。我们没有使用 gdk_input_add() 来检测 GTK+ 消息队列内部管道上的输入,而是将其作为独立运行的同步线程来处理。我们还对 child.c 进行了小的更改,以欺骗 select() 调用,该调用在 Linux 中将管道设置为异步运行
#ifdef _WIN32 ret = 0; #else ret = select(to_child + 1, &set, NULL, NULL, &tv); #endif移植 Gothello 是我们第一次使用 GTK+。如果我们首先在 Linux 下构建 Gothello,移植会更容易。我们没有这样做,因为我们想评估对于不熟悉 GTK+ 的 Windows 程序员来说,在看不到的情况下进行移植有多困难。我和我的合作伙伴 Gabrielle Pantera 花了几个星期的时间,在晚上和周末兼职工作。GTK+ 和 Qt 等流行的 Linux GUI 工具包是可移植的,这大大简化了使 Linux 应用程序在 Windows 上运行的任务。
移植的 Linux 桌面 GUI 应用程序可以缓解考虑迁移到 Linux 的 Windows 用户的学习曲线。Linux 用户在使用办公室或朋友的 Windows 机器时也可以访问他们喜欢的应用程序。您甚至可以在另一个操作系统中工作时,为您喜欢的 Linux 开源应用程序编程增强功能。此外,让一百万 Windows C++ 程序员轻松进入开源编程也没有什么坏处。
电子邮件:Robin.Rowe@MovieEditor.com
Robin Rowe (robin.rowe@movieeditor.com) 是 MovieEditor.com 的合伙人,这是一家创建互联网和广播视频技术应用的公司。他曾为 Dr. Dobb's Journal、C++ Report、C/C++ Users Journal 和 Data Based Advisor 撰稿。