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).

void *dlopen(const char *filename, int flag);

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().

void *dlsym(void *handle, const char *symbol);

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í“).

char *dlerror(void);

No a na konec je slušné knihovnu zase uzavřít pomocí funkce dlclose().

int dlclose(void *handle)

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 -c -o number1.o -fpic number1.c
$ 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).

  1. /*------------------------------------------------*/
  2. /* c07dynamic/main.c                              */
  3.  
  4. #include <stdio.h>
  5. #include <dlfcn.h>
  6.  
  7.  
  8. int main(int argc, char **argv) {
  9.         char *cesta_ke_knihovne, *nazev_funkce;
  10.         char *error;
  11.         void *lib;
  12.         void (*funkce)() = NULL;
  13.  
  14.         if(argc != 3) {
  15.                 fprintf(stderr,"Usage: %s knihovna.so nazev_funkce\n", argv[0]);
  16.                 return 1;
  17.         }
  18.  
  19.         cesta_ke_knihovne = argv[1];
  20.         nazev_funkce = argv[2];
  21.  
  22.         printf("Oteviram knihovu '%s'\n", cesta_ke_knihovne);
  23.  
  24.         lib = dlopen(cesta_ke_knihovne, RTLD_LAZY);
  25.         if(lib == NULL) {
  26.                 fprintf(stderr, "%s\n", dlerror());
  27.                 return 1;
  28.         }
  29.  
  30.         printf("Nacitam funkci '%s\n", nazev_funkce);
  31.         (void) dlerror(); /* vycisteni chyb */
  32.         funkce =  dlsym(lib, nazev_funkce);
  33.         error = dlerror();
  34.         if(error != NULL) {
  35.                 fprintf(stderr, "%s\n", error);
  36.                 return 1;
  37.         }
  38.  
  39.         /* spusteni funke */
  40.         (*funkce)();
  41.  
  42.         /* zavreni knihovny, uvolneni zdroju */
  43.         dlclose(lib);
  44.         return 0;
  45. }
  46. /*------------------------------------------------*/

Při překladu musíte připojit k programu knihovnu libdl:

$ gcc -o main main.c -Wall -ldl

A teď už si můžete hrát:

$ ./main                                                                                                                                                                                                                                                            
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:

struct sFunkce api(void);

Strukturu sFunkce si definujte třeba takto:

struct sFunkce {
        char * nazev;
        void (*fce)(char *);
};

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í.

Komentář Hlášení chyby
Vytvořeno: 17.8.2014
Naposledy upraveno: 15.11.2017
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..