Biblioteques compartides a Linux, i com utilitzar-les des de Python amb ctypes

Shared libraries, Python, Linux… feia temps que tenia ganes de tractar aquestes qüestions i, finalment, he trobat el moment. El post d’avui plantejarà aquests punts:

1. Què és una static library de Linux?
2. Què és una shared library de Linux?
3. Com fer una shared library amb C?
4. Com fer servir la shared library?
5. Com invocar dinàmicament una shared library des de Python amb CTypes?
6. Com invocar dinàmicament una shared library des de C amb les funcions de càrrega dinàmica()?

1. Què són les static library
Per explicar què són les shared libraries és interessant explicar primer que són les static libraries. Reviseu Static Libraries, del Program Library HOWTO de The Linux Documentation Project (capítol 2 de PDF).

Les static librares són col·leccions de fitxers objecte ordinaris (fitxers “.o”, els que resulten de compilar amb gcc -c) que s’agrupen en biblioteques que acostumen a prendre l’extensió “.a”. Per a generar la biblioteca es fa servir el programa “ar” (“archiver”).

Les biblioteques estàtiques permeten enllaçar amb els programes sense que calgui compilar-ne el codi estalviant, doncs,  temps de recompilació; i, per tant, sense que calgui el codi de la biblioteca.

1.1. Com crear una static library
Per a crear una static library a partir dels fitxers objecte file1.o i file2.o la instrucció bàsica seria:

ar rcs biblioteca.a file1.o file2.o

les opcions rcs volen dir:
c: crea l’arxiu de biblioteca.
r: afegeix, o reemplaça, els fitxers objecte.
s: afegeix o actualitza l’índex a la biblioteca

Per a una referència completa, consulteu el manual:

man ar

1.2. Com fer servir l’static library
Un cop creada la biblioteca aleshores es fa servir al compilar amb l’opció gcc -lnom_biblioteca. Consulteu el manual

man gcc

Tingueu en compte les opcions:

-l library Cerca la biblioteca “library” durant l’enllaçat. Cal tenir en compte on s’escriu l’opció, l’enllaçador cerca i processa les biblioteques i els mòduls objecte en l’ordre en que apareixen a l’ordre de compilació; per tant, foo.o -lz bar.o cerca la llibreria z després de foo.o però abans de bar.o. Si bar.o fa referència a funcions de z, aquestes funcions no es carregaran.

Finalment, amb l’opció -L es pot indicar l’ubicació de la lliberia.

1.3. Nom de la static llibrary

L’enllaçador cerca la biblioteca per la llista de directoris estàndard. El nom del fitxer que cerca és “liblibrary.a”, és dir, prefixa el nom de la biblioteca amb “lib” i afegeix l’extensió “.a”. En l’exemple d’abans amb la biblioteca z l’enllaçador buscaria “libz.a”.

Atenció, doncs: seguint amb l’exemple anterior, a la creació de la biblioteca z amb ar faré servir el nom libz.a, però l’invocaré al gcc amb gcc -lz. z és el nom d’invocació, i libz.a el nom real de la biblioteca

1.4. Exemple

Amb un exemple es veurà tot més clar:

Creo una biblioteca estàtica amb els dos fitxers file1.c i file2.c (i file1.h, file2.h); les funcions de la biblioteca s’invoquen des d’una funció main definida al fitxer principal.c (i principal.h). Al Makefile es troben els detalls de l’utilització. Fixeu-vos que, com era d’esperar, el resultat de compilar fent servir la biblioteca, o fent servir els mòduls objecte directament és el mateix en ambdós casos. Fixem-nos també amb la mida dels binaris (biblioteca, mòduls objecte i executables) obtinguts.

file1.c

#include 

int funcio1(int num) {
  int i;

  for(i=1; i<=num; i++) {
    printf("fun1 - %d\n",i);
  }

  return 0;
}

file2.c

#include 

int funcio2(int num) {
  int i;  

  for(i=1; i<=num; i++) {
    printf("fun2 - %d\n",i);
  }

  return 0;
}

Els fitxers de capçalera, file1.h i file2.h

file1.h

/* function prototype */
extern int funcio1(int val);

file2.h

/* function prototype */
extern int funcio2(int val);

i el fitxer principal.c, que invoca les funcio1 i funcio2.

#include  

#include "file1.h"
#include "file2.h"
 
int main(int argc, char *argv[]) {
  funcio1(5);
  funcio2(8);

  return 0; 
} 

Makefile que genera la biblioteca estàtica i dos executables, el primer fent servir la biblioteca estàtica; i el segon, enllaçant directament els mòduls objecte

all: principal2 principal clean

file1.o: file1.c
    gcc -c file1.c

file2.o: file2.c
    gcc -c file2.c

proves: file1.o file2.o
    ar rcs libprova.a file1.o file2.o

principal.o: principal.c 
    gcc -c principal.c

principal: principal.o proves
    gcc -o principal principal.o -L. -lprova

principal2: principal.o file1.o file2.o
    gcc -o principal2 principal.o file1.o file2.o

Faré servir aquests fitxers de prova al llarg del post.

Si executo el makefile

albert@atenea:/media/albert/post/prova-lib$ make
gcc -c principal.c
gcc -c file1.c
gcc -c file2.c
gcc -o principal2 principal.o file1.o file2.o
ar rcs libprova.a file1.o file2.o
gcc -o principal principal.o -L. -lprova

Es genera la biblioteca estàtica i els executables principal i principal2 que, com era d’esperar, són de la mateixa mida.

aalbert@atenea:/media/albert/post/prova-lib$ ls -al
total 152
drwx------ 2 albert albert 8192 jun 13 21:42 .
drwx------ 4 albert albert 8192 jun 13 20:47 ..
-rw-r--r-- 1 albert albert  127 jun 13 21:08 file1.c
-rw-r--r-- 1 albert albert   54 jun  2 23:29 file1.h
-rw-r--r-- 1 albert albert 1052 jun 13 21:42 file1.o
-rw-r--r-- 1 albert albert  129 jun 13 21:08 file2.c
-rw-r--r-- 1 albert albert   54 jun  2 23:29 file2.h
-rw-r--r-- 1 albert albert 1052 jun 13 21:42 file2.o
-rw-r--r-- 1 albert albert 2320 jun 13 21:42 libprova.a
-rw-r--r-- 1 albert albert  519 jun 13 21:41 Makefile
-rw-r--r-- 1 albert albert 7394 jun 13 21:42 principal
-rw-r--r-- 1 albert albert 7394 jun 13 21:42 principal2
-rw-r--r-- 1 albert albert  140 jun  2 23:29 principal.c
-rw-r--r-- 1 albert albert  992 jun 13 21:42 principal.o

2. Què són les shared library?

Pe contestar aquesta pegunta, l’article de referència és Shared Libraries, del Program Library HOWTO de The Linux Documentation Project (capítol 3 de PDF).

Literalment, shared library vol dir biblioteca -de funcions- compartida. Les shared libraries són mòduls que s’enllacen en temps d’execució, en el moment de carregar-se l’aplicació que les enllaça, és dir en runtime; a diferència de les biblioteques estàtiques que s’enllacen en temps de compilació.

Això permet que les aplicacions que les invoquen tinguin executables més petits i que siguin més fàcils de mantenir i actualitzar.

A més, mitjançant la crida al sistema dlopen(), les shared libraries també poden carregar-se dinàmicament en qualsevol moment de l’execució i no només a l’inici. Per aquesta raó, les shared libraries també se les coneix com DLL, Dinamic Link Libraries, tot i que potser aquesta nomenclatura és més utilitzada a entorns Windows.

Que diferents tipus d’aplicacions puguin fer servir les mateixes shared libraries vol dir que les funcions i dades d’aquestes biblioteques no poden fer referencies a adreces de memòria absolutes, doncs l’enllaçat farà que les posicions de memòria en que finalment s’ubicaran puguin variar entre les diferents aplicacions. Això ens diu que per generar les shared libraries caldrà demanar que el mòdul objecte generat sigui reubicable.

Una shared library pot enllaçar-se simultàniament a diverses aplicacions. Quan la shared library està correctament instal·lada i respectant uns convenis de noms, el sistema de biblioteques compartides permet la convivència de diferents versions de la mateixa llibreria i el seu us per les diferents aplicacions.

2.1. Noms de les biblioteques compartides
2.1.1. soname
La clau del sistema de shared libraries està en els convenis de noms a seguir. Cal distingir entre el nom real de la shared library i el seu “soname”.
El “soname” d’una shared library es construeix amb el prefix “lib”, seguit del nom de la biblioteca, seguit del sufix “.so”, un punt, i un número de versió que s’ha d’augmentar cada cop que es canviï la interface de la biblioteca, és dir quan canvia el nombre de les funcions, o la signatura de les funcions.
El soname complet de la biblioteca inclou també el path fins a la biblioteca.
En general, el soname no és la llibreria propiament dita, si no un enllaç simbòlic al nom real de la biblioteca compartida

2.1.2. nom real (“real name”)
El fitxer amb el modul objecte reubicable rep el nom real de la biblioteca. El nom real es construeix afegint al soname un “minor number” i, opcionalment, un altre punt i el nombre de release. El minor number i el de release permeten conèixer exactament quina versió de la biblioteca està instal·lada i habiliten un control fi de la configuració. El nom complet de la biblioteca inclou també el path fins a la biblioteca.
El “real name” complet de la biblioteca inclou també el path fins a la biblioteca.

2.1.3. Nom per l’enllaçador (“linker name”)
És el nom que es fa servir per utilitzar la biblioteca. Es el soname sense número de versió. Com amb la resta de noms, el linker name complet de la biblioteca inclou també el path fins a la biblioteca.

2.1.4 Nom d’invocació

Com amb les biblioteques estàtiques, la invocació de la biblioteca al gcc amb l’opció -l es fa pel seu “nom d’invocació”, que serveix per construir el linker name. La cadena és, doncs, Nom d’invocació – Linker Name – Soname – Real name

2.2. utilització dels noms

La gestió de les biblioteques compartides és basa en la separació d’aquests noms.

– Utilització: quan les aplicacions llisten internament les shared libraries que necessiten només han de llistar el sonames requerits.

– Creació: quan es crea una shared library s’ha de crear amb tota la informació de versió completament detallada, és dir, els noms reals (“real names”).

– Instal·lació 1. ldconfig. La instal·lació d’una shared library s’ha de fer a alguna de les carpetes especials destinades a acollir biblioteques compartides i aleshores executar el programa ldconfig. ldconfig examina els fitxers existents i crea els sonames, és dir, els enllaços simbòlics als real names. A més actualitza la caché de biblioteques compartides /etc/ld.so.cache.

– Instal·lació 2. linker name. ldconfig no estableix els “linker names”, això es fa durant la instal·lació de la biblioteca, amb un script d’instal·lació, en general. El linker name, en general, és un enllaç simbòlic al soname més recent.

Aquest joc d’enllaços simbòlics permet que es pugui apuntar a una versió o una altre de les biblioteques. Per exemple, en desenvolupament, amb finalitats de depuració enllaçant diferents versions de la biblioteca.

Per exemple, la shared library que implementa readline:

/usr/lib/libreadline.so és el linker name complet i és l’enllaç simbòlic a…
/usr/lib/libreadline.so.3 és el soname complet i és l’enllaç simbòlic a…
/usr/lib/libreadline.so.3.0 que és el nom real complet i el mòdul objecte que conté el codi.

Els tres noms anteriors, en principi, són necessaris en runtime.

En compilació, a més, es fa servir el nom d’invocació de la biblioteca a l’opció -l del gcc: és dir,  gcc -lreadline

3. Com crear una Shared Library?

És molt semblant al procés de crear una Static Library.
Primer: crear els diferents mòduls objecte que conformen la biblioteca, amb l’opció -fPIC que genera codi reubicable.
Segon: amb els mòduls objecte creats, ja es pot construir la biblioteca amb gcc:

gcc -shared -Wl,-soname,soname_de_la_library -o real_name_library mòdul1.o mòdul2.o... -lc

El següent Makefile mostra el procediment:

all: prova

file1.o: file1.c
    gcc -fPIC -g -c -Wall file1.c

file2.o: file2.c
    gcc -fPIC -g -c -Wall file2.c

prova: file1.o file2.o
    gcc -shared -Wl,-soname,libprova.so.1 -o libprova.so.1.0.1 file1.o file2.o -lc

Com a remarques: a la generació dels mòduls objecte he fet servir,a més de -fPIC que ja he explicat, les opcions -g, per afegir informació de depuració i -Wall, per a mostrar tots els missatges de warnings. Són opcions que faciliten el desenvolupament.

La línia que construeix la shared library inclou

-shared indicar que és una shared library
-Wl,-soname,libprova.so.1 -Wl passa paràmetres a l’enllaçador. El paràmetre passat és el soname. Cal posar les comes i no es poden deixar espais en blanc que no estiguin “escapats”
-o libprova.so.1.0.1 és el “real name” de la biblioteca
-lc enllaça amb la lliberia libc.a estàtica

Aleshores, executant make obtinc la shared library libprova.so.1.0.1

albert@atenea:/media/albert/16GB/post shared libraries/prova-lib$ make
gcc -fPIC -g -c -Wall file1.c
gcc -fPIC -g -c -Wall file2.c
gcc -shared -Wl,-soname,libprova.so.1 -o libprova.so.1.0.1 file1.o file2.o -lc

i , efectivament:

albert@atenea:/media/albert/post/prova-lib$ ls -al
total 168
drwx------ 2 albert albert 8192 jun 13 22:22 .
drwx------ 4 albert albert 8192 jun 13 20:47 ..
-rw-r--r-- 1 albert albert  127 jun 13 21:08 file1.c
-rw-r--r-- 1 albert albert   54 jun  2 23:29 file1.h
-rw-r--r-- 1 albert albert 2660 jun 13 22:22 file1.o
-rw-r--r-- 1 albert albert  129 jun 13 21:08 file2.c
-rw-r--r-- 1 albert albert   54 jun  2 23:29 file2.h
-rw-r--r-- 1 albert albert 2660 jun 13 22:22 file2.o
-rw-r--r-- 1 albert albert 8271 jun 13 22:22 libprova.so.1.0.1
-rw-r--r-- 1 albert albert  247 jun 13 22:22 Makefile
-rw-r--r-- 1 albert albert  140 jun  2 23:29 principal.c

4. Fer servir la biblioteca compartida

El següent pas és fer servir la biblioteca. Hi han dos punts de vista a tenir en compte:

4.1 El desenvolupador

El desenvolupador genera versions de la biblioteca i requereix provar-les ràpidament. Per al desenvolupador la ubicació de la biblioteca ha de poder ser qualsevol lloc que li vagi bé.

Hi han dues opcions principals:

– o bé fer servir LD_LIBRARY_PATH a la sessió actual

– o bé instal·lar de forma permanent la biblioteca a una carpeta de desenvolupament

4.1.1 Per sessió, amb LD_LIBRARY_PATH

Al punt 3 he creat la  biblioteca libprova.so.1.0.1. Ara la faig servir per crear un executable amb el següent makefile

all: principal3

principal.o: principal.c
    gcc -o principal.o -c principal.c

principal3: principal.o
    gcc -o principal3  principal.o -lprova -L.

El resultat de la compilació és l’executable principal3

albert@atenea:~/workspace/prova-lib$ make
gcc -o principal3  principal.o -lprova -L.
albert@atenea:~/workspace/prova-lib$ ls -al
total 100
drwxrwxr-x  2 albert albert 4096 jun 13 23:19 .
drwxrwxr-x 13 albert albert 4096 jun  6 21:46 ..
-rw-r--r--  1 albert albert  127 jun 13 21:08 file1.c
-rw-r--r--  1 albert albert   54 jun  2 23:29 file1.h
-rw-r--r--  1 albert albert  129 jun 13 21:08 file2.c
-rw-r--r--  1 albert albert   54 jun  2 23:29 file2.h
-rw-r--r--  1 albert albert 8271 jun 13 22:22 libprova.so.1.0.1
-rw-r--r--  1 albert albert  185 jun 13 22:54 Makefile
-rw-r--r--  1 albert albert 7394 jun 13 21:42 principal
-rw-r--r--  1 albert albert 7394 jun 13 21:42 principal2
-rwxrwxr-x  1 albert albert 7316 jun 13 23:19 principal3
-rw-r--r--  1 albert albert  140 jun  2 23:29 principal.c
-rw-rw-r--  1 albert albert  992 jun 13 23:04 principal.o

Es pot veure que ha generat principal3 i que, efectivament és més petit que executables obtinguts amb les biblioteques estàtiques, fins aquí bé. Però què passa quan provo d’executar principal3?

albert@atenea:~/workspace/prova-lib$ principal3
principal3: error while loading shared libraries: libprova.so.1: cannot open shared object file: No such file or directory

Que no troba la biblioteca. Com he dit abans, les biblioteques compartides han d’estar a unes carpetes específiques. O bé, a les carpeta indicades a LD_LIBRARY_PATH (cal, però, anar amb compte amb LD_LIBRARY_PATH).

Aquest problema es veu molt clar amb ldd (Consulteu el manual man ldd) que torna  les biblioteques compartides que fa servir un executable.

albert@atenea:~/workspace/prova-lib$ ldd principal3
    linux-gate.so.1 =>  (0xb7798000)
    libprova.so.1 => not found
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75cc000)
    /lib/ld-linux.so.2 (0xb7799000)

Com he explicat abans, el procediment és executar ldconfig i crear un enllaç simbòlic del linker name a soname.  Som-hi:

albert@atenea:~/workspace/prova-lib$ ldconfig -n .
albert@atenea:~/workspace/prova-lib$ ln -s ./libprova.so.1 ./libprova.so
albert@atenea:~/workspace/prova-lib$ ls -al
total 100
drwxrwxr-x  2 albert albert 4096 jun 13 23:34 .
drwxrwxr-x 13 albert albert 4096 jun  6 21:46 ..
-rw-r--r--  1 albert albert  127 jun 13 21:08 file1.c
-rw-r--r--  1 albert albert   54 jun  2 23:29 file1.h
-rw-r--r--  1 albert albert  129 jun 13 21:08 file2.c
-rw-r--r--  1 albert albert   54 jun  2 23:29 file2.h
lrwxrwxrwx  1 albert albert   15 jun 13 23:34 libprova.so -> ./libprova.so.1
lrwxrwxrwx  1 albert albert   17 jun 13 23:34 libprova.so.1 -> libprova.so.1.0.1
-rw-r--r--  1 albert albert 8271 jun 13 22:22 libprova.so.1.0.1
-rw-r--r--  1 albert albert  185 jun 13 22:54 Makefile
-rw-r--r--  1 albert albert 7394 jun 13 21:42 principal
-rw-r--r--  1 albert albert 7394 jun 13 21:42 principal2
-rwxrwxr-x  1 albert albert 7316 jun 13 23:19 principal3
-rw-r--r--  1 albert albert  140 jun  2 23:29 principal.c

“ldconfig n .” busca sobre el directori actual i crea l’enllaç simbòlic al soname

libprova.so.1 -> libprova.so.1.0.1

Manualment, amb ln -s he creat l’enllaç simbòlic al linker name

libprova.so -> ./libprova.so.1

I ara, ja només cal establir amb  LD_LIBRARY_PATH  que a la carpeta /home/abert/workspace/prova-lib hi han biblioteques compartides

albert@atenea:~/workspace/prova-lib$ LD_LIBRARY_PATH=/home/albert/workspace/prova-lib/
albert@atenea:~/workspace/prova-lib$ export LD_LIBRARY_PATH

i, per descomptat, ara sí que funciona:    😉

albert@atenea:~/workspace/prova-lib$ principal3
fun1 - 1
fun1 - 2
fun1 - 3
fun1 - 4
fun1 - 5
fun2 - 1
fun2 - 2
fun2 - 3
fun2 - 4
fun2 - 5
fun2 - 6
fun2 - 7
fun2 - 8
albert@atenea:~/workspace/prova-lib$

El procediment amb LD_LIBRARY_PATH només hauria de fer-se servir en desenvolupament, de forma puntual per provar.

4.1.1 Permanent, a una carpeta especificada a /etc/ld.so.conf

Si torno LD_LIBRARY_PATH al seu estat original principal3 deixa de funcionar. Per exemple, si apago l’ordinador i el torno engegar, LD_LIBRARY_PATH perd els canvis. En lloc d’establir de forma general un valor per LD_LIBRARY_PATH, que pot ser perillós, una alternativa segura és especificar una carpeta pròpia de desenvolupament a /etc/ld.so.conf

Resetejo LD_LIBRARY_PATH, i preparo la carpeta de desenvolupament, copiant-hi la biblioteca compartida, executant ldconfig i establint l’enllaç simbòlic del linker name

albert@atenea:~/workspace/prova-lib$ LD_LIBRARY_PATH=
albert@atenea:~/workspace/prova-lib$ export LD_LIBRARY_PATH
albert@atenea:~/workspace/prova-lib$ mkdir /home/albert/dev-lib
albert@atenea:~/workspace/prova-lib$ cp libprova.so.1.0.1 /home/albert/dev-lib/
albert@atenea:~/workspace/prova-lib$ cd /home/albert/dev-lib/
albert@atenea:~/dev-lib$ ldconfig -n /home/albert/dev-lib/
albert@atenea:~/dev-lib$ ln -s /home/albert/dev-lib/libprova.so.1 /home/albert/dev-lib/libprova.so
albert@atenea:~/dev-lib$ ls -al
total 20
drwxrwxr-x  2 albert albert 4096 jun 14 00:03 .
drwxr-xr-x 80 albert albert 4096 jun 14 00:01 ..
lrwxrwxrwx  1 albert albert   34 jun 14 00:03 libprova.so -> /home/albert/dev-lib/libprova.so.1
lrwxrwxrwx  1 albert albert   17 jun 14 00:02 libprova.so.1 -> libprova.so.1.0.1
-rw-r--r--  1 albert albert 8271 jun 14 00:01 libprova.so.1.0.1
albert@atenea:~/dev-lib$

En aquest moment, principal3 falla perquè no troba la biblioteca compartida.

Ara estableixo a /etc/ld.so.conf la nova carpeta on cercar biblioteques compartides

sudo emacs /etc/ld.so.conf

/etc/ld.so.conf queda així

include /etc/ld.so.conf.d/*.conf
# afegeixo carpeta de biblioteques de desenvolupament
/home/albert/dev-lib

A continuació cal indicar que es tingui en compte la nova carpeta i es reconstrueixi la memòria cau de biblioteques compartides. Es fa amb ldconfig:

sudo ldconfig

En aquest moment, principal3 torna a ser operativa. Si ara examino la memòria cau de biblioteques compartides observo que, efectivament, està fent servir la biblioteca de /home/albert/dev-lib

albert@atenea:~/workspace/prova-lib$ principal3 
fun1 - 1
fun1 - 2
fun1 - 3
fun1 - 4
fun1 - 5
fun2 - 1
fun2 - 2
fun2 - 3
fun2 - 4
fun2 - 5
fun2 - 6
fun2 - 7
fun2 - 8
albert@atenea:~/workspace/prova-lib$ ldconfig -p | grep "prova"
    libprova.so.1 (libc6) => /home/albert/dev-lib/libprova.so.1
    libprova.so (libc6) => /home/albert/dev-lib/libprova.so
albert@atenea:~/workspace/prova-lib$

4.2 El distribuïdor

L’altre punt de vista d’ús de la biblioteca és el del distribuïdor de la biblioteca. Hi han un parell de criteris a tenir en compte en la distribució de codi i en la ubicació de les biblioteques compartides.

Ambdós criteris estableixen ubicacions permanents de les biblioteques compartides i, per tant, es defineixen a /etc/ld.so.conf.
4.2.1 Distribució seguint l’Estàndard GNU
L’estàndard aplica a projectes de desenvolupament de codi obert. Seguint aquest estàndard, les biblioteques han d’anar a /usr/local/lib, i els executables a /usr/local/bin
4.2.2 Filesystem Hierarchy Standard (FHS)
L’estàndard aplica al programari d’una distribució. La majoria de les biblioteques ha d’anar a /usr/lib, i les biblioteques per a l’arrencada del sistema, a /lib

En general, quan es distribueix codi de projectes GNU, és dir, projectes en evolució i construcció, la recomanació és seguir el GNU Standard. En canvi, quan el codi està en “producció” i s’inclou a una distribució de Linux, aleshores la recomanació és ubicar les biblioteques d’acord amb el criteri FHS.

5. Com invocar una shared library des de Python amb CTypes?

No me n’he oblidat. Per acabar, Python incorpora el package CTypes (documentació) que li permet enllaçar shared libraries i fer-ne us de les seves funcions de forma molt senzilla, semblant al LoadLibrary de VisualBasic.

És directe:
– faig l’import;
– amb CDLL carrego la biblioteca i obtinc un handle per fer-la servir. CDLL rep el soname com a paràmetre.
– Faig servir el handle per invocar les funcions de la biblioteca.

albert@atenea:~/workspace/prova-lib$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:38) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import *
>>> prova = CDLL("libprova.so.1")
>>> ret = prova.funcio1(7)
fun1 - 1
fun1 - 2
fun1 - 3
fun1 - 4
fun1 - 5
fun1 - 6
fun1 - 7
>>> ret = prova.funcio2(3)
fun2 - 1
fun2 - 2
fun2 - 3

6. Com invocar una shared library des de C amb les funcions de càrrega dinàmica()?

El que he fet al punt 4 és una invocació dinàmica de la shared library prova des de Python. Això mateix també es pot fer amb C, fent servir les crides al sistema dl*, (dlopen(), dlsym(), dlerror() i dlclose()). La referència, en aquest cas, Dynamically Loaded (DL) Libraries, del Program Library HOWTO de The Linux Documentation Project (capítol 4 del PDF).

També és directe. Per il·lustrar el procediment, faré un principal4 que, com es veu al makefile, no invoca la biblioteca prova, sino la dl (Dynamic Load), que és la que aporta les funcions de càrrega dinàmica:

Makefile

all: principal4

principal_dl.o: principal_dl.c
    gcc -o principal_dl.o -c principal_dl.c

principal4: principal_dl.o
    gcc -o principal4  principal_dl.o -ldl

principal_dl.c

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

int main(int argc, char **argv) {
    void *handle;
    int (*fun1)(int);   /* extern int funcio1(int val); */
    int (*fun2)(int);   /* extern int funcio2(int val); */
    char *error;
    int ret1;
    int ret2;
    
    handle = dlopen ("libprova.so.1", RTLD_LAZY);
    if (!handle) {
        fputs (dlerror(), stderr);
        exit(1);
    }
    
    fun1 = dlsym(handle, "funcio1");
    if ((error = dlerror()) != NULL)  {
        fputs(error, stderr);
        exit(1);
    }

    fun2 = dlsym(handle, "funcio2");
    if ((error = dlerror()) != NULL)  {
        fputs(error, stderr);
        exit(1);
    }

    printf("Sortida de funcio1(3)\n");
    ret1 = (*fun1)(3);
    printf("\nSortida de funcio2(7)\n");
    ret2 = (*fun2)(7);
    printf("\nret1: %d; ret2: %d\n\n", ret1, ret2);

    dlclose(handle);
}

Del codi anterior, destacar que:

– He definit fun1 i fun2 com punters a funcions amb la  mateixa signatura de funcio1 i funcio2;

– La utilització de dlopen() per carregar dinàmicament la biblioteca, passant el soname com a paràmetre. dlopen() retorna un “handle”, una referència, a la biblioteca.

– Amb aquest handle, faig servir dlsym() per trobar les funcions a executar. dlsym() retorna punters a les funcions

– Faig servir els punters a les funcions, és dir, utiloitzo les funcions de la biblioteca.

– Al llarg de tot el programa, si hi ha algun error de càrrega de la biblioteca, aquest es pot obtenir amb dlerror()

– Quan he fet servir la biblioteca, la descarrego amb dlclose()

7. Resum
En aquest post he explicat com
– Què és una static library.
– Què és una shared library.
– Com fer una shared library amb C.
– Com fer servir la shared library.
– Com invocar una shared library des de Python amb ctypes.
– I com invocar una shared library des de C amb el grup de funcions de càrrega dinàmica().

EL codi del post es pot descarregar del  repositori GitHub.