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

标签 c++ qt plugins shared-libraries

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

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

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

最佳答案

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

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

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

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

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

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

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

(你需要 extern "C" ,否则你会因为 C++ 名称重整而遇到 QLibrary 的问题 ― 见下文)

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

现在是加载部分。最简单的方法是使用 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 的签名,代码会超出需要的数量.

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

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

关于c++ - 使用 Qt 将应用程序的功能拆分为插件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51246785/

相关文章:

c++ - QT问题中声明StringStream

wordpress - 多合一 seo 包和 qtranslate 标题属性

php - 如何在 Sublime Text 2 中自动检查代码(PHP、Python、HTML、Javascript)中的错误?

C++ 使用指向其他方法的指针在构造函数中分配方法 : what am I doing wrong?

c++ - Qt 4.8.0 - 未列出 MySQL 驱动程序

c++ - 奇怪的多线程问题

c++ - 在带有启用自动换行的标签的 QFormLayout 中,四分之一的布局会变得乏味

javascript - 在 Ckeditor 插件中获取输入的字母

c++ - C++访问类的私有(private)成员

c++ - CStringArray::GetAt(int index) 返回一个常量。为什么?