就像标题所说的一样,我想将Qt应用程序的某些部分拆分为插件,所以我 可以在运行时添加新功能。理想情况下,插件应单独编译并放入 插件专用路径;应用程序启动时,已安装的扩展名将自动 已加载,也可以根据用户要求随时重新加载。
我应该提到我要放入插件的对象不是QObject
,但是如果可以
解决方案更简单,可以从QObject
继承它们。
我该怎么做?我想要最简单的便携式解决方案,不需要其他任何东西 比Qt(无外部依赖项)。
答案 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的类,可以完成您要实现的目标。