LinuxBegin.ru - offline-версия от 26.04.2004
Главная | Все темы | Все статьи | Ссылки | Аналоги Windows-программ в Linux | Top 20

Совместно используемые библиотеки в C под Linux для начинающих.
(Основы программирования в Linux.)


- LogRus, 4.11.03, club.shelek.com, оригинал -

Для начала напишем два файлика 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
#include

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

 

Иногда требуется загрузить какую-нибудь библиотеку, явно не компануя её. Например, страшное слово плагин. Для этих целей у нас есть 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
#include

int main(int argc,char** argv[])
{
/* Проверяем кол-во параметров.
Если чего не так выводим сообщение об ошибке и уходим. */
if (argc<2) {
fprintf(stderr,"Usage: %s libname ",argv[0]);
exit(1);
}

/* Пытаемся загрузить библиотеку. */
printf("Try to load library %s ",argv[1]);
void * handle = dlopen(argv[1], RTLD_LAZY);
/* Если чего не так выводим сообщение об ошибке и уходим. */
if (handle == NULL) {
fprintf(stderr,"%s",dlerror());
exit(2);
}

printf("Ok! Loaded! ");

/* Пытаемся загрузить функцию библиотеки. */
void (*libfunc)(int) = dlsym(handle, "libfunc");
/* Если чего не так выводим сообщение об ошибке и уходим. */
if (libfunc == NULL) {
fprintf(stderr,"%s ",dlerror());
exit(3);
}

/* Выводим название загруженной библиотеки. */
printf("Loaded library: %s Lib output: ",argv[1]);

/* Вызываем функцию из библиотеки передаем при этом дискриптор
для вывода данных. */
libfunc(stderr);

/* Закрываем библиотеку. */
dlclose(handle);
return 0;
}
------------------------------------------------------------


Листинг mygettime.c:

------------------------------------------------------------

#include
#include
#include
#include

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 ",time_string);
return;
}
------------------------------------------------------------

Листинг myversion.c:

------------------------------------------------------------
#include

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 ");
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

Всё!

Просмотров: 1467  |  Рейтинг статьи: 4.5 (Голосов: 4).
4.5
Дата размещения статьи: 2003-11-08 18:37:12



5 последних поступлений в раздел Основы программирования в Linux.:

29.12.03: Однострочник месяца на Perl: Приключение с произвольными архивами ("Резолв Дот Конф, простой... м-м-м, скажем, сисадмин. Недавно его жесточайшим образом заставили устан...)
29.12.03: Создание man-страниц (Работа каждой программы, запускаемой из UNIX shell'а, описана в man-странице. В этой заметке мы рассм...)
10.12.03: Новая рассылка "Программирование в Linux с нуля" (Автор рассылки (программист под Linux со стажем) поэтапно научит читателей профессиональному програм...)
06.12.03: Занимательное пингвиностроение. Фундамент (Этим коротким вступлением я собираюсь начать небольшой цикл статей, посвященный некоторым аспектам на...)
22.11.03: OSS API - Цифровой звук (Крайне рекомендуется ознакомиться с главой Programming Guidelines раздела Introduction документации п...)

Комментарии:

LinuxBegin.ru © 2003-2004 Valery V. Kachurov | Условия использования материалов | О проекте