分享

如何在运行时加载C++函数和类

 星光闪亮图书馆 2020-04-14

Problem
有些时候你想在运行时加载一个lib或者function or class,这种事情经常发生在你开发一个plugin或者module时遇到。

在C语言里,你可以轻松的利用dlopen, dlsym, dlclose来做到,但是在C++的世界里却没那么简单了。困难就在C++语言的name mangling上,还有一部分就是dlopen函数是用纯C语言写的,不提供load classes功能。

在解析如何load function和class在c++语言中之前,还是线弄清问题吧---name mangling。

C++ Name Mangling
在C++程序里(或lib or object file中),所有的non-static functions都是以二进制文件symbols来表示。这些symbols都是些特殊的文本字符串,都是唯一的在程序中,lib中活着object file中来标示一个文件。

然而在C语言中,函数的symbol名字就是函数名字本身,例如strcpy的symbol就是strcpy,所以在C语言中不会有中non-static函数出现重名情况。

由于C++有很多C语言没有的功能,例如class,函数的overloading,异常处理等等,所以symbol不可能简单的以函数名来定。为了解决这个问题,C++提出了name mangling,这个name mangling的功能就是把function的名字转换成只有compiler知道的奇怪字符串,利用该函数所有的已知信息,如果函数参数的类型,个数,函数等等,所有如果函数名字为foo(int, char),利用name mangling之后,其名字可能是foo_int_char或者其他字符串也说不定。

现在的问题是在C++标准里(ISO14882)中还没有定义这个function name是被怎样的mangled的,每个编译器都有自己的一套方法。

Classes
另外一个问题就是dlopen函数仅支持load 函数,不支持load class。

很明显的是如果你想使用该class,就需要new instance出来。

Solution
extern “C”
C++有个特殊的关键字来声明函数用C的方式来绑定——extern “C”,在C++中函数如果用extern “C”在前面声明的话,就表示该函数的symbol以C语言方式来命名。

所以只有非成员函数可以用extern “C”来声明,而且他们不能被重载。 
虽然很局限,但是这样足够利用dlopen来运行时调用function了。需要强调的是用extern “C”不是就不可以在function内写C++ 代码,依然可以调用class和class的function的。

Loading functions
用dlsym,加载C++函数就像加载C函数一样,你要加载的函数必须要用extern “C”来声明,避免name mangling。 
Example 1: load a function

  1. #include <iostream>
  2. #include <dlfcn.h>
  3. int main() {
  4.     using std::cout;
  5.     using std::cerr;
  6.     cout << "C++ dlopen demo\n\n";
  7.     // open the library
  8.     cout << "Opening hello.so...\n";
  9.     void* handle = dlopen("./hello.so", RTLD_LAZY);
  10.     if (!handle) {
  11.         cerr << "Cannot open library: " << dlerror() << '\n';
  12.         return 1;
  13. }
  14.     // load the symbol
  15.     cout << "Loading symbol hello...\n";
  16.     typedef void (*hello_t)();
  17.     // reset errors
  18.     dlerror();
  19.     hello_t hello = (hello_t) dlsym(handle, "hello");
  20.     const char *dlsym_error = dlerror();
  21.     if (dlsym_error) {
  22.         cerr << "Cannot load symbol 'hello': " << dlsym_error <<
  23.             '\n';
  24.         dlclose(handle);
  25.         return 1; }
  26.     // use it to do the calculation
  27.     cout << "Calling hello...\n";
  28.     hello();
  29.     // close the library
  30.     cout << "Closing library...\n";
  31.     dlclose(handle);
  32. }


hello.cpp

  1. #include <iostream>
  2. extern "C" void hello() {
  3.     std::cout << "hello" << '\n';
  4. }



注意,以下两种方式是等价的

  1. extern "C" int foo
  2. extern "C" void bar();

  1. extern "C" {
  2.    extern int foo;
  3.    extern void bar();
  4. }



但是定义变量却是不等价的

load classes
加载类比加载函数还是有点困难的。

我们不能通过new instance来实例一个class在执行的时候,因为某些时候我们根本不知道要记载的类的名字,

怎么解决呐?我们可以通过多态性来解决。开始我们定义一个base interface,声明一些抽象方法,子类继承并实现这些方法。

所以现在我们需要定义两个helper 函数,用extern “c”声明,一个用来new一个class实例,返回class的指针,一个用来销毁这个指针。

基于此,我们就可以用dlsym来调用这两个helper函数了。

Example 2: loading class 
main.cpp

  1. #include "polygon.hpp"
  2. #include <iostream>
  3. #include <dlfcn.h>
  4. int main() {
  5.     using std::cout;
  6.     using std::cerr;
  7.     // load the triangle library
  8.     void* triangle = dlopen("./triangle.so", RTLD_LAZY);
  9.     if (!triangle) {
  10.         cerr << "Cannot load library: " << dlerror() << '\n';
  11. return 1;
  12. }
  13.     // reset errors
  14.     dlerror();
  15.     // load the symbols
  16.     create_t* create_triangle = (create_t*) dlsym(triangle, "create");
  17.     const char* dlsym_error = dlerror();
  18.     if (dlsym_error) {
  19.         cerr << "Cannot load symbol create: " << dlsym_error << '\n';
  20.         return 1;
  21. }
  22.     destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
  23.     dlsym_error = dlerror();
  24.     if (dlsym_error) {
  25.         cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
  26. return 1;
  27. }
  28.     // create an instance of the class
  29.     polygon* poly = create_triangle();
  30.     // use the class
  31.     poly−>set_side_length(7);
  32.         cout << "The area is: " << poly−>area() << '\n';
  33.     // destroy the class
  34.     destroy_triangle(poly);
  35.     // unload the triangle library
  36.     dlclose(triangle);
  37. }


polygon.hpp

  1. #ifndef POLYGON_HPP
  2. #define POLYGON_HPP
  3. class polygon {
  4. protected:
  5.     double side_length_;
  6. public:
  7.     polygon()
  8.         : side_length_(0) {}
  9.     virtual ~polygon() {}
  10.     void set_side_length(double side_length) {
  11.         side_length_ = side_length;
  12. }
  13.     virtual double area() const = 0;
  14. };
  15. // the types of the class factories
  16. typedef polygon* create_t();
  17. typedef void destroy_t(polygon*);
  18. #endif


triangle.cpp:


这里还有一些需要提醒的地方:

你必须提供一个creation和destruction函数,因为在C中,你是无法在执行时调用delete来销毁对象的。
interface class 中的析构函数应该定义为virtual的,这种做法可能在不必要的地方很少见,但是也不能因为这个而冒险不是。
上面的例子笔者已经测试通过了,完全OK。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多