使用dlopen和dlsym來使用C++中的類

 使用dlopen和dlsym來使用C++中的類 2008-08-09 23:43:37php

分類:html

通常來講,dlopen和dlsym是來處理C庫中的函數的,對於C++中存在的name mangle問題,類的問題就不易處理,看下文你會有所收穫。linux

轉載自:ios

http://www.linuxsir.org/bbs/printthread.php?t=266890c++

 

C++ dlopen mini HOWTO 中譯版 [原創]
 程序員

C++ dlopen mini HOWTO
做者:Aaron Isotton <aaron@isotton.com> 2006-03-16
譯者:Lolita@linuxsir.org 2006-08-05
算法

------------------------------------------------
摘要
  如何使用dlopen API動態地加載C++函數和類

------------------------------------------------
目錄
  介紹
    版權和許可證
    不承諾
    貢獻者
    反饋
    術語
  問題所在
    Name Mangling
    類
  解決方案
    extern "C"
    加載函數
    加載類
  源代碼 
  FAQ
  其餘
  參考書

------------------------------------------------
介紹
  如何使用dlopen API動態地加載C++函數和類,是Unix C++程序員常常碰到的問題。事實上,狀況偶爾有些複雜,須要一些解釋。這正是寫這篇mini HOWTO的原因。
  理解這篇文檔的前提是對C/C++語言中dlopen API有基本的瞭解。這篇HOWTO的維護連接是 http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/ 

  版權和許可證
  這篇文檔《C++ dlopen mini HOWTO》版權爲Aaron Isotton全部(copyrighted (c) 2002-2006),任何人在遵照自由軟件基金會制定的GPLv2許可證條款前提下能夠自由拷貝、分發和修改這份文檔。

  不承諾
  本文不對文中的任何內容做可靠性承諾。您必須爲您本身使用文中任何概念、示例和信息承擔風險,由於其中可能存在錯誤和不許確的地方,或許會損壞您的系統──儘管幾乎不可能發生此類事故,但您仍是當心行事──做者不會爲此負任何責任。

  貢獻者
  在這篇文檔中,我欣然致謝(按字母順序):
  ◆ Joy Y Goodreau <joyg (at) us.ibm.com> 她的編輯工做.
  ◆ D. Stimitis <stimitis (at) idcomm.com> 指出一些formatting和name mangling的問題, 還指出extern 「C」的一些微妙之處。

  反饋
  歡迎對本文檔的反饋信息!請把您的補充、評論和批評發送到這個郵件地址:<aaron@isotton.com>。

  術語
  dlopen API
    關於dlclose、dlerror、dlopen和dlsym函數的描述能夠在 dlopen(3) man手冊頁查到。
    請注意,咱們使用「dlopen」時,指的是dlopen函數,而使用「dlopen API」則是指整個API集合。
------------------------------------------------
問題所在
  有時你想在運行時加載一個庫(並使用其中的函數),這在你爲你的程序寫一些插件或模塊架構的時候常常發生。
  在C語言中,加載一個庫垂手可得(調用dlopen、dlsym和dlclose就夠了),但對C++來講,狀況稍微複雜。動態加載一個C++庫的困難一部分是由於C++的name mangling(譯者注:也有人把它翻譯爲「名字毀壞」,我以爲仍是不翻譯好),另外一部分是由於dlopen API是用C語言實現的,於是沒有提供一個合適的方式來裝載類。
  在解釋如何裝載C++庫以前,最好再詳細瞭解一下name mangling。我推薦您瞭解一下它,即便您對它不感興趣。由於這有助於您理解問題是如何產生的,如何才能解決它們。

  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++中,您可能要用到庫中的一個類,而這須要建立該類的一個實例,這不容易作到。

解決方案

  extern "C"
  C++有個特定的關鍵字用來聲明採用C binding的函數:extern "C" 。 用 extern "C"聲明的函數將使用函數名做符號名,就像C函數同樣。所以,只有非成員函數才能被聲明爲extern "C",而且不能被重載。儘管限制多多,extern "C"函數仍是很是有用,由於它們能夠象C函數同樣被dlopen動態加載。冠以extern "C"限定符後,並不意味着函數中沒法使用C++代碼了,相反,它仍然是一個徹底的C++函數,可使用任何C++特性和各類類型的參數。

  加載函數
  在C++中,函數用dlsym加載,就像C中同樣。不過,該函數要用extern "C"限定符聲明以防止其符號名被mangle。
  
  示例1.加載函數
架構

代碼:app

//----------
//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';
}
ide

  在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的變量尋幽訪勝以前,請細讀「其餘」一節中羅列的文檔。

  加載類
  加載類有點困難,由於咱們須要類的一個實例,而不只僅是一個函數指針。咱們沒法經過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);
}


//----------
//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;
}

  加載類時有一些值得注意的地方:
  ◆ 你必須(譯者注:在模塊或者說共享庫中)同時提供一個創造函數和一個銷燬函數,且不能在執行文件內部使用delete來銷燬實例,只能把實例指針傳遞給模塊的銷燬函數處理。這是由於C++裏頭,new操做符能夠被重載;這容易致使new-delete的不匹配調用,形成莫名其妙的內存泄漏和段錯誤。這在用不一樣的標準庫連接模塊和可執行文件時也同樣。
  ◆ 接口類的析構函數在任何狀況下都必須是虛函數(virtual)。由於即便出錯的可能極小,近乎杞人憂天了,但仍舊不值得去冒險,反正額外的開銷微不足道。若是基類不須要析構函數,定義一個空的(但必須虛的)析構函數吧,不然你早晚要遇到問題,我向您保證。你能夠在comp.lang.c++ FAQ( http://www.parashift.com/c++-faq-lite/ )的第20節瞭解到更多關於該問題的信息。

源代碼
  你能夠下載全部包含在本文檔中的代碼包: http://www.isotton.com/howtos/C++-dl...xamples.tar.gz

FAQ
(譯者注:下文翻譯暫時省略)
1.I'm using Windows and I can't find the dlfcn.h header file! What's the problem?

The problem is that Windows doesn't have the dlopen API, and thus there is no dlfcn.h header. There is a similar API around the LoadLibrary function, and most of what is written here applies to it, too. Please refer to the Microsoft Developer Network Website for more information.

2.Is there some kind of dlopen-compatible wrapper for the Windows LoadLibrary API?

I don't know of any, and I don't think there'll ever be one supporting all of dlopen's options.

There are alternatives though: libtltdl (a part of libtool), which wraps a variety of different dynamic loading APIs, among others dlopen and LoadLibrary. Another one is the Dynamic Module Loading functionality of GLib. You can use one of these to ensure better possible cross-platform compatibility. I've never used any of them, so I can't tell you how stable they are and whether they really work.

You should also read section 4, 「Dynamically Loaded (DL) Libraries」, of the Program Library HOWTO for more techniques to load libraries and create classes independently of your platform.


其餘* The dlopen(3) man page. It explains the purpose and the use of the dlopen API.* The article Dynamic Class Loading for C++ on Linux by James Norton published on the Linux Journal.* Your favorite C++ reference about extern "C", inheritance, virtual functions, new and delete. I recommend [STR2000].* [ISO14882]* The Program Library HOWTO, which tells you most things you'll ever need about static, shared and dynamically loaded libraries and how to create them. Highly recommended.* The Linux GCC HOWTO to learn more about how to create libraries with GCC.

相關文章
相關標籤/搜索