Dynamické knihovny
Dynamické knihovny jsou takové, které se načítají až na požádání programem,
tedy až po spuštění programu – nejsou potřeba pro jeho spuštění.
Dynamické knihovny se s oblibou používají pro vytváření různých pluginů.
Stačí, když do vašeho programu zakomponujete nějaké funkce,
které umožní načíst dynamickou knihovnu (plugin) a použít její funkce.
Takové sadě funkcí se odborně říká rozhraní.
Jde o jasně definované funkce, které musí plugin obsahovat, aby jej
dokázal program používat.
Dynamické knihovny jsou totéž, co shared knihovny z minulé kapitoly. (Mají příponu .so
).
V minulé kapitole se připojovaly ke spustitelnému souboru už při překladu
a automaticky se načetly (pomocí ld-linux.so) při startu programu.
V této kapitole ukáži, jak můžete připojit knihovnu .so
až za běhu programu.
Použití dynamické knihovny
Prvním krokem je otevření dynamické knihovny funkcí dlopen()
z knihovny
<dlfcn.h> (z této knihovny jsou i ostatní zde použité funkce).
Druhým krokem je načtení tzv. „symbolu“, v našem příkladě funkce (mohou se načítat i globální
proměnné atp.) pomocí funkce dlsym()
.
Funkce dlsym()
vrací ukazatel na void
, který bude ukazovat na nalezený symbol.
V našem případě ukazatel na funkci, protože budeme žádat o symbol, který
označuje funkci.
Funkce dlsym()
může vrátit NULL, což znamená, že symbol je NULL,
nebo že došlo k nějaké chybě (třeba že nebyl symbol nalezen). Pro
zjištění, zda došlo k chybě, se používá funkce dlerror()
,
která vrací ukazatel na poslední chybovou hlášku (NULL, pokud k chybě nedošlo).
Abyste měli jistotu, že dlerror()
nevrací chybu z nějakého
dřívějšího volání dlsym()
než toho vašeho,
měli byste pro jistotu zavolat dlerror()
před voláním dlsym()
(čímž se „vyčistí“).
No a na konec je slušné knihovnu zase uzavřít pomocí funkce dlclose()
.
Příklad
Zdrojové kódy pro dynamické knihovny použiji z kapitoly o překladu. Vytvořím si (pro účely testování) dvě dynamické (shared) knihovny (jak bylo vysvětleno v minulé kapitole):
$ clang -shared -o libnumber1.so number1.o
$ clang -c -o number2.o -fpic number2.c
$ clang -shared -o libnumber2.so number2.o
Dále uvádím zdrojový kód programu, který předpokládá, že dostane jako argumenty z příkazové řádky název dynamické knihovny a název funkce, kterou má načíst a spustit. Program předpokládá, že funkce nemá žádné argumenty a nic nevrací (viz typ ukazatele na funkci).
- /*------------------------------------------------*/
- /* c07dynamic/main.c */
- #include <stdio.h>
- #include <dlfcn.h>
- }
- cesta_ke_knihovne = argv[1];
- nazev_funkce = argv[2];
- lib = dlopen(cesta_ke_knihovne, RTLD_LAZY);
- }
- funkce = dlsym(lib, nazev_funkce);
- error = dlerror();
- }
- /* spusteni funke */
- (*funkce)();
- /* zavreni knihovny, uvolneni zdroju */
- dlclose(lib);
- }
- /*------------------------------------------------*/
Při překladu musíte připojit k programu knihovnu
libdl
:
A teď už si můžete hrát:
Usage: ./main knihovna.so nazev_funkce
$ ./main libnumber1.so number1
Oteviram knihovu 'libnumber1.so'
libnumber1.so: cannot open shared object file: No such file or directory
$ ./main ./libnumber1.so number1
Oteviram knihovu './libnumber1.so'
Nacitam funkci 'number1
n1 = 1
$ ./main ./libnumber2.so number1
Oteviram knihovu './libnumber2.so'
Nacitam funkci 'number1
./libnumber2.so: undefined symbol: number1
$ ./main ./libnumber2.so number2
Oteviram knihovu './libnumber2.so'
Nacitam funkci 'number2
n2 = 2
U dynamicky načítaných knihoven se nemusí používat předpona "lib". Vlastně je doporučováno ji nepoužívat, aby se odlišily od sdílených knihoven.
Za domácí úkol si napište program, který otevře všechny dynamické knihovny ze zadaného adresáře a načte z nich tuto funkci:
Strukturu sFunkce si definujte třeba takto:
Jedná se tedy o dvojici název funkce a ukazatel na funkci s jedním
argumentem typu char *
.
Váš hlavní program pak vypíše všechny nalezené funkce (jejich název a pořadové číslo) a pak požádá uživatele o zadání pořadového čísla funkce a nějakého řetězce, který funkce zpracuje.
Kdykoliv se rozhodnete k tomuto programu přidat nějaké nové funkce na
zpracování textu, stačí napsat dynamickou knihovnu, která poskytne funkci
api()
!
Až to budete mít, změňte program tak, aby api()
vracel pole
referencí na struktury sFunkce (ukončené NULL), aby
mohla dynamická knihovna poskytovat „neomezené“ množství
funkcí.
Částečné řešení najdete ve zdrojových kódech ke stažení.