虽然我回答了自己的问题,但我更愿意听听别人的问题!
首先,您的插件之间需要有一个通用接口。这是一个例子:
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<MyPlugin> (*)()) 并与auto 一起使用将省去您更换的麻烦
如果您更改 MyPlugin_new 的签名,代码超出了它的需要。
此方法只是尝试将文件加载为库(无论它是否是有效的库文件!)和
尝试解析所需的函数,如果我们没有处理
有效的库文件或请求的符号(我们的函数)不存在。请注意,因为我们执行
直接在动态库中搜索,我们必须知道该库中实体的确切名称。
因为 C++ 会破坏名称,并且这种破坏取决于实现,所以唯一明智的
事情是使用extern "C" 函数。不过别担心:extern "C" 只会阻止
该函数的重载,但除此之外,所有 C++ 都可以在该函数内部使用。还有,即使
虽然工厂函数不在任何命名空间内,但它不会与其他工厂发生冲突
其他库中的函数,因为我们使用显式链接;这样,我们可以有
MyPlugin_new 来自插件 A 和 MyPlugin_new 来自插件 B,它们将分开居住
地址。
最后,如果您的插件集过于多样化而无法通过一个界面来表达,一种解决方案是
只需在插件中定义(可能)多个工厂,每个工厂都返回一个指向
不同类型的界面。