1.12.2018
Sdílené knihovny obsahují binární kód, který může jedna nebo více aplikací použít. Pokud máte zkušenost s programováním v prostředí Windows, jde o obdobu tzv. dynamicky linkovaných knihoven (dll).
Vytvoření sdílené knihovny
Pro náš ukázkový příklad si nejprve vytvoříme a sestavíme jednoduchou sdílenou knihovnu. Celý kód bude v jednom zdrojovém souboru knihovna.cpp.
#include <stdlib.h> #include <stdio.h> #include <string.h> extern "C" size_t vypis_text(const char* text) { if (!text) { printf("nezadal jsi žádný text!\n"); return 0; } else { printf("zadal jsi: %s\n", text); return strlen(text); } } extern "C" bool vrat_text(char* p_text, size_t delka_bufferu) { if (!p_text) return false; strncpy(p_text, "Nějaký vrácený text", delka_bufferu-1); return true; }
Překlad a sestavení sdílené knihovny s GCC
Pro sestavení sdílené knihovny si vytvoříme jednoduchý skript (zde nazvaný preloz_knihovnu.sh)
g++ -g -c -Wall -Werror -fpic knihovna.cpp g++ -shared -o knihovna.so knihovna.o
V prvním kroku vytvoříme objektový soubor (knihovna.o), a z něj v druhém kroku vlastní sdílenou knihovnu (knihovna.so). V tomto případě vytváříme knihovnu obsahující "debug informace" (to určujeme parametrem -g) protože jak si lze později vyzkoušet můžeme pak jednoduše krokovat kód knihovny. V praktickém použití bychom samozřejmě po odladění a před tím než aplikaci "dáme ven" při sestavení parametr -g nepoužili a místo toho naopak nastavili nějaký stupeň optimalizace (parametr -O3)
Aplikace využívající sdílenou knihovnu
Nyní si vytvoříme jednoduchou aplikaci využívající funkce z naší sdílené knihovny. Půjde o konzolovou aplikaci (soubor aplikace.cpp) do které musíme kromě standardních základních hlavičkových souborů vložit hlavičkový soubor dlfcn.h obsahující deklarace funkcí potřebných k načtení knihovny a získání adres požadovaných funkcí.
Načtení sdílené knihovny
Pro načení knihovny si vytvoříme globální proměnnou typu void* do které si uložíme handle načtené knihovny
void* _p_knihovna = nullptr;
V aplikaci (přímo v hlavní funkci main pak takto načteme sdílenou knihovnu:
_p_knihovna = dlopen("./knihovna.so", RTLD_LAZY); if (!_p_knihovna) { printf("Chyba načtení knihovny: %s", dlerror()); return EXIT_FAILURE; }
Knihovnu načteme pomocí funkce dlopen a v případě chyby získáme text chybové hlášky pomocí funkce dlerror(). Po použití někde na konci aplikace pak knihovnu uvolníme:
if (0 != dlclose(_p_knihovna)) { printf("Chyba uvolnění knihovny: %s", dlerror()); return EXIT_FAILURE; }
Získání a volání funkcí ze sdílené knihovny
Nyní si napíšeme funkci která získá adresu funkce ve sdílené knihovně a zavolá ji. Předesílám že v praxi pokud bychom v aplikaci funkci ze sdílené knihovny volali opakovaně, načetli bychom si ji jen jednou a uložili do globální proměnné pro opakované volání.
size_t vypis_text(const char* text) noexcept { typedef size_t (*vypis_text_t)(const char*); vypis_text_t p_vypis_text = (vypis_text_t)dlsym(_p_knihovna, "vypis_text"); if (!p_vypis_text) { printf("Chyba získání funkce: %s", dlerror()); return 0; } return p_vypis_text(text); }
Jak je vidět nejprve si musíme nadeklarovat typ odpovídající parametrům a návratovému typu funkce a do ukazatele na tento typ pak uložíme získanou adresu požadované funkce. Tu získáme pomocí funkce dlsym.
V případě druhé funkce které ve svém parametru vrací text jeho zkopírováním do zadaného bufferu pak kód vypadá takto:
bool vrat_text() noexcept { typedef bool (*vrat_text_t)(char*, size_t); vrat_text_t p_vrat_text = (vrat_text_t)dlsym(_p_knihovna, "vrat_text"); if (!p_vrat_text) { printf("Chyba získání funkce: %s", dlerror()); return false; } char buff[255]; bool bret = p_vrat_text(buff, sizeof(buff)); printf("vrácený text: %s\n", buff); return bret; }
Hlavní funkce main obsahující volání těchto funkcí pak bude vypadat následovně:
int main (int argc, char *argv[]) { _p_knihovna = dlopen("./knihovna.so", RTLD_LAZY); if (!_p_knihovna) { printf("Chyba načtení knihovny: %s", dlerror()); return EXIT_FAILURE; } vypis_text("ahoj"); vrat_text(); if (0 != dlclose(_p_knihovna)) { printf("Chyba uvolnění knihovny: %s", dlerror()); return EXIT_FAILURE; } return EXIT_SUCCESS; }
Překlad a sestavení aplikace
Použité funkce pro dynamické načítání musíme přilinkovat do aplikace proto v parametrech překladu musí být -ldl. Překlad můžeme zavolat z konzole:
g++ aplikace.cpp -g -Wall -ldl -o aplikace
V případě použití Visual Studia Code si pak můžeme pro překlad následující tasks.json:
{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "prekladac", "type": "shell", "command": "g++", "args": [ "aplikace.cpp", "-g", "-Wall", "-ldl", // pro funkci dlopen "-oaplikace" ], "group": { "kind": "build", "isDefault": true } } ] }
Pro spuštění a ladění aplikace z VSCode pak následující launch.json
{ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/aplikace", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }
Školení
Kontakt
739 219 991
live:radekchalupa_1
Nové články