[轉載總結]Linux環境下C++運行時動態鏈接庫加載要點總結
最近在看的一些web服務器源碼的擴展部分基本上都使用到了.so動態庫的運行時加載,也遇到了不少坑,看了不少網上的博客,在這裏稍微總結轉載一下,也是為了以後少走彎路。
首先需要了解的就是靜態庫與動態庫的知識了,可以參考https://blog.csdn.net/jnu_simba/article/details/9569107。
一般來說,如果用gcc編譯的時候加上-shared和-fPIC選項,可以把源文件編譯成一個so文件,可以在其他源程序連接階段把這個鏈接上去,從而可以調用so文件提供的函數接口,這樣可以多文件共用一個so文件提供的函數,即節省內存空間,也便於更新,所有的接口只需要更新so文件就行。除了上面的方法,還有一個方法,那就是在運行時由程序自己動態加載so文件,使用一系列系統調用如dlopen,dlsym,dlclose等來進行動態加載,獲取函數地址從而進行函數調用,關閉加載的so文件等。一般以第一種方法用得多,但是第二種方法更靈活,結合配置文件,更具一般意義上的服務擴展性。
其實在這裏本人碰到的就是對於C++Mangling的迷惑,每次使用dlsym函數都沒法從g++編譯產生的動態庫中提取到相應的函數,最後才發現原來是缺少了一個extern "C"的問題,不過通過動態庫加載類的方法也是挺有用的。
以下轉載自https://blog.csdn.net/wzhg0508/article/details/46282973
Problem
有些時候你想在運行時加載一個lib或者function or class,這種事情經常發生在你開發一個plugin或者module時遇到。
在C語言裏,你可以輕松的利用dlopen, dlsym, dlclose來做到,但是在C++的世界裏卻沒那麽簡單了。困難就在C++語言的name mangling上,還有一部分就是dlopen函數是用純C語言寫的,不提供load classes功能。
在解析如何load function和class在c++語言中之前,還是線弄清問題吧---
。
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
#include <iostream> #include <dlfcn.h> int main() { using std::cout; using std::cerr; 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‘; }
load classes
加載類比加載函數還是有點困難的。
我們不能通過new instance來實例一個class在執行的時候,因為某些時候我們根本不知道要記載的類的名字,
怎麽解決吶?我們可以通過多態性來解決。開始我們定義一個base interface,聲明一些抽象方法,子類繼承並實現這些方法。
所以現在我們需要定義兩個helper 函數,用extern “c”聲明,一個用來new一個class實例,返回class的指針,一個用來銷毀這個指針。
基於此,我們就可以用dlsym來調用這兩個helper函數了。
Example 2: loading class
main.cpp
#include "polygon.hpp" #include <iostream> #include <dlfcn.h> int main() { using std::cout; using std::cerr; // 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"); //若使用函數指針這裏直接用destroy_t destroy_triangle。 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); }
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 polygon* (*create_t)(),這樣後面就可以直接定義函數指針變量
typedef void destroy_t(polygon*); #endif //typedef void (*destroy_t)(polygon*) 同理
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; }
編譯命令:g++ -shared -fpic triangle.cpp -o triangle.so
g++ main.cpp -o main -ldl
運行命令:./main
輸出結果:The area is: 42.4352
這裏還有一些需要提醒的地方:
- 你必須提供一個creation和destruction函數,因為在C中,你是無法在執行時調用delete來銷毀對象的。
- interface class 中的析構函數應該定義為virtual的,這種做法可能在不必要的地方很少見,但是也不能因為這個而冒險不是。
[轉載總結]Linux環境下C++運行時動態鏈接庫加載要點總結