【问题标题】:Dynamically load virtual class C++动态加载虚拟类 C++
【发布时间】:2021-04-21 13:19:28
【问题描述】:

我有一个带有虚拟方法foo的类:

class SimpleClass {
 public:
  SimpleClass() = default;
  virtual ~SimpleClass() {
    std::cout << "in virtual destructor\n";
  }
  virtual int foo() {
    std::cout << "in virtual method\n";
    x = 42;
    return x;
  }

private:
  int x;
};

我想动态加载它并创建实例。这是我的做法:

interfaces.h:

#include <string>

class AbstractClass
{
    friend class ClassLoader;
public:
    explicit AbstractClass();
    ~AbstractClass();
protected:
    void* newInstanceWithSize(size_t sizeofClass);
    struct ClassImpl* pImpl;
};

template <class T>
class Class
        : public AbstractClass
{
public:
    T* newInstance()
    {
        size_t classSize = sizeof(T);
        void* rawPtr = newInstanceWithSize(classSize);
        return reinterpret_cast<T*>(rawPtr);
    }
};

enum class ClassLoaderError {
    NoError = 0,
    FileNotFound,
    LibraryLoadError,
    NoClassInLibrary
};


class ClassLoader
{
public:
    explicit ClassLoader();
    AbstractClass* loadClass(const std::string &fullyQualifiedName);
    ClassLoaderError lastError() const;
    ~ClassLoader();
private:
    struct ClassLoaderImpl* pImpl;
};
#include <algorithm>                                                                                                                                                                                                                        
#include <cstdlib>                                                                                                                                                                                                                          
#include <cstring>                                                                                                                                                                                                                          
#include <new>                                                                                                                                                                                                                              
#include <string>                                                                                                                                                                                                                           
                                                                                                                                                                                                                                            
#include <dlfcn.h>                                                                                                                                                                                                                          
                                                                                                                                                                                                                                            
#include "interfaces.h"                                                                                                                                                                                                                     
                                                                                                                                                                                                                                            
typedef void* (*constructor_t)();                                                                                                                                                                                                           
                                                                                                                                                                                                                                            
struct ClassImpl {                                                                                                                                                                                                                          
  void* lib = nullptr;                                                                                                                                                                                                                      
  std::string class_name;                                                                                                                                                                                                                   
};                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                            
AbstractClass::AbstractClass(): pImpl(new ClassImpl()) {}                                                                                                                                                                                   
AbstractClass::~AbstractClass() {                                                                                                                                                                                                           
  if (pImpl->lib) {                                                                                                                                                                                                                         
    dlclose(pImpl->lib);                                                                                                                                                                                                                    
  }                                                                                                                                                                                                                                         
  delete pImpl;                                                                                                                                                                                                                             
}                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                            
std::string CreateSymbolicName(const std::string& class_name) {                                                                                                                                                                             
  size_t pos = 0;                                                                                                                                                                                                                           
  size_t new_pos = 0;                                                                                                                                                                                                                       
  std::string sym_name = "_ZN";                                                                                                                                                                                                             
  while ((new_pos = class_name.find("::", pos)) != std::string::npos) {                                                                                                                                                                     
    sym_name += std::to_string(new_pos - pos);                                                                                                                                                                                              
    sym_name += class_name.substr(pos, new_pos - pos);                                                                                                                                                                                      
    pos = new_pos + 2;                                                                                                                                                                                                                      
  }                                                                                                                                                                                                                                         
  sym_name += std::to_string(class_name.size() - pos);                                                                                                                                                                                      
  sym_name += class_name.substr(pos);                                                                                                                                                                                                       
  sym_name += "C1Ev";                                                                                                                                                                                                                       
  return sym_name;                                                                                                                                                                                                                          
}                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                            
void* AbstractClass::newInstanceWithSize(size_t size_of_class) {                                                                                                                                                                            
  std::string sym_name = CreateSymbolicName(pImpl->class_name);                                                                                                                                                                             
  constructor_t constructor =                                                                                                                                                                                                               
    reinterpret_cast<constructor_t>(dlsym(pImpl->lib, sym_name.c_str()));                                                                                                                                                                   
  void* obj = constructor();                                                                                                                                                                                                                
  void* memory = new char[size_of_class];                                                                                                                                                                                                   
  memcpy(memory, obj, size_of_class);                                                                                                                                                                                                       
  return memory;                                                                                                                                                                                                                            
}                                       

struct AbstractClassList {                                                                                                                                                                                                                  
  AbstractClass* abstract_class = nullptr;                                                                                                                                                                                                  
  AbstractClassList* next = nullptr;                                                                                                                                                                                                        
};                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                            
struct ClassLoaderImpl {                                                                                                                                                                                                                    
  AbstractClassList* head = nullptr;                                                                                                                                                                                                        
  AbstractClassList* cur = nullptr;                                                                                                                                                                                                         
};                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                            
ClassLoader::ClassLoader(): pImpl(new ClassLoaderImpl()) {}                                                                                                                                                                                 
ClassLoader::~ClassLoader() {                                                                                                                                                                                                               
  while (!pImpl->head) {                                                                                                                                                                                                                    
    pImpl->cur = pImpl->head;                                                                                                                                                                                                               
    pImpl->head = pImpl->head->next;                                                                                                                                                                                                        
    delete pImpl->cur;                                                                                                                                                                                                                      
  }                                                                                                                                                                                                                                         
  delete pImpl;                                                                                                                                                                                                                             
}                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                            
std::string parsePath(const std::string& fully_qualified_name) {                                                                                                                                                                            
  std::string result = fully_qualified_name;                                                                                                                                                                                                
  std::replace(result.begin(), result.end(), ':', '/');                                                                                                                                                                                     
  return std::string(std::getenv("CLASSPATH")) + "/" + result + ".so";                                                                                                                                                                      
} 

AbstractClass* ClassLoader::loadClass(const std::string& fully_qualified_name) {                                                                                                                                                            
  auto path = parsePath(fully_qualified_name);                                                                                                                                                                                              
  void* lib = dlopen(path.c_str(), RTLD_NOW|RTLD_LOCAL);                                                                                                                                                                                    
  if (!lib) {                                                                                                                                                                                                                               
    return nullptr;                                                                                                                                                                                                                         
  }                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                            
  if (!pImpl->head) {                                                                                                                                                                                                                       
    pImpl->head = new AbstractClassList();                                                                                                                                                                                                  
    pImpl->cur = pImpl->head;                                                                                                                                                                                                               
  } else {                                                                                                                                                                                                                                  
    pImpl->cur->next = new AbstractClassList();                                                                                                                                                                                             
    pImpl->cur = pImpl->cur->next;                                                                                                                                                                                                          
  }                                                                                                                                                                                                                                         
  pImpl->cur->abstract_class = new AbstractClass();                                                                                                                                                                                         
  pImpl->cur->abstract_class->pImpl->lib = lib;                                                                                                                                                                                             
  pImpl->cur->abstract_class->pImpl->class_name = fully_qualified_name;                                                                                                                                                                     
                                                                                                                                                                                                                                            
  return pImpl->cur->abstract_class;                                                                                                                                                                                                        
}  

foo 是虚拟的之前,它可以正常工作。如果foo 是虚拟的,我在尝试调用它时会收到段错误。 怎么了?

【问题讨论】:

  • 为什么不能只调用会创建所需对象的函数? IE。类似:SomeClass *CreateSomeClass() {return new SomeClass();} 然后你只需使用返回的指针,没有任何“魔法”。
  • 这是徒劳的练习; C++ 虚拟类不能手动加载。编译器生成具有弱链接的 RTTI 位,然后链接器决定是否将它们包含在程序中。因此,任何手动事后加载都将不起作用,因为各种位(RTTI、vtables、析构函数等)已经嵌入到程序中。

标签: c++ unix dll


【解决方案1】:

可以动态加载虚拟子类型,尽管有很多特定于平台的详细信息。

Sam 完全正确,标准没有定义这种行为,因为dlopen 是一个特定于平台的扩展。名称修改以及 RTTI 符号的位置和链接也将是特定于平台的。这并不意味着它不能完成,只是它既不便携也不在语言标准中描述。

你应该:

  1. 为您要创建的东西提供一个抽象基类。

    您的AbstractClass 似乎是您的对象的抽象基类和抽象工厂的组合。不要混淆这些东西。

  2. 有一个单独的标准工厂接口用于返回指向此基的指针。

    这可以只是一个具有标准名称的extern "C" 函数指针,以避免混淆。显然你可以用一个漂亮的对象包装它并将它映射到一个字符串 ID 以便于使用,但要保持实际的动态库接口尽可能简单。

  3. 使用dlsym 为每个加载的库获取这个工厂函数指针

  4. 确保基类 RTTI 要么从可执行文件中导出(通常通过与 -rdynamic 链接),要么发送到您使用 RTLD_GLOBAL 显式加载的单独共享对象中

注意 rustyx 的评论:

编译器生成弱链接的RTTI位,然后链接器决定是否将它们包含在程序中

并不意味着这是不可能。然而,这确实意味着由您决定如何强制编译器和链接器产生您需要的结果。

如果你弄错了一点,你会发现像dynamic_cast 这样的RTTI 功能会以可怕的方式出现错误。您可以使用objdumpnm 找出每个符号的确切位置。

还要注意,只要每个工厂使用相同的分配方案(即new,没有花哨的分配器,放置新的或覆盖的@ 987654331@)。如果你做更复杂的事情,你也需要支持正确销毁这些对象。

【讨论】:

    【解决方案2】:

    问题在于这是未定义的行为,C++ 根本无法以这种方式工作。

      std::string sym_name = CreateSymbolicName(pImpl->class_name);                                                                                                                                                                             
      constructor_t constructor =                                                                                                                                                                                                               
        reinterpret_cast<constructor_t>(dlsym(pImpl->lib, sym_name.c_str()));                                                                                                                                                                   
      void* obj = constructor();                                                                                                                                                                                                                
    

    这似乎是在查找类构造函数的错误名称,并将其称为constructor

    我几乎可以肯定这是未定义的行为,在 C++ 标准中没有定义的方式以这种方式调用 C++ 构造函数。即使不是,对于特定的 C++ 实现,这也是未定义的行为:

    void* obj = constructor();                                                                                                                                                                                                                
    void* memory = new char[size_of_class];                                                                                                                                                                                                   
    memcpy(memory, obj, size_of_class);                                                                                                                                                                                                       
    

    memcpy 仅对 POD 有效。对于非 POD 对象,这是未定义的行为。当类具有虚方法时,观察到的崩溃是未定义行为的明显表现。

    这里还有其他几个问题,但这是导致崩溃的主要原因。复制类实例的唯一定义机制是使用它们的复制构造函数。

    【讨论】:

      猜你喜欢
      • 2013-05-05
      • 2010-09-30
      • 1970-01-01
      • 2012-03-29
      • 2010-09-07
      • 2012-10-05
      • 1970-01-01
      • 1970-01-01
      • 2011-08-01
      相关资源
      最近更新 更多