Для начала код разделяемых библиотек (три файла: foo.h, foo1.cpp, foo2.cpp):
// foo.h
#ifndef foo_h__
#define foo_h__
extern "C"
__attribute__ ((visibility("default")))
void foo(void);
#endif // foo_h__
// foo1.cpp
#include <stdio.h>
#include "foo.h"
__attribute__((constructor))
void foo_init()
{
puts("Init shared library 1");
}
__attribute__((destructor))
void foo_deinit()
{
puts("Deinit shared library 1");
}
void foo(void)
{
puts("Hello, I'm a shared library 1");
}
// foo2.cpp
#include <stdio.h>
#include "foo.h"
__attribute__((constructor))
void foo_init()
{
puts("Init shared library 2");
}
__attribute__((destructor))
void foo_deinit()
{
puts("Deinit shared library 2");
}
void foo(void)
{
puts("Hello, I'm a shared library 2");
}
|
Теперь код, загружающий библиотеки и использующий их функционал (main.cpp):
#include <stdio.h>
#include <dlfcn.h>
//gcc -o main main.cpp -ldl
int main()
{
void *h1, *h2;
// define function prototype
typedef void (*foo_fn_ptr)(void);
// function pointers
foo_fn_ptr foo1, foo2;
char *error;
// open first library
h1 = dlopen ("./libfoo1.so", RTLD_LAZY);
if(!h1)
{
fputs(dlerror(), stderr);
puts("");
return 1;
}
// get function from first lib
foo1 = (foo_fn_ptr) dlsym(h1, "foo");
error = dlerror();
if (error)
{
puts(error);
return 1;
}
// use function
foo1();
// load second lib
h2 = dlopen ("./libfoo2.so", RTLD_LAZY);
if(!h2)
{
fputs(dlerror(), stderr);
puts("");
return 1;
}
// get function from second lib
foo2 = (foo_fn_ptr) dlsym(h2, "foo");
error = dlerror();
if (error)
{
puts(error);
return 1;
}
// use function
foo2();
// unload libraries
dlclose(h1);
dlclose(h2);
return 0;
}
Как все это собирать. Сначала соберем наши shared objects:
gcc -Wall -Werror -o libfoo1.so -shared -fpic foo1.cpp
gcc -Wall -Werror -o libfoo2.so -shared -fpic foo2.cpp
Теперь соберем наш исполняемый файл, который загружает и использует эти библиотеки:
gcc -o main main.cpp -ldl
У нас все собрано и готово, надо только добавить папку, где лежат наши библиотеки в список путей, откуда их можно загружать:
export LD_LIBRARY_PATH=$PWD
Теперь можно запустить:
./main
Hello world!
Init shared library 1
Hello, I'm a shared library 1
Init shared library 2
Hello, I'm a shared library 2
Deinit shared library 1
Deinit shared library 2
Ниже пояснения.
Код библиотек.
Зачем нам нужен
extern "C"? Строго говоря, не нужен, потому что по умолчанию
GCC (нынешняя версия, по крайней мере), экспортирует все функции и они доступны для использования, но есть один нюанс. Можно закомментировать эту строчку, пересобрать библиотеку и вместо увидеть ошибку "./libfoo2.so: undefined symbol: foo". То есть функция dlsym не находит символа "foo" в библиотеке, хотя мы точно знаем, что он там есть. С помощью
nm мы просмотрим таблицу экспорта нашей библиотеки:
$ nm -D ./libfoo2.so
0000000000201028 B __bss_start
w __cxa_finalize
0000000000201028 D _edata
0000000000201030 B _end
00000000000006b4 T _fini
w __gmon_start__
0000000000000548 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
U puts
0000000000000733 T _Z10foo_deinitv
0000000000000720 T _Z8foo_initv
00000000000006a0 T _Z3foov
$
Вот эта последняя строчка "_Z3foov" и есть имя нашей экспортированной функции и это очевидно не то же самое, что "foo". Теперь раскомментируем строчку extern "C", пересоберем библиотеку и запустим
nm еще раз:
$ nm -D ./libfoo2.so
0000000000201028 B __bss_start
w __cxa_finalize
0000000000201028 D _edata
0000000000201030 B _end
000000000000075c T _fini
0000000000000746 T foo
w __gmon_start__
00000000000005d0 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
U puts
0000000000000733 T _Z10foo_deinitv
0000000000000720 T _Z8foo_initv
$
Теперь мы видим "foo" и у нас все опять работает:
./main
Hello world!
Init shared library 1
Hello, I'm a shared library 1
Init shared library 2
Hello, I'm a shared library 2
Deinit shared library 1
Deinit shared library 2
Это все связано с правилами наименования экспортируемых функций, можо погуглить "name mangling", а
extern "C" принудительно заставляет компилятор именовать функции по простым и ясным правилам языка C.
Что же насчет
__attribute__ ((visibility("default"))), то по умолчанию все функции и так имеют видимость default, но, во избежание всяких неожиданностей типа нестандартных флагов компилятора, мы добавляем этот флаг в каждую экспортируемую функцию.
Сборка библиотек.
Компилируем и создаем из объектного файла shared object двумя отдельными командами.
Компиляция:
gcc -c -Wall -Werror -fpic foo1.cpp
Все нюансы тут состоят в использовании флага pic (Position Independent Code), что необходимо для динамически загружаемых библиотек (статические библиотеки не нуждаются в таком флаге).
Сборка происходит так:
gcc -shared -o libfoo1.so foo1.o
gcc -Wall -Werror -o libfoo1.so -shared -fpic foo1.cpp
тут используется специальный флаг shared для создания динамических библиотек. То же самое одной командой:
gcc -Wall -Werror -o libfoo1.so -shared -fpic foo1.cpp
Сборка main.
Все как обычно, но линкуем библиотеку dl, которая содержит dlopen, dlsym, dlclose и прочие фукции:
gcc -o main main.cpp -ldl
Настройка окружения и запуск.
В linux библиотеки лежат в строго определенных местах и откуда-то еще их загрузить не получится (в отличии от Windows), поэтому, когда мы запустим программу в первый раз, мы увидим что-то такое:
$ ./main
./libfoo1.so: cannot open shared object file: No such file or directory
$
Чтобы добавить новый путь, делаем следующее:
export LD_LIBRARY_PATH=$PWD