gpt4 book ai didi

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

转载 作者:可可西里 更新时间:2023-11-01 16:06:09 26 4
gpt4 key购买 nike

就像标题所说的那样,我想将我的 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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com