我目前正在尝试在应用程序中构建基本的插件系统。理想情况下,我不想对插件的类信息一无所知,因此在获取适当的内存管理功能时,我将使用基Plugin
类,例如:
void* handle = nullptr;
if (!(handle = dlopen(path.c_str(), RTLD_LOCAL | RTLD_NOW))) {
throw std::runtime_error("Failed to load library: " + path);
}
using allocClass = Plugin *(*)();
using deleteClass = void (*)(Plugin *);
auto allocFunc = reinterpret_cast<allocClass>(
dlsym(handle, allocClassSymbol.c_str()));
auto deleteFunc = reinterpret_cast<deleteClass>(
dlsym(handle, deleteClassSymbol.c_str()));
if (!allocFunc || !deleteFunc) {
throw std::runtime_error("Allocator or deleter not found");
}
return std::shared_ptr<Plugin>(
allocFunc(),
[deleteFunc](Plugin *p){ deleteFunc(p); }
);
插件侧的alloc / delete函数基本上只是调用new或delete,例如:
extern "C" {
TestPlugin *allocator() {
return new TestPlugin();
}
void deleter(TestPlugin *ptr) {
delete ptr;
}
}
我的问题基本上是关于这种类型不匹配的安全性-插件使用自己的类型,但是加载程序将其声明为基本类型。经过有限的测试,没有什么出现会出错,但是我不确定在幕后是否删除了Plugin部分而不是派生部分的内存片段。
是否有更好的方法可以解决此问题,而无需应用程序导入每个插件的标头?
答案 0 :(得分:-1)
不考虑将void*
重新解释广播到函数指针的有效性,通过另一种类型的指针调用函数是UB。类型是否相关无关紧要,例如Plugin* (*)()
和TestPlugin* ()
。 UB也可以通过不同类型的指针访问一种类型的对象,即使这些类型分别是派生类和基类也是如此。例如
Derived derived;
Base *base;
base = &derived; // OK, will access base subobject
base = reinterpret_cast<Base*>(&derived); // not OK, will access derived object
如果仅涉及一个非虚拟继承,您的代码很可能 与许多(但不是全部)流行的编译器一起工作。但是,无需冒险。只要让该库仅公开基于Plugin*
的接口,就可以轻松解决所有这些问题。
extern "C" {
Plugin *allocator() {
return new TestPlugin();
}
void deleter(Plugin *ptr) {
delete ptr;
}
}
此实现要求插件具有虚拟析构函数。无论如何,这可能是一个好主意。 OTOH确实不需要删除器功能,客户端只需调用delete Plugin
。更好的是,可以从分配器返回一个shared_ptr,可能还带有一个自定义删除器。