Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. 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, который обеспечивает и то и другое действие.


Предыдущий раздел: Оглавление Следующий раздел:
Библиотеки: связывание   Как это всё работает?