如何通过dlopen和dlsym调用无源码第三方C++库ClassLibrary.so中的ClassTest类?
如何用dlopen/dlsym调用无源码的C++动态库中的类
核心结论
可以用dlopen/dlsym实现调用,但因为没有源码,需要绕过C++名字修饰和类实例化的限制,这是一种hack式的做法,兼容性有限,但能满足客户要求的动态加载需求。
关键步骤
1. 获取类相关函数的 mangled name
C++编译器会对类的成员函数、构造/析构函数进行名字修饰(name mangling),直接用ClassTest::doSomething这类名字调用dlsym会找不到符号,必须先获取修饰后的名字:
- 在Linux下,用
nm -D ClassLibrary.so查看动态库的导出符号,找到和ClassTest相关的条目(比如_ZN9ClassTestC1Ev对应默认构造函数,_ZN9ClassTest11doSomethingEi对应void doSomething(int)成员函数) - 如果库未被stripped(符号表保留),也可以用
objdump -tC ClassLibrary.so,-C参数会自动还原修饰后的名字,方便识别对应关系。
2. 处理类的实例化
没有源码无法直接用new ClassTest()创建对象,需要手动分配内存并调用构造函数:
- 必须知道
ClassTest的内存大小:如果没有源码,可通过逆向工程分析,或借助库中已有的实例创建接口间接推导(风险较高) - 用
operator new分配对应大小的内存,再通过dlsym拿到的构造函数指针初始化对象。
3. 编写调用代码
示例代码如下(需替换为实际的mangled name和类大小):
#include <dlfcn.h> #include <iostream> // 仅声明类,无需定义 class ClassTest; int main() { // 打开动态库 void* handle = dlopen("./ClassLibrary.so", RTLD_NOW); if (!handle) { std::cerr << "dlopen失败: " << dlerror() << std::endl; return 1; } // 获取默认构造函数指针 typedef void (*Constructor)(void*); Constructor ctor = reinterpret_cast<Constructor>(dlsym(handle, "_ZN9ClassTestC1Ev")); if (!ctor) { std::cerr << "获取构造函数失败: " << dlerror() << std::endl; dlclose(handle); return 1; } // 获取成员函数指针 typedef void (*DoSomethingFunc)(ClassTest*, int); DoSomethingFunc do_something = reinterpret_cast<DoSomethingFunc>(dlsym(handle, "_ZN9ClassTest11doSomethingEi")); if (!do_something) { std::cerr << "获取成员函数失败: " << dlerror() << std::endl; dlclose(handle); return 1; } // 获取析构函数指针 typedef void (*Destructor)(void*); Destructor dtor = reinterpret_cast<Destructor>(dlsym(handle, "_ZN9ClassTestD1Ev")); if (!dtor) { std::cerr << "获取析构函数失败: " << dlerror() << std::endl; dlclose(handle); return 1; } // 分配内存(替换为实际的ClassTest大小) ClassTest* obj = static_cast<ClassTest*>(operator new(64)); // 调用构造函数初始化对象 ctor(obj); // 调用成员函数(第一个参数为对象指针,即this) do_something(obj, 42); // 清理资源 dtor(obj); operator delete(obj); dlclose(handle); return 0; }
4. 编译运行
编译时必须链接dl库:
g++ your_code.cpp -ldl -o class_test_runner ./class_test_runner
重要风险与限制
- 名字修饰依赖编译器:不同编译器(GCC/Clang/MSVC)的名字修饰规则完全不同,同一编译器不同版本也可能有变化,跨环境兼容性差
- 类大小必须准确:内存分配大小错误会直接导致程序崩溃或内存损坏
- stripped库无法使用:如果动态库被移除了符号表(经
strip命令处理),dlsym无法找到任何符号,这种方法完全失效 - 成员函数调用必须传对this指针:C++成员函数的第一个隐式参数是对象指针,调用时必须正确传递,否则会出现未定义行为
内容的提问来源于stack exchange,提问作者Umar Karim
相关产品推荐
相关产品推荐

