Kirjaston tekeminen Linuxissa
Mureakuha
Sisällysluettelo |
Yleistä kirjastoista
Kirjastojen nimeäminen
Linuxissa, BSD:issä ja Solariksessa kirjaston nimen edessä on aina "lib" ja sen pääte on tyypistä riippuen ".a" (arkistoitu staattinen kirjasto) tai ".so" (dynaamisesti linkitetty kirjasto). Päätteen perässä on vielä mahdollisesti kirjaston versionumero, joka on usein muotoa libx.so.x.y.z. Esimerkkinä tämä "libstdc++.so.5", joka on symbolinen linkki kohteeseen "libstdc++.so.5.0.7".
Kirjastot sijaitsevat FHS:n (The Filesystem
Hierarchy Standard) määritelmän mukaan useimmiten hakemistoissa "/usr/lib"
ja "/lib" ja "/lib". Usein kirjastoja löytyy myös hakemistosta "/usr/local/lib".
Jos käytät muita hakemistoja sinun täytyy editoida tiedostoa "/etc/ld.so.conf"
tai vaihtoehtoisesti muuttaa ympäristömuuttujaa $LD_LIBRARY_PATH
Jaettu kirjasto
Kirjastojen lataaminen
Normaalisti lataaja /lib/ld-linux.so etsii ja lataa ohjelman tarvitsemat tiedostot ja ajaa ohjelman. (Katso manuaali ld.so(8))
Ohjelmaa ldconfig käytetään tarpeellisten linkkien luomista ld.so:ta varten. Koska ld.so:n on turha jokaisella käynnistyskerralla turha etsiä kaikkia hakemistoja läpi, luo ldconfig välimuistitiedoston /etc/ld.so.cache, joka sisältää binäärisessä muodossa listan kirjastoista.
Jaetun kirjaston luominen
Kirjaston luominen on lähes luvattoman helppoa, pitää muistaa vain muutama komentoriviargumentti.
Ensiksi argumentti -shared, joka luo jaetun kirjaston, joka voidaan linkittää suoritettavasta ohjelmasta.
Toisena argumenttinä on -fPIC, joka luo koodista paikkariippumatonta (position-independent code). Jos koodi ei olisi paikkariippumatonta niin kirjastosta pitäisi luoda aina uusi kopio muistiin, kun ohjelma lataa sen, jolloin taas menetetään koko jaetun kirjaston idea.
Esimerkki jaetusta kirjastosta
library.h
#ifndef _LIBRARY_H_ #define _LIBRARY_H_ std::string to_binary( int num ); #endif // _LIBRARY_H_
library.cpp
#include <sstream> #include <string> #include "library.h" std::string to_binary( int num ) { std::stringstream result; for(int i=0;i<12;++i) { result << ((num&1)==1); num>>=1; } return result.str(); }
main.cpp
#include <string> #include <cstdlib> #include <iostream> #include "library.h" int main( int argc, char **argv ) { for( int i=1; i<argc ;++i ) { std::cout << *(argv+i) << " = " << to_binary(atoi(*(argv+i))) << std::endl; } return 0; }
Tämän jälkeen käännetään tiedostot
gcc -shared -fPIC library.cpp -o -Wl,-soname,libmylib.so.1 g++ main.cpp -lmylib -L. -o usemylib
Sitten kokeilaan ajaa ohjelma.
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ln -s ./libmylib.so ./libmylib.so.1 ./usemylib 1 3 1024 1023
Saamme tulostuksen:
1 = 100000000000 3 = 110000000000 1024 = 000000000010 1023 = 111111111100
Voit ajaa vielä komennon ldd usemylib, niin näet mihin kirjastoihin ohjelma on linkattu.
libmylib.so.1 => ./libmylib.so.1 (0x40018000) libstdc++.so.5 => /usr/lib/libstdc++.so.5 (0x40039000) libm.so.6 => /lib/tls/libm.so.6 (0x400f3000) libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x40115000) libc.so.6 => /lib/tls/libc.so.6 (0x40120000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Dynaamisesti ladattava kirjasto
Dynaamisesti ladattavat kirjastot ovat kirjastoja, jotka ladataan ohjelmasta käsin ohjelman ajon aikana. Niillä voi toteuttaa ohjelmaan esimerkiksi plugin-tuen tai tuen erilaisille lisämoduuleille.
Funktiot
void *dlopen(const char *filename, int flag)
Funktio lataa dynaamisen kirjaston, jonka tiedostonimi on annettu ensimmäisessä parametrissä. Jos polku sisältää kauttaviivan ("/"), tiedostonimi tulkitaan absoluuttisena tai relatiivisena polkuna. Muuten linkkeri etsii tiedostoa oletushakemistoista.
Jos ladattavalla kirjastolla on riippuvuuksia ne ladataan.
Kun kirjasto on ladattu oikein, sen kahva palautetaan. Jos jokin menee pieleen palautetaan NULL.
void *dlsym(void *handle, const char *symbol)
Funktiolle annetaan kirjaston kahva, jonka dlopen palautti, sekä etsittävän symbolin nimen. Kun symboli löydetään palautetaan sen osoite, jos sitä ei löydetä palautetaan NULL.
char *dlerror()
dlerror palauttaa mahdollisesta virheestä luettavan virhettä kuvaavan stringin, kuten "./ohjelma: undefined symbol: funktio".
int dlclose(void *handle)
Sulkee kirjaston. Palauttaa onnistuessaan 0 ja muuten jonkun muun.
Esimerkki dynaamisesti ladattavasta kirjastosta
main.cpp
#include <string> #include <cstdlib> #include <iostream> #include <dlfcn.h> // dlopen, dlsym, dlclose, dlerror int main( int argc, char **argv ) { void *handle = 0; void *func = 0; try { handle = dlopen( "./libmylib.so", RTLD_LAZY ); if(!handle) { throw std::string(dlerror()); } func = dlsym(handle, "to_binary"); if(!func) { throw std::string(dlerror()); } } catch( std::string &ex ) { std::cout << "Exception: " << ex << std::endl; if(handle) { dlclose( handle ); } return 1; } typedef std::string (*function)(int); function to_binary = (function)func; for( int i=1; i<argc ;++i ) { std::cout << *(argv+i) << " = " << to_binary(atoi(*(argv+i))) << std::endl; } dlclose( handle ); return 0; }
library.cpp
#include <sstream> #include <string> #include <iostream> extern "C" std::string to_binary( int num ) { std::stringstream result; for(int i=0;i<12;++i) { result << ((num&1)==1); num>>=1; } return result.str(); } int *my_int; void __attribute__ ((constructor)) load() { std::cout << "load" << std::endl; /* alustetaan muuttujat konstruktorissa */ my_int = new int; } void __attribute__ ((destructor)) unload() { std::cout << "unload" << std::endl; /* tuhotaan ja putsataan kaikki destruktorissa */ delete my_int; }
Kääntäminen
gcc -shared -fPIC library.cpp -export-dynamic -o libmylib.so -Wl,-soname,libmylib.so.1 g++ main.cpp -ldl -o usemylib
HUOM. Esimerkissä on käytetty __attribute__-määritteitä, joilla määritetään kirjaston lataus ja tuhoamisfunktiot (itse funktioiden nimillä ei ole väliä). __attribute__-määritteet kuitenkin toimivan vain GNU GCC:tä käytettäessä. Lisätietoja: Using GNU C __attribute__
Staattinen kirjasto
Mikä on staattinen kirjasto?
Staattinen kirjasto on arkisto, joka sisältää valmiiksi käännettyjä koodimoduuleita objektitiedostoina, jotka puolestaa voidaan linkittää kirjastoa käyttävään ohjelmaan. Tällöin käytetyt objektimoduulit sisällytetään osaksi kirjastoa käyttävään ohjelmaan. Tämä kasvattaa ohjelman kokoa levyllä, sekä aiheuttaa sen, että jos useat ohjelmat käyttävät samaa staattista kirjastoa, ladataan kirjaston tarjoamat funktiot ja rakenteet muistiin useita kertoja koska jokaiseen ohjelmaan on sisällytetty omat kopiot.
Staattisen kirjaston luonti
Et tarvitse muuta kuin koodit, kääntäjän, sekä ar-työkalun. Oletan tässä tekstissä että kääntäjäsi on gcc eli GNU Compiler Collection. Sinun pitää kääntää koodimoduulisi objektitiedostoiksi linkittämättä niitä, jolloin sinulla on vino pino .o-tiedostoja. Oletetaan, että olet tekemässä kirjastoa, joka sisältää moduulit vaikkapa PNG-kuvan muuttamiseen JPEG-kuvaksi ja päinvastoin. Oletetaan lisäksi, että käytetty kieli on C (on tosin täysin sovellettavissa C++:aan). Tällöin meillä on kolme tiedostoa:
- png2jpeg.h Sisältää funktioiden esittelyt.
- png2jpeg.c Sisältää toteutuksen funktiolle, joka muuttaa PNG-kuvan JPEG-kuvaksi.
- jpeg2png.c Sisältää toteutuksen funktiolle, joka muuttaa JPEG-kuvan PNG-kuvaksi.
Käännä nyt koodimoduulisi esim. gcc:llä ilman linkitystä parametria -c käyttäen, jolloin tuloksena on kaksi objektitiedostoa:
- png2jpeg.o
- jpeg2png.o
Nyt voit luoda staattisen kirjaston joka sisältää nämä kaksi objektitiedostoa. Nimeltään se voi olla vaikkapa "libpng2jpeg.a" kohdan 1.1 nimeämiskäytännön mukaisesti. Tähän käytetään ar-työkalua seuraavasti:
ar crs libpng2jpeg.a png2jpeg.o jpeg2png.o
Tuloksena on tiedosto:
libpng2jpeg.a
Staattisen kirjaston käsittely
Jos sinun tarvitsee päivittää myöhemmin kirjastossa olevaa objektia, vaikkapa png2jpeg.o:ta, sinun ei tarvitse koota kaikkea uudestaan. Yksittäisiä moduuleita voidaan päivittää samalla komennolla, millä staattinen kirjasto luotiin:
ar rs libpng2jpeg.a png2jpeg.o
Staattisen kirjaston sisältämät moduulit voi myös listata ar-työkalulla. Tässä esimerkissä tarkastellaan libogg.a:ta, joka on Ogg-audiokirjaston osa.
ar t /usr/lib/libogg.a
Palaute on listaus tuon kirjaston sisältämistä objektitiedostoista, joka minun järjestelmässäni on:
framing.o
bitwise.o
Vielä tarkemmat tiedot kuten aikaleimat, objektitiedostojen koot sun muut saa selville lisäämällä vielä yhden parametrin:
ar tv /usr/lib/libogg.a
Palaute on minun järjestelmässäni:
rw-r--r-- 0/0 8628 Aug 18 12:55 2005 framing.o
rw-r--r-- 0/0 5172 Aug 18 12:55 2005 bitwise.o
ar-työkalulla on vielä paljon muita ominaisuuksia. Sen avulla voidaan mm. poistaa moduuleita kirjastosta sekä siirtää ja purkaa niitä. Suosittelen ar:in man-sivun (katso: ar(1)) vilkaisua heti tämän lukemisen jälkeen.
Staattisen kirjaston käyttö
Oletetaan, että sinulla on tekeillä ohjelma imageconv.c, joka käyttää edellä luotua kirjastoa libpng2jpeg.a. imageconv.c sisällyttää otsikkotiedoston png2jpeg.h, jossa käytetyt funktiot esitellään. Tämän jälkeen ohjelma käännetään ja linkitetään staattisen kirjastomme kanssa:
gcc imageconv.c libpng2jpeg.a -o imageconv
Ohjelmasi kanssa linkitettävät staattiset kirjastot siis lisätään gcc:n listaukseen yhdessä koodimoduulien kanssa. Koodipuolella staattisen kirjaston käyttö ei otsikkotiedoston (ja siinä olevien funktioiden esittelyjen) lisäksi vaadi mitään erikoista, koska kääntäjän perspektiivistä staattisen kirjaston moduulit ovat osa kirjatoa käyttävää ohjelmaa. Huomaa kuitenkin että vaikka kirjastossasi olisi yli viisisataa moduulia, vain ne joita käytetään suoraan ohjelmassasi, linkitetään itse ohjelmaasi.
