C++ dlopen mini HOWTO: How to dynamically load C++ functions and classes using the dlopen API.
写插件或模块的时候可能需要在运行时加载库并使用其函数。c语言加载一个库很简单,调dlopen、dlsym和dlclose就行了,但c++有Name Mangling,且c++标准没有定义mangling的标准所以不同编译器都用了自己的算法,所以需要通过extern "C"
来声明被加载的函数,然后像c语言一样通过dlsym。
例子:
main.cpp
#include <dlfcn.h>
#include <iostream>
int main() {
using std::cerr;
using std::cout;
cout << "C++ dlopen demo\n\n";
// open the library
cout << "Opening hello.so...\n";
void *handle = dlopen("./hello.so", RTLD_LAZY);
if (!handle) {
cerr << "Cannot open library: " << dlerror() << '\n';
return 1;
}
// load the symbol
cout << "Loading symbol hello...\n";
typedef void (*hello_t)();
// reset errors
dlerror();
hello_t hello = (hello_t)dlsym(handle, "hello");
const char *dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol 'hello': " << dlsym_error << '\n';
dlclose(handle);
return 1;
}
// use it to do the calculation
cout << "Calling hello...\n";
hello();
// close the library
cout << "Closing library...\n";
dlclose(handle);
}
hello.cpp
#include <iostream>
extern "C" void hello() { std::cout << "hello" << '\n'; }
extern "C"
和带大括号的extern "C" { … }
区别:第一种形式的声明具有extern链接和C语言链接;第二个仅影响语言链接。所以下面两种声明是等效的:
extern "C" int foo;
extern "C" void bar();
和
extern "C" {
extern int foo;
extern void bar();
}
dlopen API另一个问题是只能加载函数,不能加载,因为我们需要类的实例,而不仅仅是指向函数的指针。解决方法是把类的创建过程也api化了,即工厂模式。
例子:use a generic polygon class as interface and the derived class triangle as implementation
polygon.hpp
#ifndef POLYGON_HPP
#define POLYGON_HPP
class polygon {
protected:
double side_length_;
public:
polygon() : side_length_(0) {}
virtual ~polygon() {}
void set_side_length(double side_length) {
side_length_ = side_length;
}
virtual double area() const = 0;
};
// the types of the class factories
typedef polygon *create_t();
typedef void destroy_t(polygon *);
#endif
main.cpp
#include "polygon.hpp"
#include <dlfcn.h>
#include <iostream>
int main() {
using std::cerr;
using std::cout;
// load the triangle library
void *triangle = dlopen("./triangle.so", RTLD_LAZY);
if (!triangle) {
cerr << "Cannot load library: " << dlerror() << '\n';
return 1;
}
// reset errors
dlerror();
// load the symbols
create_t *create_triangle = (create_t *)dlsym(triangle, "create");
const char *dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol create: " << dlsym_error << '\n';
return 1;
}
destroy_t *destroy_triangle =
(destroy_t *)dlsym(triangle, "destroy");
dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
return 1;
}
// create an instance of the class
polygon *poly = create_triangle();
// use the class
poly->set_side_length(7);
cout << "The area is: " << poly->area() << '\n';
// destroy the class
destroy_triangle(poly);
// unload the triangle library
dlclose(triangle);
}
triangle.cpp
#include "polygon.hpp"
#include <cmath>
class triangle : public polygon {
public:
virtual double area() const {
return side_length_ * side_length_ * sqrt(3) / 2;
}
};
// the class factories
extern "C" polygon *create() { return new triangle; }
extern "C" void destroy(polygon *p) { delete p; }
由此可见c++的插件系统基本都是基于这样的原理:
- 定义纯虚基类作为interface。
- 把实现类封装为dll文件,用LoadLibrary运行时载入。
- 扩展接口不要用c++,用纯c。即通过C API获取插件对象实例。因为一个基本的常识,C++ ABI在不同编译器、不同编译器版本之间有差异,而的C ABI是稳定的。