C++ dlopen mini HOWTO 中譯版

來源:互聯網
上載者:User

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

 

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.載入函數代碼:

//----------
//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的變數尋幽訪勝之前,請細讀“其他”一節中羅列的文檔。

  載入類
  載入類有點困難,因為我們需要類的一個執行個體,而不僅僅是一個函數指標。我們無法通過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.

參考書目
[ISO14482] ISO/IEC 14482-1998 — The C++ Programming Language. Available as PDF and as printed book from http://webstore.ansi.org/.
[STR2000] StroustrupBjarne The C++ Programming Language, Special Edition. ISBN 0-201-70073-5. Addison-Wesley

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.