linux下C++动态链接C++库示例详解
注意其中使用函數返回基類指針的用法,因為Linux的動態鏈接庫不能像MFC中那樣直接導出類
一、介紹
如何使用dlopen API動態地加載C++函數和類,是Unix C++程序員經常碰到的問題。
事實上,情況偶爾有些復雜,需要一些解釋。這正是寫這篇mini HOWTO的緣由。
理解這篇文檔的前提是對C/C++語言中dlopen API有基本的了解。
這篇HOWTO的維護鏈接是:
http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/?
二、問題所在
有時你想在運行時加載一個庫(并使用其中的函數),這在你為你的程序寫一些插件或模塊架構的時候經常發生。
在C語言中,加載一個庫輕而易舉(調用dlopen、dlsym和dlclose就夠了),但對C++來說,情況稍微復雜。
動態加載一個C++庫的困難一部分是因為C++的name mangling
(譯者注:也有人把它翻譯為“名字毀壞”,我覺得還是不翻譯好),
另一部分是因為dlopen API是用C語言實現的,因而沒有提供一個合適的方式來裝載類。
在解釋如何裝載C++庫之前,最好再詳細了解一下name mangling。
我推薦您了解一下它,即使您對它不感興趣。因為這有助于您理解問題是如何產生的,如何才能解決它們。
1. Name Mangling
在每個C++程序(或庫、目標文件)中,
所有非靜態(non-static)函數在二進制文件中都是以“符號(symbol)”形式出現的。
這些符號都是唯一的字符串,從而把各個函數在程序、庫、目標文件中區分開來。
在C中,符號名正是函數名:strcpy函數的符號名就是“strcpy”,等等。
這可能是因為兩個非靜態函數的名字一定各不相同的緣故。
而C++允許重載(不同的函數有相同的名字但不同的參數),
并且有很多C所沒有的特性──比如類、成員函數、異常說明──幾乎不可能直接用函數名作符號名。
為了解決這個問題,C++采用了所謂的name mangling。它把函數名和一些信息(如參數數量和大小)雜糅在一起,
改造成奇形怪狀,只有編譯器才懂的符號名。
例如,被mangle后的foo可能看起來像foo@4%6^,或者,符號名里頭甚至不包括“foo”。
其中一個問題是,C++標準(目前是[ISO14882])并沒有定義名字必須如何被mangle,
所以每個編譯器都按自己的方式來進行name mangling。
有些編譯器甚至在不同版本間更換mangling算法(尤其是g++ 2.x和3.x)。
即使您搞清楚了您的編譯器到底怎么進行mangling的,從而可以用dlsym調用函數了,
但可能僅僅限于您手頭的這個編譯器而已,而無法在下一版編譯器下工作。
三、類
使用dlopen API的另一個問題是,它只支持加載函數。
但在C++中,您可能要用到庫中的一個類,而這需要創建該類的一個實例,這不容易做到。
四、解決方案
1. extern "C"
C++有個特定的關鍵字用來聲明采用C binding的函數:
? extern "C" 。?
用 extern "C"聲明的函數將使用函數名作符號名,就像C函數一樣。
因此,只有非成員函數才能被聲明為extern "C",并且不能被重載。
盡管限制多多,extern "C"函數還是非常有用,因為它們可以象C函數一樣被dlopen動態加載。
冠以extern "C"限定符后,并不意味著函數中無法使用C++代碼了,
相反,它仍然是一個完全的C++函數,可以使用任何C++特性和各種類型的參數。
2. 加載函數
在C++中,函數用dlsym加載,就像C中一樣。不過,該函數要用extern "C"限定符聲明以防止其符號名被mangle。
示例1.加載函數
代碼:
//----------
//main.cpp:
//----------
#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';
}
在hello.cpp中函數hello被定義為extern "C"。它在main.cpp中被dlsym調用。
函數必須以extern "C"限定,否則我們無從知曉其符號名。
警告:
extern "C"的聲明形式有兩種:
? 上面示例中使用的那種內聯(inline)形式extern "C" ,?
? 還有只用花括號的extern "C" { ... }這種。?
第一種內聯形式聲明包含兩層意義:外部鏈接(extern linkage)和C語言鏈接(language linkage),
而第二種僅影響語言鏈接。
下面兩種聲明形式等價:
代碼:
extern "C" int foo;
extern "C" void bar();
和代碼:
extern "C" {
? extern int foo;
? extern void bar();
}
對于函數來說,extern和non-extern的函數聲明沒有區別,但對于變量就有不同了。
如果您聲明變量,請牢記:
代碼:
extern "C" int foo;
和代碼:
extern "C" {
? int foo;
}
是不同的物事(譯者注:簡言之,前者是個聲明; 而后者不僅是聲明,也可以是定義)。
進一步的解釋請參考[ISO14882],7.5, 特別注意第7段;
?或者參考[STR2000],9.2.4。
在用extern的變量尋幽訪勝之前,請細讀“其他”一節中羅列的文檔。
3. 加載類
加載類有點困難,因為我們需要類的一個實例,而不僅僅是一個函數指針。
我們無法通過new來創建類的實例,因為類不是在可執行文件中定義的,況且(有時候)我們連它的名字都不知道。
解決方案是:利用多態性!?
我們在可執行文件中定義一個帶虛成員函數的接口基類,而在模塊中定義派生實現類。
通常來說,接口類是抽象的(如果一個類含有虛函數,那它就是抽象的)。
因為動態加載類往往用于實現插件,
這意味著必須提供一個清晰定義的接口──我們將定義一個接口類和派生實現類。
接下來,在模塊中,我們會定義兩個附加的helper函數,
就是眾所周知的“類工廠函數(class factory functions)(譯者注:或稱對象工廠函數)”。
其中一個函數創建一個類實例,并返回其指針;?
另一個函數則用以銷毀該指針。這兩個函數都以extern "C"來限定修飾。
為了使用模塊中的類,我們用dlsym像示例1中加載hello函數那樣加載這兩個函數,
然后我們就可以隨心所欲地創建和銷毀實例了。
示例2.加載類
我們用一個一般性的多邊形類作為接口,而繼承它的三角形類(譯者注:正三角形類)作為實現。
代碼:
//----------
//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");
? 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);
}
主程序的編譯與運行:
$?g++ -Wall -g -rdynamic -ldl main.cpp -o compile_c++LIBc++
$ ./compile_c++LIBc++?
The area is: 42.4352
//----------
//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
//----------
//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++ -Wall -g -fPIC -o triangle.so -shared triangle.cpp
加載類時有一些值得注意的地方:
◆ 你必須(譯者注:在模塊或者說共享庫中)同時提供一個創造函數和一個銷毀函數,
? ?且不能在執行文件內部使用delete來銷毀實例,只能把實例指針傳遞給模塊的銷毀函數處理。
? ?這是因為C++里頭,new操作符可以被重載;
? ?這容易導致new-delete的不匹配調用,造成莫名其妙的內存泄漏和段錯誤。
? ?這在用不同的標準庫鏈接模塊和可執行文件時也一樣。
◆ 接口類的析構函數在任何情況下都必須是虛函數(virtual)。
? ?因為即使出錯的可能極小,近乎杞人憂天了,但仍舊不值得去冒險,反正額外的開銷微不足道。
? ?如果基類不需要析構函數,定義一個空的(但必須虛的)析構函數吧,否則你遲早要遇到問題,我向您保證。
? ?你可以在comp.lang.c++ FAQ( http://www.parashift.com/c++-faq-lite/ )的
? ?第20節了解到更多關于該問題的信息。
示例3:
/*!
?******************************************************************************
?* \File
?* ? arith.h
?******************************************************************************
?*/?
#ifndef __ARITH_H__
#define __ARITH_H__
? ??
class Arithmetic
{?
? protected:
? ? int m_iVarA;
? ? int m_iVarB;
? ??
? public:
? ? void set_member_var(int a, int b){
? ? ? m_iVarA = a;
? ? ? m_iVarB = b;
? ? }
? ??
? public:
? ? virtual int add() const = 0;
? ? //int add();
? ? int sub();
? ? int mul();
? ? int div();
? ? int mod();
? public:
? ? Arithmetic():m_iVarA(0),m_iVarB(0){}
? ? virtual ~Arithmetic(){}
};
typedef Arithmetic* create_t();
typedef void destroy_t(Arithmetic*);
#endif
/*!
?******************************************************************************
?* \File
?* ? arith.cpp
?******************************************************************************
?*/?
#include "arith.h"
class arith : public Arithmetic{
? public:
? ? virtual int add() const {
? ? ? return (m_iVarA + m_iVarB);
? ? }
}; ?
? ??
// the class factories
extern "C" Arithmetic* create(int a, int b) {
? return new arith;
} ? ??
? ??
extern "C" void destroy(Arithmetic* p) {
? delete p;
}
編譯動態庫:
$ g++ -Wall -g -fPIC -o arith.so -shared arith.cpp
主程序:
/*!
?******************************************************************************
?* \File
?* ? main.cpp
?* \Brief
?* ? C++ source code
?* \Author
?* ? Hank
?******************************************************************************
?*/
#include <iostream>
#include <dlfcn.h>
#include "arith.h"
using namespace std;
int main(int argc, char* argv[])
{
? int a = 4, b = 3;
? int ret = 0;
? void *p_Handler = dlopen("./arith.so", RTLD_LAZY);
? if (!p_Handler)
? {
? ? printf("%s\n",dlerror());
? ? exit(1);
? }
? dlerror();
? create_t* create_arith = (create_t*)dlsym(p_Handler, "create");
? const char* dlsym_error = dlerror();
? if (dlsym_error) {
? ? cerr << "Cannot load symbol create: " << dlsym_error << '\n';
? ? return 1;
? }
? destroy_t* destroy_arith = (destroy_t*)dlsym(p_Handler, "destroy");
? dlsym_error = dlerror();
? if (dlsym_error) {
? ? cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
? ? return 1;
? }
? Arithmetic* arith_obj = create_arith();
? arith_obj->set_member_var(a, b);
? ret = arith_obj->add();
? cout<<a<<" + "<<b<<" = "<<ret<<endl;
? destroy_arith(arith_obj);
? dlclose(p_Handler);
? return 0;
}
編譯與運行:
$ g++ -Wall -g -rdynamic -ldl main.cpp -o compile_c++LIBc++
$ ./compile_c++LIBc++?
4 + 3 = 7
?
轉載于:https://www.cnblogs.com/jiangu66/p/3161543.html
總結
以上是生活随笔為你收集整理的linux下C++动态链接C++库示例详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网站改版之指标分析
- 下一篇: Linux下查看操作系统的位数和系统名称