C++ dlopen mini-HOGYAN

Aaron Isotton

aaron@isotton.com

2003.08.12

Verziótörténet
Verzió: 1.032003.08.12Átdolgozta: AI
Referencia hozzáadása a GLib dinamikus modul betöltőjéről. Köszönet érte G. V. Sriraam-nak.
Verzió: 1.022002.12.08Átdolgozta: AI
GYIK hozzáadása. Kisebb változtatások.
Verzió: 1.012002.06.30Átdolgozta: AI
Frissített magyarázat a virtuális dekonstuktorokról. Kisebb változtatások.
Verzió: 1.002002.06.19Átdolgozta: AI
A „Szerzői jog és licenc” fejezet az elejére került. „A dokumentumban használt kifejezések” fejezet hozzáadása. Kisebb változtatások.
Verzió: 0.972002.06.19Átdolgozta: JYG
Egy kis szótár, valamint mondat-szintű változtatások.
Verzió: 0.962002.06.12Átdolgozta: AI
Irodalomjegyzék hozzáadása. Az extern függvények és változók leírásának javítása.
Verzió: 0.952002.06.11Átdolgozta: AI
Kisebb javítások.

Tartalomjegyzék
1. Bevezető
1.1. Szerzői jog és licenc
1.2. A felelősség teljes elhárítása
1.3. Közreműködők
1.4. Visszajelzés
1.5. A dokumentumban használt kifejezések
1.6. Magyar fordítás
2. A probléma
2.1. „Név szétszedése”
2.2. Osztályok
3. A megoldás
3.1. extern "C"
3.2. Függvények betöltése
3.3. Osztályok betöltése
4. Gyakran Ismételt Kérdések
5. További információ
Irodalomjegyzék

1. Bevezető

UNIX C++ programozókban felmerülő gyakori kérdés, hogyan töltsenek be dinamikusan C++ függvényeket és osztályokat a dlopen használatával.

Tény, hogy nem minden esetben egyszerű ez, és némi magyarázatot igényel. Ez van leírva ebben a mini-HOGYANban.

Egy átlagos C és C++ programozási nyelv ismeret valamint a dlopen API ismerete szükséges ahhoz, hogy megérthesd ezt a dokumentumot.

Ez a HOGYAN elsődleges a http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/ webhelyen található meg.


1.3. Közreműködők

Örömmel mondok köszönetet az alábbi személyeknek (abc sorrendben):


1.6. Magyar fordítás

A magyar fordítást Szalai Ferenc készítette (2004.04.17). A lektorálást Daczi László végezte el (2004.05.04). Utoljára javítva 2004.05.05.-én (r2). A dokumentum legfrissebb változata megtalálható a Magyar Linux Dokumentációs Projekt honlapján.


2. A probléma

Néha futásidőben kellene betölteni programkönyvtárakat (és használni a függvényeiket). Ez leginkább akkor szükséges, ha valamilyen plug-in vagy modul architektúrájú programot írsz.

A C nyelvben a program könyvtárak betöltése igen egyszerű (dlopen, dlsym és dlclose meghívása elegendő). C++-al ez egy kicsit bonyolultabb. A C++ program könyvtárak betöltésének nehézséget részint a „nevek szétszedése”, részben pedig az a tény okozza, hogy a dlopen API C-ben lett írva, így nem teszi lehetővé osztályok egyszerű betöltését.

Mielőtt bemutatnánk a programkönyvtárak betöltését, C++-ban megvizsgáljuk a „név szétszedési” problémát egy kicsit alaposabban. Azt ajánlom akkor is olvasd el ezt a részt, ha nem érdekel, mert segít megérteni mi is a probléma és mi a megoldása.


2.1. „Név szétszedése”

Minden C++ programban (vagy programkönyvtárban vagy tárgykód állományban) minden nem statikus függvény a bináris állományban szimbólumokkal van reprezentálva. Ezek a szimbólumok speciális karaktersorozatok, amik egyértelműen azonosítják a függvényt a programban, programkönyvtárban vagy tárgykód állományban.

C-ben a szimbólum nevek megegyeznek a függvények neveivel: az strcpy függvény szimbóluma strcpy és így tovább. Ez azért lehetséges, mert C-ben két nem statikus függvénynek nem lehet azonos a neve.

Mivel a C++ engedélyezi az átdefiniálást (overloading - különböző függvények azonos névvel, de különböző argumentumokkal), valamint számos új tulajdonsága van, ami a C-nek nincs — mint osztályok, tagfüggvények, kivétel kezelés — ezért nem lehetséges a függvények nevét egyszerűen szimbólumnévnek használni. A C++ ezt az problémát az úgynevezett „név szétszedéssel” (mangling) oldja meg. Ez úgy működik, hogy a a függvények és egyéb szükséges információk (mint az argumentumok száma és mérete) alapján létrehoz egy csak a fordító számára értelmes karaktersorozatot, amit az szimbólum névnek tud használni. A foo függvény ilyen módon előállított neve így nézhet ki például: foo@4%6^. Vagy nem is feltétlen kell tartalmaznia a "foo" szót magát.

Az egyik probléma ezzel az eljárással az, hogy a C++ standard (jelenleg [ISO14882]) nem definiálja ennek a menetét. Így minden fordító a saját módszerét használja. Néhány fordító meg is változtatja az algoritmust verzióról verzióra (különösen a g++ 2.x és 3.x között). Ezért ha ki is találtad, hogy a te fordítód hogyan is működik e tekintetben (és így be fogod tudni tölteni a függvényeidet a dlsym segítségével) ez valószínűleg csak a te fordítóddal fog működni és használhatatlan lesz annak következő verziójával.


3. A megoldás


3.2. Függvények betöltése

C++ a függvények úgy tölthetőek be mint C-ben; a dlsym segítségével. A betölteni kívánt függvényeket extern "C"-vel kell jelölnöd, hogy a C-szerű szimbólum névképzést kikényszerítsd.

A hello függvény a hello.cpp állományban van definiálva, mint extern "C". A main.cpp-ben töltődik be a dlsym hívással. A függvényt extern "C"-vel kell megjelölni, mert különben nem tudjuk biztosan a hozzá tartozó szimbólumnevet.


3.3. Osztályok betöltése

Az osztályok betöltése egy kicsit komplikáltabb, mert nekünk az osztály egy példányára van szükségünk, nem csak egy függvényre mutató mutatóra.

Nem tudjuk létrehozni az osztály egy példányát a new operátor segítségével, mert az osztály nincs definiálva a futtatható állományban, és mert nem tudjuk a nevét.

A megoldás a polimorfizmus segítségével adódik. Egy alap interfész osztályt definiálunk a futtatható állományban virtuális tagfüggvényekkel, és egy származtatott implementációs osztályt a modulban. Általában az interfész absztrakt osztály (egy osztály absztrakt, ha minden függvénye virtuális).

A dinamikus osztálybetöltést általában plug-in-okban használják — Ezeknek egy világosan definiált interfészt kell használniuk — Egy interfészt és az azt implementáló osztályokat kell definiálnunk.

Ezek után - még mindig a modulban - definiálunk két további segédfüggvényt (úgynevezett class factory functions). Az egyik függvény ezek közül elkészíti egy példányát az osztálynak, és egy arra irányított mutatót ad vissza. Míg a másik egy osztályra irányított mutatót kap (amit a factory készített) és felszabadítja azt. Ezt a két függvényt extern "C" direktívával jelöljük meg.

Ahhoz, hogy osztályt tölts be modulból csak a két factory függvényt kell betöltened a dlsym segítségével. Szerkeszteni (link) ugyanúgy kell, mint ahogy azt ebben részben tettük a hello függvénnyel. Ezek után már annyi példányt tudsz létrehozni és felszabadítani az osztályból, amennyit csak akarsz.

Példa 2. Egy osztály betöltése

Itt mi most egy általános polygon osztályt használunk, mint interfész és egy származtatott triangle osztályt, mint implementációt.

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

    // load the symbols
    create_t* create_triangle = (create_t*) dlsym(triangle, "create");
    destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
    if (!create_triangle || !destroy_triangle) {
        cerr << "Cannot load symbols: " << dlerror() << '\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) {}

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

Néhány dolgot meg kell jegyeznünk az osztályok betöltésével kapcsolatban:


4. Gyakran Ismételt Kérdések

4.1. Windowst használok és nem találom a dlfcn.h header állományt a PC-men! Mi a probléma?
4.2. Létezik bármilyen dlopen-kompatibilis illesztőfelület a Windows LoadLibrary API-jához?

5. További információ


Irodalomjegyzék

ISO14482 ISO/IEC 14482-1998 — The C++ Programming Language. PDF és nyomtatott könyv formájában is elérhető a http://webstore.ansi.org/ webhelyen.

STR2000 Bjarne Stroustrup The C++ Programming Language, Special Edition. ISBN 0-201-70073-5. Addison-Wesley.