从共享库加载的类的基类/派生类的安全性

时间:2019-07-01 17:24:11

标签: c++ dlopen

我目前正在尝试在应用程序中构建基本的插件系统。理想情况下,我不想对插件的类信息一无所知,因此在获取适当的内存管理功能时,我将使用基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部分而不是派生部分的内存片段。

是否有更好的方法可以解决此问题,而无需应用程序导入每个插件的标头?

1 个答案:

答案 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,可能还带有一个自定义删除器。