如何在運行時加載C++函數和類

如何在運行時加載C++函數和類

標籤(空格分隔): 編程


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

#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';
}

注意,以下兩種方式是等價的

extern "C" int foo
extern "C" void bar();

extern "C" {
   extern int foo;
   extern void bar();
}

但是定義變量卻是不等價的

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");
    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;
}

這裏還有一些需要提醒的地方:

  • 你必須提供一個creation和destruction函數,因爲在C中,你是無法在執行時調用delete來銷燬對象的。
  • interface class 中的析構函數應該定義爲virtual的,這種做法可能在不必要的地方很少見,但是也不能因爲這個而冒險不是。

上面的例子筆者已經測試通過了,完全OK。

想研究更深的,可以查看以下論文:

  1. A lightweight mechanism to update code in a running program Dynamic c++ classes
  2. Dynamic c++ classes A lightweight mechanism to update code in a running program
  3. (Dynamic Code Updates)[http://ttic.uchicago.edu/~mmaire/papers/pdf/dynamic_update.pdf]

參考:C++ dlopen mini HOWTO

上述如有錯誤地方還請指正,不剩感激……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章