Ключевые слова:share, lib, gcc, linux, (найти похожие документы)
From: LogRus <[email protected]>
Date: Mon, 25 Dec 2003 14:31:37 +0000 (UTC)
Subject: Совместно используемые библиотеки в C под Linux для начинающих.
Оригинал: http://club.shelek.com/viewart.php?id=106
Совместно используемые библиотеки в C под Linux для начинающих.
Для начала напишем два файлика test.c и app.c
Листинг test.c
---------------------------------------------------
int f()
{
return 3;
}
---------------------------------------------------
Листинг app.c
--------------------------------------------------
int main()
{
return f();
}
--------------------------------------------------
Конечно можно всё сразу собрать и всё зарабоет. Но мы пойдем другим
путём берём компилируем test.c в test.o.
$ gcc -c -o test.o test.c
Опция -c значит скомпилировать не соберая. Теперь делаем из test.o
библиотечку статическую. Выполняем вот эту команду.
$ ar cr libtest.a test.o test1.o test2.o
Файлы test1.o и test2.o я вписал просто показать, что можно собрать
некий архив объектных файлов ("архив" это второе название статической
библиотеки). Заметьте, что у слова тест появился префикс lib и
окончание поменяли с .o на .a . Команда опции команды ar означают: c
не ругатся если надо создать файл; r запихать в архив файлы или
заменить существующие.
Попробуем это всё собрать.
$ gcc -c -o app.o app.c
$ gcc -o app -L. -ltest app.o
app.o(.text+0x11): In function 'main':
: undefined reference to 'f'
collect2: ld returned. 1 exit status
Спрашиваете, что буквально за фигня. Отвечаю: компоновщик смотрит свои
параметры слева на право и когда он доходит до app.o он "думает", что
libtest.a ему нафиг не нужна никто её не пользует, отсюды вывод надо
поставить app.o сразу после app и всё заработает. Проверяем.
$ gcc -o app app.o -L. -ltest
Работает. Далее надо проверить работает ли наш файлик app который мы
собрали.
$ ./app
$ echo $?
3
$
Работает, как не странно! Опять на счет опций -L. значит искать
библиотеки сначала в директории указанной после -L в нашем случае у
нас стоит там точка значит в дериктории. где мы находимся. Опция
-ltest как раз и значит использовать библиотеку libtest.
С этим надеюсь ясно. Тепер переходим к совместно используемым
библиотекам(динамически подключаемым). Эти библиотеки похожы на
архивы, но гдавное отличие в том, что вместо кода из библиотеки в
программу включается ссылка на библиотеку.
Для создания библиотеки составляющие её файлы надо собрать использую
опцию -fPIC (Position-Independent Code):
$ gcc -c -fPIC test.c
Теперь соберём библиотеку:
$ gcc -shared -fPIC -o libtest.so test.o test1.o test2.o
Опция -shared заставляет компоновщик создать именно совместно
используемую библиотеку. Заново собераем нашу программу:
$ gcc -o app app.o -L. -ltest
Теперь выполним команду:
$ ldd app
libtest.so => not found
libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Эта команда нам показывает используемые библиотеки. Как видите нашу
библиотечку программа не нашла. А всё потому что в файл помещается
только имя библиотеки, а не путь к ней и при запуске библиотека ищется
только в стандартных каталогах /lib и /usr/lib. При попытке запустить
программу получаем сообщение о том, что не найдена библиотека. Первое
решение проблемы установить переменную окружения LD_LIBRARY_PATH
$ export LD_LIBRARY_PATH=/home/logrus/Documents/test:/usr/local/lib
Выполнив эту команду мы сообщили ситеме, что библиотеки могут хранится
еще и в директориях /home/logrus/Documents/test и /usr/local/lib .
Другое решение использование при компиляции опции -Wl,-rpath примерно
так:
$ gcc -o app app.o -L. -ltest -Wl,-rpath,/home/logrus/Documents/test
В этом случае в вашем приложении будет прописан полный путь до
библиотеки.
У вас наверняка возник вопрос, а что, если у меня есть оба вариата
библиотеки и .a и .so? В этом случае если компоновщик обнаруживает обе
библиотеки, то он выберет динамическую, если не указано иначе. Иначе
это использовать опцию -static.
Рекомендую всё это срочно протестировать! Набавать текста не много.
Если вам захочится использовать математические функции, которые не
входят в состав libc (стандартаня библиотека C) автоматически
подключаемой к вашей программе (это если вы этого не заметили в
результатах работы комады ldd), то компилируем так:
# gcc -o compute compute.c -lm
При компоновке програм на C++ к вашей программе подключается
библиотека libstdc++.
Часто библиотеки связаны между собой.
Например соберем такую штуку:
Листинг testtiff.c
----------------------------------------------------------------------
#include <stdio.h>
#include <tiffio.h>
int main (int argc, char** argv)
{
TIFF* tiff;
tiff = TIFFOpen (argv[1], "r");
TIFFClose(tiff);
return 0;
}
----------------------------------------------------------------------
При компилляции используем флаг -ltiff
$ gcc -o tifftest tifftest.c -ltiff
Посмотрим что нам выдаст ldd
$ ldd tifftest
libtiff.so.3 => /usr/lib/libtiff.so.3 (0x40022000)
libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0x40063000)
libz.so.1 => /usr/lib/libz.so.1 (0x40082000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Это говорит нам о том, что динимические библиотеки зависят от друг
друга и что эти библиотеки также подключены. Посмотри и запешите или
запомните разменр исполняемого файла tifftest.
Соберем его со статическими библиотеками.
$ gcc -static -o tifftest tifftest.c -ltiff
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0xf74): In function `LogL16toY':
../libtiff/tif_luv.c:657: undefined reference to `exp'
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0x1014): In function `LogL16fromY':
../libtiff/tif_luv.c:672: undefined reference to `log'
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0x1036):../libtiff/tif_luv.c
:672: undefined reference to `log'
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0x10b8):../libtiff/tif_luv.c
:674: undefined reference to `log'
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0x10e8):../libtiff/tif_luv.c
:674: undefined reference to `log'
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0x1467): In function `LogL10toY':
../libtiff/tif_luv.c:736: undefined reference to `exp'
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0x14cd): In function `LogL10fromY':
../libtiff/tif_luv.c:750: undefined reference to `log'
/usr/local/lib/libtiff.a(tif_luv.o)(.text+0x14ef):../libtiff/tif_luv.c
:750: undefined reference to `log'
collect2: ld returned 1 exit status
Ну как красиво? Это из-за того, что статические библиотеки не могут
ссылаться друг на друга. Значит нам надо вручною указать все
необходимые библиотеки.
$ gcc -static -o tifftest tifftest.c -ltiff -ljpeg -lz -lm
Проверьте размер исполняемого файла. Ну как впечатляет? Проверьте
подключеныль динамические библиотеки.
И на последок. Допустим есть ситуация когда библиотеки циклически
зависят друг от друга, т.е. libtest1.a зависит от libtest2.a, а
libtest2.a зависит от libtest1.a тогда просто следует сделать например
так:
$ gcc -o app app.o -ltest1 -ltest2 -ltest1
Пока всё! ;)
ЧАСТЬ 2.
Иногда требуется загрузить какую нибуть библиотеку я вно не компануя
её. Например страшное слово плагин. Для этих целей у нас есть dlopen,
dlclose, dlsym и dlerror объявленны в dlfcn.h, компилируется с флагом -ldl.
void *dlopen(const char *filename, int flag);
const char *dlerror(void);
void *dlsym(void *handle, char *symbol);
int dlclose(void *handle);
Функция dlopen открывает файл filename с флагами flag. Вернее
откравает она его в том случае, если он еще небыл кем-то открыт в этом
случае она увеличивет счетчик ссылок возврашает дискриптор библиотеки.
Путь к библиотеки можно не указывать, тогда бибилиотека ищется .
dlclose совершает обратную работу уменьшает счетчик и закрывает
библиотеку, если её больше не использует.
Функция dlsys возврашает указатель на функцию или переменную с именем
symbol для библиотеки Handle.
Функция dlerror возвращает струказатель на строку с описанием ошибки.
Пример:
void * handle = dlopen("mylib.so", RTLD_LAZY);
void (* test)() = dlsym( handle, "myfunc");
(* test)();
dlclose(handle);
Если библиотека пишется на C++ имеет смысл объявить обще доступные
функции со спецификатором extern "C".
extern "C" void my_func();
Это делается из-за того, что компилятор C++ имеет свойство подменять
имена функций своими именами в которых закодированна информация о
функции.
Пример:
Листинг testdl.c:
------------------------------------------------------------
#include <stdio.h>
#include <dlfcn.h>
int main(int argc,char** argv[])
{
/* Проверяем кол-во параметров.
Если чего не так выводим сообщение об ошибке и уходим. */
if (argc<2) {
fprintf(stderr,"Usage: %s libname\n",argv[0]);
exit(1);
}
/* Пытаемся загрузить библиотеку. */
printf("Try to load library %s\n",argv[1]);
void * handle = dlopen(argv[1], RTLD_LAZY);
/* Если чего не так выводим сообщение об ошибке и уходим. */
if (handle == NULL) {
fprintf(stderr,"%s",dlerror());
exit(2);
}
printf("Ok! Loaded!\n");
/* Пытаемся загрузить функцию библиотеки. */
void (*libfunc)(int) = dlsym(handle, "libfunc");
/* Если чего не так выводим сообщение об ошибке и уходим. */
if (libfunc == NULL) {
fprintf(stderr,"%s\n",dlerror());
exit(3);
}
/* Выводим название загруженной библиотеки. */
printf("Loaded library: %s \nLib output:\n",argv[1]);
/* Вызываем функцию из библиотеки передаем при этом дискриптор
для вывода данных. */
libfunc(stderr);
/* Закрываем библиотеку. */
dlclose(handle);
return 0;
}
------------------------------------------------------------
Листинг mygettime.c:
------------------------------------------------------------
#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
void libfunc(int fd)
{
struct timeval tv;
struct tm* ptm;
char * time_string[40];
/* Получаем локальное время. */
gettimeofday(&tv, NULL);
ptm = localtime(&tv.tv_sec);
/* Преобразуем в строку. */
strftime(time_string, sizeof(time_string), "%H:%M:%S", ptm);
/* Выводим время. */
fprintf(fd,"Local time: %s\n",time_string);
return;
}
------------------------------------------------------------
Листинг myversion.c:
------------------------------------------------------------
#include <fcntl.h>
void libfunc(int fd)
{
int in_fd;
int rval;
char *buffer=calloc(512,sizeof(char));
/* Открываем файл /etc/issue. */
in_fd = open("/etc/issue",O_RDONLY);
if(in_fd == -1) {
fprintf(fd,"Error open /etc/issue\n");
exit(1);
}
/* Читаем из /etc/issue и пишем в fd. */
while ((rval=read(in_fd,buffer,512))!= 0)
fprintf(fd,"%s",buffer);
return;
}
------------------------------------------------------------
Листинг Makefile:
------------------------------------------------------------
### Конфигурация. ###################################
SOURCES = testdl.c
MODULES = mygettime.so myversion.so
### Правила. ########################################
# Служебный целевой модуль
.PHONY: all clean
# Стандартный целевой модуль: компиляция всех фалов.
all: $(MODULES) testdl
# Удаление всего чего насобирали.
clean:
rm -f $(MODULES) testdl
# Собираем гвавный файл.
testdl: $(SOURCES)
$(CC) $(CFLAGS) -Wl,-export-dynamic -o $@ $^ -ldl
# Собираем библиотеки.
$(MODULES): \
%.so: %.c
$(CC) $(CFLAGS) -fPIC -shared -o $@ $<
------------------------------------------------------------
С файлами надеюсь всё понятно. Testdl.c наша главная программа которая
будет использовать наши библиотечки. Файл mygettime.c и myversion.c
файлы для библиотек выводят текушее время и версию системы. Makefile
это, что бы нам руками всё время команды не писать для компиляции, а
набрали make и всё собралось, набрали make clean и всё собраное
удалилось.
По тестируем всё это:
$ make
$ export LD_LIBRARY_PATH=/path_to_current_dir
$ ./testdl mygettime.so && ./testdl myversion.so
Try to load library mygettime.so
Ok! Loaded!
Loaded library: mygettime.so
Lib output:
Local time: 09:44:00
Try to load library myversion.so
Ok! Loaded!
Loaded library: myversion.so
Lib output:
ASPLinux release 9 (Ural)
Kernel 2.4.20-9asp on an i686
Спрашивается, на зачем мы передавали в функции stderr, если всё равно
идет вывод на экран. а мы за одно посмотрим как перенаправить поток.
Есть три стандартаных дескриптора файлов это 0, 1, 2 или stdin,
stdout, stderr. Перенаправим в файлик test.log стандартный вывод
ошибок.
$ ./testdl myversion.so 2>test.log
Try to load library myversion.so
Ok! Loaded!
Loaded library: myversion.so
Lib output:
$ cat test.log
ASPLinux release 9 (Ural)
Kernel 2.4.20-9asp on an i686
$
Спрашивается. а зачем. Ну даже не знаю. Можно например запустить две
программы терминала в графическом режиме. Затем в одной запустить
другую программу например компиляцию чего-нибуть и перенаправить вывод
ощибок в другое окно, чтобы не путались сообщения об ошибках и
сообщения о ходе выполнения. Или в файли можно лог ошибок сохранить.
Да малоли чего. Пример
$ echo "Hi, everybody." >>/dev/pts/1
или
$ make 2>test.log 1>/dev/pts/1
Всё!
Сильно укороченная и написанная расхлябанным языком глава 2.3 из книги Advanced Linux Programming (mark Mitchell, Jeffrey Oldman, Alex Samuel) английский оригинал ISBN 0-7357-1043-0 русское издание 5-8459-0243-6
Между прочим, на источники полезно ссылаться хоть бы для того, чтобы читатели могли расширить знания.
Как откомпилировать шареную библиотеку которая использует другие библиотеки, чтобы
1. ldd выдавал зависимости
2. при линковке програмы с етой библиотекой автоматом подключались все зависимости
А то у меня не получаеться
Обясню детальней
К примеру есть libfirst.so он использует потоки (-lpthread)
Затем я использую со-шку в своей програме и во время линоковки выдает
/usr/local/lib/libfirst.so: undefined reference to 'pthread_create'
Пока я явно не укажу в линкере использовать -lpthread. Что я не так делаю?