自己动手写插件框架(16)

源代码概览

好了,这是我们本系列的最后一篇(希望你不会感到厌烦——不过,这也是最后一次会让你感到厌烦的机会了 ;-P)。现在,让我们亮出我们的源代码。浏览源代码相当复杂,我们也是能给出一些简单代码片段的解释。通过前面的章节可以看出,我们实际已经耗费的大量精力在组织源代码结构上,试图让我们的代码更易于重用。我们将源代码依据其功能分为多个库,这些库又会依赖于其他的库。我们有一个核心库,两个游戏相关库以及游戏本身(可执行文件)和四个插件(三个动态链接库一个静态链接库)。另外,还有两个第三方库——APR 和 boost。最后,还有适用于多个平台的构建系统。

目录结构

整个项目有三个顶级目录:include、lib 和 projects。include 和 lib 是外部库以及包含了项目文件的工程文件。在 include 和 lib 中,又有几个子目录:darwin86、linux32 以及 win32。这些目录包含了平台相关的头文件和静态库。实际上,唯一的库只是 APR,因为我们只使用了 Boost 的头文件,并没有构建 Boost 库。所以在 Boost 目录下只有 include,没有平台相关的目录。

源代码

所有源代码都是在 projects 的子目录中:

  • plugin_framework。这是插件框架的核心库,包含可移植层(DynamicLibrary、Directory 和 Path),插件 API 定义(plugin.h)以及所有支持和帮助类。如果你需要开发自己的基于插件的系统,就必须使用这个库。你可能希望将与 OS 移植相关的类放到独立的库中,或者是将其替换为你自己的 OS 抽象层。我之所以将其放在同一目录中,目的是让其能够自包含(我们会在后面详细解释这个概念)。
  • utils。这是仅有两个函数的小巧的库:calcDistance 和 findClosest。之所以将其作为一个库,是因为它们需要由Hero使用(Hero 是主程序一部分),又要供插件对象使用。所以它需要链接到不同的库中。当然,你可以简单地将其忽略。
  • object_model。这里是所有与游戏相关的 C/C++ 的对象,包括ActorFactory、对象包装器以及供混合插件使用的 ActorBaseTemplate 类。如果你需要将插件框架与你自己的对象结合起来,这里的代码就提供了一个可行的蓝图以及所有你需要的工具代码。注意,你应该对这里的代码仔细挑选后使用。如果你自己构建主系统和所有插件,不需要关系 C 兼容性,那么就可以直接将所有与 C 相关的代码删除掉。如果你只希望使用 C 而不需要提供 C/C++ 混合 API,则直接删除ActorBaseTemplate以及所有对象包装器即可。你应当重新阅读前面的章节,以了解这些代码是做什么用的。插件库很直接。c_plugin 就是 C 插件(MellowMonster),cpp_plugin 就是 C++ 插件(KillerBunny 和 StationarySatan),static_plugin 就是静态链接插件 (FidgetyPhantom),wrapper_plugin 则是混合 C/C++ 插件(GnarlyGolem 和PsychicPiranea)。在大多数情况下,wrapper_plugin是比较好的选择。利用基础模板以及对象模型包装器,你可以很方便地编写插件。与插件相关的缺点几乎是零。
  • great_game。这里就是游戏本身,包含了HeroBattleManager以及main() 函数。从这里你可以了解到如何启动游戏,如何在应用程序中调用PluginManager

上面的部分覆盖了几乎所有的重要代码,解释了这么设计的原因和好处。我试图维护一个干净的、稳定的接口。plugin_framework 和 object_model 库是系统的核心,几乎达到了工业应用的强度(如果说还缺少什么,也就是更多的错误处理和文档支持)。代码的剩余部分则是整个系统。唯一没有仔细说明的是invokeService() 函数调用。不过,我们已经介绍了所有的机制。如果你对此感兴趣,不妨试着完成 great_game.cpp 文件中的 DummyInvokeService() 函数。

外部库

游戏和框架使用了两个外部库:APR 和 boost。Apache Portable Runtime(APR)是一个 C 库。我们主要使用这个库的跨平台基本类型(所有的 apr_xxx_t 类型)和一些(极少的)OS 抽象机制。我们自己则利用 Windows 和 POSIX API 实现了大多数的可移植 OS 抽象层(DynamicLibrary、Directory 和 Path)。Boost 最多使用的是shared_ptr 和 scoped_ptr 模板。这是依赖外部库的非常简单的 ROI(投资回报),特别是对于那些跨平台的系统。事实就是如此。我也曾尝试去除所有依赖,自己实现一个类似 APR 的简单的库,编写自己的可移植基本类型,编写自己的类似 boost 的智能指针。但是,我还是决定保留这些依赖库。这样可以看出,我们的系统是一个严肃的系统,可以满足不同的需求,可以同不同的库结合使用。后面,你将会了解到这么做的好处。

构建游戏和插件

跨平台构建会增加系统复杂性。如果你没有合适的工具,那么处理那些烦人的跨平台问题将消耗你大量时间。

  • Windows。我们在根目录提供了 Visual Studio 2005 解决方案文件。对于 Windows 开发者,99.99% 都会熟悉这个 IDE。Visual Studio Express 则是免费的。有些人认为,跨平台开发意味着使用同一个构建系统。对此,我不赞同。我在使用 cygwin 和 MinGW 的时候有特别不好的经历。当它们工作时,一切都很好;但是如果它们不能很好地工作,那就糟糕了。不过,使用不同的构建系统需要注意要保持所有平台的同步。
  • Mac OS X。在 Mac(包括 Kubuntu),我使用的是 NetBeans 6.0。我无法用语言表达究竟是怎样的感觉。这是一个很不错的 C++ 开发环境。它同 Visual Studio 很像。它很像 Eclipse CDT(另一个 C/C++ 开发工具)。唯一不好的地方(Visual Studio 没有这个问题)是当你设置项目属性,例如预处理器符号、编译器标记等,你必须为每个项目分别设置,即使你有 20 或 200 项目都需要设置为同样的值。另外,NetBeans 的调试器比较弱。它使用的是 GDB,并不是一个主要的 C++ 调试器(你试过在 GBD 中查看std::map的值吗?)NetBeans 6.0(包括更早的版本)有一个好处是。它使用 Makefile 作为底层的构建系统。这意味着你可以将源代码分发给任何人(就像我现在这么做的),他们只要做 ./configure; make,然后就可以运行你的系统啦。如果你不是单纯为 Windows 开发系统,我建议使用 NetBeans 6.0。
  • Linux (Kubuntu 7.10)。简单一句话:我成功了!故事的开始是,我本来以为我使用 NetBeans 可以直接使用 Mac 上面设置好的工程,至少应该差不多一样,没什么大问题。想了又想,我在 Kubuntu 7.10 上面安装了 NetBeans 5.5.1。我很开心地尝试打开我的项目。不对!结果证明,现在只安装了 Java 环境!好吧!我开始更新,但是 C++ 安装包不可用。好吧,继续。我试着从 NetBeans.org 下载源代码,自己编译,结果我得安装 JDK1.6 update 3。我再试着用 apt-get 获取,然后 Kubuntu 问我要安装盘。但是,我是在一个虚拟机中进行测试的,直接使用的是镜像文件安装。我已经直接删除了 4+ GB 的镜像文件,因为我觉得用不到它了(为什么我不能从网上更新?)。我不想再去下载 4+ GB 的文件了,所以我放弃了 NetBeans,转而使用 KDevelop。这是 KDE 原生 IDE,专门用于开发 C++,使用的是 automake/autoconf 作为底层构建系统。我有差不多 10 个项目,但是我还是想把全部工程都提取出来,看看能不能简单地创建一个拥有多个子项目、基于 automake 的良好的解决方案。不幸的是,我失败了。我不记得其中的细节,但是我失败了。我记得它花费了几分钟去定位 Automake manager(在 IDE 右侧边栏的一个页面,如果那些重要的标签页都在左侧,你可以将其移动过去)。我决定回归命令行。毕竟,现在是 Linux。人们可以直接使用命令行,而不是 GUI IDE。我使用 NetBeans 生成的基于 Makefile 的项目,开始配置和构建。我必须要使用好几遍“sed s/GNU-MacOSX/GNU-Linux-x86/g -i”这个命令,还有一些奇技淫巧,比如替换链接器标志位(事实证明,这些标志位在 Mac 和 Linux 上是不一样的)以及更多操作。所有项目都编译好了,所有插件也都链接成功了。只有 great_game 自己链接失败,因为 APR 失败了(一般是由于找不到 pthreads)。当然,这不大可能是 pthreads 没有正常安装。我试着使用 apt-get 看看是不是缺少某些开发库。终于,我找到原因了!我的预编译的 APR 库(实际上是两个库)是在一个不同的 Linux 发行版上编译的!于是,我下载了 APR 源代码,从源代码开始构建。这次构建成功了,但是还是有相同的链接错误。现在,我很沮丧,有点恼火。我重新下载了 Kubuntu-7.10 那巨大的 .iso 镜像文件,重新挂载。事情从此峰回路转。我使用 apt-get 成功安装了 JDK 1.6 update 3,从 NetBeans.org 安装了 NetBeans 6.0 C++ 包。它在桌面上生成了一个图标,让我能够直接启动 NetBeans。它成功加载了 Mac 项目文件。现在,我需要做一些小的修正,以及一些大的修改。事实证明,我必须显式地向项目添加 pthreads,而在 Mac 上则不需要。终于,这个统一的构建系统成功了。最后,一切都工作的很好(好吧,我承认,由于我把插件目录工错了,所以也出现了一些小错误)。好了,现在一切正常了!

Mac 和 Linux 构建系统使用各自项目目录下的相同的 Makefile。这个共享的 Makefile 包含了从其子目录 nbproject 找到的其他文件。这其中的有些文件在 Mac 和 Linux 上是不同的。我希望能够直接使用两个构建系统,所以我创建了两个子目录 nbproject.mac 和 nbproject.linux。这意味着,如果你在 Mac 或者 Linux 上使用 NetBeans 直接打开项目会发生错误。你需要将适当的子目录(依据你的系统)更名为 nbproject。如果你有八个不同的工程,这可有的受了。所以我在根目录下提供了一个简单的 shell 脚本,build.sh。你需要结合 “mac” 或者“linux” 使用,它会将每个项目的 nbproject.mac 或者 nbproject.linux 复制命名为 nbproject,然后运行 make。你只需要使用一次,然后就可以在 NetBeans 中打开项目了。

构建外部库

好消息!你不需要单独构建外部库。我已经为全平台的 APR 提供了预编译的 static 链接库,已经我们自己使用的 Boost 的子集。

当然,现在又是讲故事的时间。跨平台开发从来都是一个困难的工作,所以很多公司都是从一个平台开始,然后等到发展壮大之后再移植到其他平台。Numenta(作者所在的公司)却有一个不同的想法。他们决定从一开始就跨平台,但是只做 Mac 和 Linux。这可能是绝无仅有的这么做的公司了!当我们开始我们的研究性平台时,最大的抱怨是缺少对 Windows 平台的支持。我仅仅完成了 NuPIC 插件框架的第二阶段工作。这是我之前给大家展示的插件框架的原型。我决定把它移植到 Windows 平台。我是唯一拥有 Windows 开发经验的工程师(其他人都是 UNIX 开发者),大多数时间我都希望能够在 Visual Studio 中调试,忘记 gdb。我们的代码库是高纯度的标准 C++,极少有直接的系统调用,不过还是有不少 UNIX 路径问题。但这些都不难解决,直到我遇到了 Boost。当时,我们使用了比先前提到的更多的 Boost 库进行构建:boost::file_systemboost::regexboost::serialization。我为此头疼了几天,在网上到处寻找解决方案,试图去修改那些奇怪的符号,但我就是不能用 VC++ 2005 构建这些库。所以,我不得不基于 Win32 和 POSIX API 编写 OS 抽象层,来替代这个 Boost 库。Directory 和 Path 类就是我此阶段的成果。

结语

我已经完成了。感谢您花费这么多时间来阅读本系列文章。我确信,很有可能你已经晕头转向。这些内容并不简单。我希望这个插件框架会对你有用,无论是框架本身,还是我在开发框架时涉及到的一些思想。我希望这些都能够对您的系统有所帮助!

3 Comments

  1. 吉人天相 2015年4月21日
  2. nigelyao 2015年6月18日
    • 豆子 2015年6月29日

Leave a Reply