使用Qt将应用程序的功能拆分为插件

时间:2018-07-09 13:29:09

标签: c++ qt plugins shared-libraries

就像标题所说的一样,我想将Qt应用程序的某些部分拆分为插件,所以我 可以在运行时添加新功能。理想情况下,插件应单独编译并放入 插件专用路径;应用程序启动时,已安装的扩展名将自动 已加载,也可以根据用户要求随时重新加载。

我应该提到我要放入插件的对象不是QObject,但是如果可以 解决方案更简单,可以从QObject继承它们。

我该怎么做?我想要最简单的便携式解决方案,不需要其他任何东西 比Qt(无外部依赖项)。

2 个答案:

答案 0 :(得分:5)

尽管我回答了自己的问题,但我很想听听别人的声音!

首先,您需要在插件之间有一个公共接口。这是一个示例:

class MyPlugin
{
public:
    virtual ~MyPlugin() {}  // Needs to be virtual. Important!

    // Put here your method(s)
    virtual void frobnicate() = 0;
};

但是不要这样命名您的界面。如果您的插件代表视频编解码器,请命名 例如,“ VideoCodec”。有些人更喜欢在接口名称前加上“ I”(例如IVideoCodec)。 另外,有些人会告诉您有调用受保护的虚拟机的公共方法,但这不是 在那里绝对必要。

为什么要使用接口?那是因为这是应用程序不知不觉使用插件的唯一方法 事先上课。这意味着,因为应用程序不知道 类,插件必须允许通过 factory 创建插件组件。实际上,唯一的 需要声明的函数是一个工厂函数,它创建“插件”的新实例。 此工厂函数可以这样声明:

extern "C" std::unique_ptr<MyPlugin> MyPlugin_new();

(您需要extern "C",否则将由于C ++名称修改而在QLibrary上遇到麻烦― 见下文)

工厂函数不必没有参数,但是参数对于所有类型都必须有意义 的插件。这可以是哈希表或包含常规配置信息的文件,或者 甚至更好,例如,用于配置对象的接口。

现在正在加载零件。最简单的方法是使用初始化为插件的QDirIterator 目录,遍历所有文件并尝试加载它们。类似于...

void load_plugins_from_path(const QString &plugin_dir)
{
    QDirIterator it(plugin_dir, QDir::Files, QDir::Readable);

    while (it.hasNext()) {
        try_load_plugin(it.next());
    }
}

(它的写法就像是一个函数,但它应该是一个方法)

请勿尝试通过扩展名或使用QDir::Executable标志来过滤文件: 会不必要地降低程序的可移植性-每个操作系统都有其自己的文件扩展名,并且QDir::Executable仅在unices上运行(可能是因为Windows上没有exec位)。 在这里,方法load_plugins_from_path只是从一个给定的路径加载插件;来电者可以 在包含搜索插件的所有路径的列表的元素上调用该方法 例。 try_load_plugin可以这样定义:

void try_load_plugin(const QString &filename)
{
    QLibrary lib(filename);

    auto factory = reinterpret_cast<decltype (MyPlugin_new) *>(lib.resolve("MyPlugin_new"));

    if (factory) {
        std::unique_ptr<MyPlugin> plugin(factory());

        // Do something with "plugin", e.g. store in a std::vector
    }
}

decltype用于MyPlugin_new,因此我们不必指定其类型 (std::unique_ptr<MyPlugin> (*)())并与auto一起使用将为您节省更改的麻烦 如果您更改MyPlugin_new的签名,则代码将超出其本应的范围。

此方法只是尝试将文件加载为库(无论它是否是有效的库文件!),然后 尝试解析所需的函数,如果我们不处理{ 有效的库文件或请求的符号(我们的函数)不存在。请注意,因为我们这样做 直接在动态库中搜索,我们必须知道该库中实体的确切名称。 因为C ++破坏了名称,并且这种破坏取决于实现,所以唯一明智的做法是 事情是使用nullptr函数。不过请不要担心:extern "C"只会阻止 该函数的重载,但否则所有C ++都可以在该函数内部使用。而且,即使 尽管工厂函数不在任何名称空间中,但不会与其他工厂冲突 其他库中的函数,因为我们使用了显式链接;这样,我们可以拥有 extern "C"来自插件A,MyPlugin_new来自插件B,它们将分别存在 地址。

最后,如果您的一组插件过于多样化而无法用一个接口表示,那么一种解决方案是 只需在插件内部定义(可能)多个工厂,每个工厂都会返回一个指向 不同类型的界面。

答案 1 :(得分:3)

Qt已经有一个名为QPluginLoader的类,可以完成您要实现的目标。