A C++-nak van egy speciális kulcsszava arra, hogy függvényeket C kötéssel definiáljuk. Ez az extern "C". Az a függvény ami extern "C"-ként lett definiálva annak függvényneve szimbólumként használható akárcsak egy C függvénynek. Ezért csak nem-tagfüggvények deklarálhatók extern "C" segítségével, és ezeket nem lehet átdefiniálni.
Habár van néhány megkötés az extern "C" függvényekre, mégis igen hasznosak, mivel dinamikusan betölthetőek a dlopen segítségével akárcsak a C függvények.
Ez nem jelenti azt, hogy az extern "C"-vel definiált függvények nem tartalmazhatnak C++ kódot. Az ilyen függvények teljes értékű C++ függvények, kihasználhatják a C++ lehetőségeit és bármilyen típusú argumentummal rendelkezhetnek.
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.
Példa 1. Egy függvény betöltése
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)(); hello_t hello = (hello_t) dlsym(handle, "hello"); if (!hello) { cerr << "Cannot load symbol 'hello': " << dlerror() << '\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'; } |
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.
![]() | Két típusa létezik az extern "C" deklarációnak: extern "C" ahogy fent is használtuk, és extern "C" { … } a deklaráció kapcsos zárójelek között. Az első (inline) forma egy deklaráció ami egyszerre extern és C nyelvű kiértékelést ír elő, míg a második csak a nyelvi előírást befolyásolja. Az alábbi két deklaráció ekvivalens: és Ahogy nincs különbség extern és a nem-extern függvény függvénydeklarációk között sem. Ez mindaddig nem jelent problémát amíg nem deklarálsz változókat. Ha változókat deklarálsz tartsd észben, hogy és nem ugyanaz a dolog.További részleteket találsz a [ISO14882], 7.5 fejezetében, különös tekintettel a 7. bekezdésre vagy a [STR2000], 9.2.4. paragrafusában. Mielőtt bármi extra dolgot csinálnál az extern változókkal, ajánlott elolvasni a „További információ” fejezetben felsorolt dokumentumokat. |
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:
Az osztályt létrehozó és felszabadító függvényeket meg kell írnod. Soha ne szabadítsd fel a példányokat a delete operátorral a futtatható állományon belül. Mindig add vissza azokat a modulnak. Ez azért szükséges, mert a new és a delete C++ operátorok használata nem feltétlenül konzekvens. Ezért lehetséges, hogy egy pár nélküli new vagy delete hívás az oka a memória-szivárgásnak vagy segmentation fault-nak. Ugyanez igaz akkor is, ha különböző standard programkönyvtárakat használsz a modulban és futtatható állományban.
Az interfész osztály dekonstruktorának virtuálisnak kell lennie szinte minden esetben. Lehetséges egy meglehetősen ritka eset, amikor ez nem feltétlen szükséges. Ez a megkötés nem okoz problémát, mert az általa keletkező többletterhelés (overhead) elhanyagolható.
Ha az alap osztályodnak nincs szükséges dekonstruktorra akkor is definiálj egy üreset (és virtual-t), különben előbb vagy utóbb problémáid lesznek. Ezt garantálom. Többet tudhatsz meg erről a problémáról a C++ FAQ lite webhelyen található comp.lang.c++ GYIK 20. fejezetéből.
Előző | Tartalomjegyzék | Következő |
A probléma | Gyakran Ismételt Kérdések |