Библиотека сайта rus-linux.net
Цилюрик О.И. Linux-инструменты для Windows-программистов | ||
Назад | Компиляция и сборка приложений | Вперед |
Библиотеки: построение
А теперь мы дополним изложение предыдущего раздела, позволяющее скомпоновать приложение с любой библиотекой, ещё и умением самим изготавливать такие библиотеки.
Когда у нас есть в приложении несколько (как минимум
2, или сколь угодно более) отдельно скомпилированных объектных модуля
(один из которых — главный объектный модуль с функцией main()
),
то у нас есть, на выбор, целый спектр возможностей скомпоновать их в
единое приложение:
а). всё скомпоновать в единое монолитное приложение;
б). собрать модули в статическую библиотеку и прикомпоновывать её к приложению;
в). собрать модули в автоматически подгружаемую разделяемую библиотеку;
г). собрать модули в динамически подгружаемую по требованию разделяемую библиотеку;
Все эти возможности показаны в архиве примеров libraries.tgz.
Во всех случаях (кроме последнего 4-го) используем
практически одни исходные файлы (см. архив примера), главным отличием
будут Makefile
для сборки (сравнивайте размеры аналогичных файлов!):
<i><u><b>hello_main.c</b></u></i> #include "hello_child.h" int main( int argc, char *argv[] ) { char *messg = "Hello world!\n"; int res = put_my_msg( messg ); return res; }; <i><u><b>hello_child.c</b></u></i> #include "hello_child.h" int put_my_msg( char *messg ) { printf( messg ); return -1; }; <i><u><b>hello_child.h</b></u></i> #include <stdio.h> int put_my_msg( char* );
Собираем цельное монолитное приложение из отдельно компилируемых объектных модулей (никакая техника библиотек не используется):
<i><u><b>Makefile</b></u></i> TARGET = hello MAIN = $(TARGET)_main CHILD = $(TARGET)_child all: $(TARGET) $(TARGET): ../$(MAIN).c ../$(CHILD).c ../$(CHILD).h gcc ../$(MAIN).c ../$(CHILD).c -o $(TARGET) rm -f *.o
Выполнение:
<b>$ make</b> gcc ../hello_main.c ../hello_child.c -o hello </tt> rm -f *.o </tt> <b>$ ./hello</b> Hello world! <b>$ ls -l hello </b></tt> -rwxrwxr-x 1 olej olej 4975 Июл 30 15:25 hello </tt>
Собираем аналогичное приложение с использованием статической библиотеки:
<i><u><b>Makefile</b></u></i></p> TARGET = hello MAIN = $(TARGET)_main CHILD = $(TARGET)_child LIB = lib$(TARGET) all: $(LIB) $(TARGET) $(LIB): ../$(CHILD).c ../$(CHILD).h gcc -c ../$(CHILD).c -o $(CHILD).o ar -q $(LIB).a $(CHILD).o rm -f *.o ar -t $(LIB).a $(TARGET): ../$(MAIN).c $(LIB) gcc ../$(MAIN).c -Bstatic -L./ -l$(TARGET) -o $(TARGET)
Объектные модули в статическую библиотеку (архив) в
Linux собирает (добавляет, удаляет, замещает, ...) утилита ar
из пакета binutils
(но если у вас установлен пакет gcc
,
то он по зависимостям установит и пакет binutils
).
Архив .a
в Linux не является специальным библиотечным форматом, это набор
отдельных компонент с каталогом их имён, он может использоваться для
самых разных целевых назначения. Но, в частности, с ним, как с
хранилищем объектных модулей умеет работать компоновщик ld
.
Выполнение для этого варианта сборки:
<b>$ make</b> gcc -c ../hello_child.c -o hello_child.o ar -q libhello.a hello_child.o ar: creating libhello.a rm -f *.o ar -t libhello.a hello_child.o gcc ../hello_main.c -Bstatic -L./ -lhello -o hello <b>$ ./hello </b> Hello world! <b>$ ls -l hello </b> -rwxrwxr-x 1 olej olej 4975 Июл 30 15:31 hello
Обращаем внимание, что размер собранного приложения здесь в точности соответствует предыдущему случаю (что совершенно естественно: собираются в точности идентичные экземпляры приложения, только источники одних и тех же объектных модулей для их сборки используются различные, в первом случае — это модули в отдельных файлах, во втором — те же модули, но в каталогизированном архиве).
Переходим к сборке разделяемых библиотек. Объектные
модули для разделяемой библиотеки должны быть компилированы в
позиционно независимый код (PIC - перемещаемый, не привязанный к
адресу размещения — ключи -fpic
или -fPIC
компилятора).
Примечание: Документация говорит, что опция
-fPIC
обходит некоторые ограничения -fpic
на некоторых аппаратных платформах (m68k, Spark), но в обсуждениях
утверждается, что из-за некоторых ошибок в реализации -fpic
,
лучше указывать обе эти опции, хуже от этого не становится. Но всё
это, наверное, очень сильно зависит от версии компилятора.
С учётом всех этих обстоятельств, собираем автоматически подгружаемую (динамическая компоновка) разделяемую библиотеку 4:
<i><u><b>Makefile</b></u></i> TARGET = hello MAIN = $(TARGET)_main CHILD = $(TARGET)_child LIB = lib$(TARGET) all: $(LIB) $(TARGET) $(LIB): ../$(CHILD).c ../$(CHILD).h gcc -c -fpic -fPIC -shared ../$(CHILD).c -o $(CHILD).o gcc -shared -o $(LIB).so $(CHILD).o rm -f *.o $(TARGET): ../$(MAIN).c $(LIB) gcc ../$(MAIN).c -Bdynamic -L./ -l$(TARGET) -o $(TARGET)
Сборка приложения:
<b>$ make</b> gcc -c -fpic -fPIC -shared ../hello_child.c -o hello_child.o gcc -shared -o libhello.so hello_child.o rm -f *.o gcc ../hello_main.c -Bdynamic -L./ -lhello -o hello <b>$ ls </b> hello libhello.so Makefile <b>$ ls -l hello </b> -rwxrwxr-x 1 olej olej 5051 Июл 30 15:40 hello
Отмечаем, что на этот раз размер приложения отличается и, вопреки ожиданиям, в сторону увеличения. Конкретный размер, пока, нас не интересует, но различия в размере говорят, что это — совершенно другое приложение, в отличие от предыдущих случаев. На этот раз запустить приложение будет не так просто (на этот счёт смотрите подробное разъяснение о путях поиска библиотек: текущий каталог не входит и не может входить в пути поиска динамических библиотек):
<b>$ ./hello</b> ./hello: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
Мы можем (для тестирования) обойти эту сложность следующим образом:
<b>$ export LD_LIBRARY_PATH=`pwd`; ./hello </b> Hello world!
Отметим:
- после такого запуска переменная окружения уже установлена в текущем терминале, и в последующем может быть многократно использована приложением:
<b>$ ./hello </b> Hello world!
- но это относится только к текущему терминалу, в любом другом терминале картина повторится сначала...
- пути поиска динамических библиотек не могут быть заданы
относительными путями (./, ../,
...), а могут быть только абсолютными (от корня
файловой системы /
):
<b>$ echo $LD_LIBRARY_PATH </b> /home/olej/2011_WORK/GlobalLogic/my.EXAMPLES/examples.DRAFT/libraries/auto
- последнее (абсолютность путей) и понятно, поскольку для динамических библиотек сборка и использование — это два совершенно различных акта во времени, и на сборке невозможно предугадать какой каталог будет текущим при запуске, и от какого отсчитывать относительные пути.
Наконец, собираем динамически подгружаемую по требованию (динамическая загрузка) разделяемую библиотека. В этом случае главный файл приложения имеет иной вид:
<i><u><b>hello_main.c</b></u></i> #include <dlfcn.h> #include "../hello_child.h" typedef int (*my_func)( char* ); int main( int argc, char *argv[] ) { char *messg = "Hello world!\n"; // Открываем совместно используемую библиотеку void *dl_handle = dlopen( "./libhello.so", RTLD_LAZY ); if( !dl_handle ) { printf( "ERROR: %s\n", dlerror() ); return 3; } // Находим адрес функции в библиотеке my_func func = dlsym( dl_handle, "put_my_msg" ); char *error = dlerror(); if( error != NULL ) { printf( "ERROR: %s\n", dlerror() ); return 4; } // Вызываем функцию по найденному адресу int res = (*func)( messg ); // Закрываем библиотеку dlclose( dl_handle ); return res; };
Примечание: Отметим важное малозаметное
обстоятельство: вызов функции библиотеки (*func)(messg)
по имени происходит без какой-либо синтаксической проверки на
соответствие тому прототипу, который объявлен для типа функции my_func
.
Соответствие обеспечивается только «доброй волей»
пишущего код. С таким же успехом функция могла бы вызываться с 2-мя
параметрами, 3-мя и так далее. На это место нужно обращать
пристальное внимание при написании реальных плагинов.
<i><u><b>Makefile</b></u></i></p> TARGET = hello MAIN = $(TARGET)_main CHILD = $(TARGET)_child LIB = lib$(TARGET) all: $(LIB) $(TARGET) $(LIB): ../$(CHILD).c ../$(CHILD).h gcc -c -fpic -fPIC -shared ../$(CHILD).c -o $(CHILD).o gcc -shared -o $(LIB).so $(CHILD).o rm -f *.o *.so $(TARGET) $(TARGET): $(MAIN).c $(LIB) gcc $(MAIN).c -o $(TARGET) -ldl
Сборка:
<b>$ make</b> gcc -c -fpic -fPIC -shared ../hello_child.c -o hello_child.o gcc -shared -o libhello.so hello_child.o rm -f *.o gcc hello_main.c -o hello -ldl <b>$ ls </b> hello hello_main.c libhello.so Makefile <b>$ ls -l hello </b> -rwxrwxr-x 1 olej olej 5487 Июл 30 16:06 hello
Выполнение:
<b>$ ./hello </b> Hello world! <b>$ echo $? </b> 255
- для дополнительного контроля код приложения написан так, что код возврата приложения в систему (255) равен значению, возвращённому функцией из динамической библиотеки.
4. Компиляция
и сборка самой библиотеки в этом и следующем примере записана 2-мя
строками, командами вызова gcc
:
первая компилирует модуль, а вторая помещает его в библиотеку. Это
сделано для наглядности, чтобы разделить опции (ключи) компиляции и
сборки. На практике эти две строки записываются в один вызов gcc
,
который обеспечивает и то и другое действие.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Библиотеки: связывание | Как это всё работает? |