【问题标题】:Split functionalities of an application into plugins with Qt使用 Qt 将应用程序的功能拆分为插件
【发布时间】:2018-12-17 05:03:31
【问题描述】:

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

我要提一下,我想放入插件的对象不是QObjects,但如果可以的话 解决方案更简单,它们从QObject 继承是可以接受的。

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

【问题讨论】:

  • 为什么不直接使用standard Qt plugin system
  • 该页面说,[使用高级 API] 为 Qt 本身编写扩展:自定义数据库驱动程序、图像格式、文本编解码器、自定义样式等。 ,对于低级API,它说'不仅Qt本身,Qt应用程序也可以通过插件扩展',这表明高级API仅用于扩展Qt本身,而不是某些应用程序.你还认为标准的 Qt 插件系统可以用于“常规”应用程序插件吗?
  • 我认为是的。我看不出为什么不使用它们(我用于相同的事情)。除非你需要一些 Qt 插件的静态链接。
  • @DmitrySazonov:然后提供一个例子。您不需要像我写的那样详尽,只需说明它有多容易/更好。去领奖吧;我不想把它交给单线答案。
  • 我不在乎声誉。无论如何,它不是几行代码,您可以在 Qt 文档中看到一个示例。最复杂的事情是正确拆分逻辑+部署内容。我写了一条评论,因为我不想准备开箱即用的解决方案,而且这需要时间。我认为你应该尝试自己做,必要时寻求帮助。

标签: c++ qt plugins shared-libraries


【解决方案1】:

虽然我回答了自己的问题,但我更愿意听听别人的问题!

首先,您的插件之间需要有一个通用接口。这是一个例子:

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

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

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

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

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

(你需要extern "C",否则你会遇到QLibrary 的麻烦,因为 C++ 名称修改 ― 见下文)

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

现在是加载部分。最简单的方法是使用 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&lt;MyPlugin&gt; (*)()) 并与auto 一起使用将省去您更换的麻烦 如果您更改 MyPlugin_new 的签名,代码超出了它的需要。

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

最后,如果您的插件集过于多样化而无法通过一个界面来表达,一种解决方案是 只需在插件中定义(可能)多个工厂,每个工厂都返回一个指向 不同类型的界面。

【讨论】:

  • 这是实现您所要求的最佳方式。在设置赏金后,我没有看到回答您自己的问题的意义......
  • 嗯,这就是我找到的方式,为了表明我做了功课,我把它作为答案而不是问题放在了那里,如果答案是唯一的。但是这个过程看起来很复杂,特别是考虑到 Qt 以一种非常高级的方式处理这么多事情,而且这有点难以教授。这可能是一个大问题,但我希望有人会出现并说,“这是一种更简单的方法”。也许我试图得到答案的尝试太笨拙了......
【解决方案2】:

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

【讨论】:

猜你喜欢
  • 2010-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-03
  • 1970-01-01
相关资源
最近更新 更多