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