插件系统分成两个主要部分:插件加载器和插件。为简单起见,我们将整个应用程序设计为一个插件加载器。下面,我们将给出一个实例,来阐述如何在 Qt 4 中开发应用程序插件。
我们的例子很简单:这是一个类似游戏的系统,由插件提供游戏中出现的各种怪物(类似于我们前面设计插件框架时所提供的程序)。游戏启动时,将这些插件加载到系统中,并与主程序进行交互。我们将从最简单的情形开始着手,一步步丰富我们的游戏示例。
插件与主程序应当有一种约定。通过这种约定,主程序知道插件提供了哪些功能。也就是说,插件不是随意编写的,而是应该应该满足一些接口。主程序通过这些接口与插件交互,无需知道插件本身具体是如何实现这些接口,只需知道,插件的确实现了这些接口。这也就是基于接口编程的优势之一。
下面,我们就先建立一个最简单的工程作为主程序,名字就叫作 GameSystem,其 pro 文件如下所示:
QT += core QT -= gui TARGET = GameSystem CONFIG += console TEMPLATE = app DESTDIR = .. MOC_DIR = sys OBJECTS_DIR = sys SOURCES += main.cpp HEADERS += \ monsterinterface.h
我们可以在 Qt Creator 中新建工程。不过,如果你使用 Qt Creator 这里 IDE,需要注意的是,将 Project 的 Build Directory 设置一下。比如豆子设置为 E:\Qt\game\build\sys。注意,我们为简单起见,使用的是 Qt 命令行工程,没有添加 GUI 部分,因此我们的 .pro 文件中有这么一行:QT -= gui。
main.cpp 使用默认的:
#include <QtCore> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); return app.exec(); }
下面是 monsterinterface.h 的代码,这就是我们前面所说的插件都需要实现的这个约定(接口):
#ifndef MONSTER_H #define MONSTER_H #include <QtPlugin> #include <QString> class MonsterInterface { public: virtual ~MonsterInterface() {} virtual QString name() const = 0; }; Q_DECLARE_INTERFACE(MonsterInterface,"org.galaxyworld.plugins.MonsterInterface/1.0") #endif // MONSTER_H
这个接口值得我们说明一下。首先,C++ 没有类似 Java 或者 C# 的专门的 interface,因此,我们使用的是 class 来模拟接口。这样做的要求是,接口应当有一个 virtual 的析构函数,以便实现了接口的子类在析构的时候能够自动调用父类的析构函数,从而避免内存泄露。第二,接口中所有的函数都应当是纯虚函数,也就是 virtual 的并且要有 = 0。这是 C++ 的要求,与 Qt 无关。最后,注意,文件末尾处有一个Q_DECLARE_INTERFACE
宏,用于告诉 Qt 的元数据系统 metadata system,这是一个接口。这个宏有两个参数,第一个是接口类的名字,这里就是 MonsterInterface;第二个是接口的标识符,是一个唯一的字符串,为保证唯一性,通常采取域名倒写的形式,最后用 / 分隔开版本号(关于域名倒写,可以参考 Java package 的命名规范,简而言之,就是把你的域名倒过来书写)。这样,我们就完成了接口的编写。
我们的接口很简单,出去虚析构函数,只有一个函数是子类必须实现的:QString name() const
。顾名思义,也就是返回这个插件的名字。当然,在真正使用的系统中,这个接口可能相当复杂,并且像 name 或者 version 这种属性,一般不大会放在代码中,而是在配置文件中(特别是版本号这种经常会修改的常量)。
现在,你可以编译一下这个工程。显然,你不会得到任何有意义的东西———仅仅是能够测试你的配置是否正确,代码是否能通过编译。编译之后,如果使用了影子构建,你应该在于 GameSystem 文件夹平级处得到一个 build 文件夹,里面就是我们编译出的 GameSystem.exe (Windows)。
下一节,我们将继续介绍如何基于这个 MonsterInterface 开发一个可以使用的插件。