Содержание


Инструменты ОС Linux для разработчиков приложений для ОС Windows. Часть 6. Создание собственных библиотек

Comments

Продолжая обсуждение вопросов использования библиотек в ОС Linux, начатое в предыдущей статье, мы рассмотрим, как создать собственную библиотеку и скомпоновать с ней приложение.

Сборка статических библиотек

При наличии в проекте нескольких (как минимум 2 или больше) отдельно скомпилированных объектных модулей (один из которых — главный объектный модуль с функцией main()), у нас имеются несколько возможностей скомпоновать их в единое приложение:

  1. скомпоновать все имеющиеся компоненты в единое монолитное приложение;
  2. собрать модули в статическую библиотеку и скомпоновать её с приложением;
  3. собрать модули в автоматически подгружаемую разделяемую библиотеку;
  4. собрать модули в динамически подгружаемую по требованию разделяемую библиотеку;

Во всех случаях (кроме последнего 4-го) мы будем использовать практически одни и те же исходные файлы, а отличия будут заключаться в сценариях Makefile для сборки библиотек (сравните размеры аналогичных файлов). Примеры реализации всех способов компоновки представлены в архиве libraries.tgz в разделе "Материалы для скачивания".

Листинг 1. Используемые общие файлы исходного кода
#include "hello_child.h" //файл hello_main.c
int main( int argc, char *argv[] ) {
   char *messg = "Hello world!\n";
   int res = put_my_msg( messg );
   return res;
};

#include "hello_child.h" //файл hello_child.c
int put_my_msg( char *messg ) {
   printf( messg );
   return -1;
};

#include <stdio.h>       // файл hello_child.h
int put_my_msg( char* );

Сначала соберём цельное монолитное приложение из отдельно компилируемых объектных модулей без использования библиотек.

Листинг 2. Makefile для сборки монолитного приложения
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

Запустим процесс сборки и проверим результат:

$ make
gcc ../hello_main.c ../hello_child.c -o hello
rm -f *.o
$ ./hello
Hello world!
$ ls -l hello
-rwxrwxr-x 1 olej olej 4975 Июл 30 15:25 hello

Теперь соберём аналогичное приложение, использующее статическую библиотеку.

Листинг 3. Makefile для сборки приложения с использованием статической библиотеки
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). Архивный файл формата .a в Linux не является специальным библиотечным форматом, на самом деле это набор отдельных компонентов с каталогом их имён. Но компоновщик ld может работать с ним как с хранилищем объектных модулей.

Запустим данный сценарий сборки и проверим результат:

$ make
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
$ ./hello
Hello world!
$ ls -l hello
-rwxrwxr-x 1 olej olej 4975 Июл 30 15:31 hello

Обратите внимание, что размеры собранных приложений в обоих случаях в точности совпадают, так как собираются идентичные экземпляры приложения. Разница только в источнике объектных модулей, использованных в процессе сборки, в первом случае — это модули в отдельных файлах, во втором — те же модули, но в каталогизированном архиве.

Сборка разделяемых библиотек

Теперь можно перейти к сборке разделяемых библиотек, так как объектные модули для них должны быть компилированы в позиционно независимый код (PIC - перемещаемый, не привязанный к адресу размещения — ключи -fpic или -fPIC компилятора).

С учётом этой информации, попробуем собрать автоматически подгружаемую (динамическая компоновка) разделяемую библиотеку. Для компиляции и сборки самой библиотеки в этом и следующем примерах необходимо всего две строки с командами вызова gcc для компиляции модуля и помещения его в библиотеку. В примере для большей наглядности для этого используются две отдельные команды, но на практике эти две строки записываются в один вызов gcc, который выполняет обе операции.

Листинг 3. Makefile для сборки разделяемой библиотеки
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)

Проверим, выполнив сборку и запустив приложение:

$ make
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
$ ls
hello  libhello.so  Makefile
$ ls -l hello
-rwxrwxr-x 1 olej olej 5051 Июл 30 15:40 hello

Отметим, что на этот раз размер приложения отличается в большую сторону от предыдущих примеров, хотя это и несколько неожиданно. Различие в размере приложений говорит о том, что это совершенно разные приложения, и на этот раз запустить приложение будет не так просто. Первая попытка запустить приложение окажется неудачной, так как текущий каталог не входит и не может входить в пути поиска динамических библиотек (см. раздел о поиске библиотек в предыдущей статье):

$ ./hello
./hello: error while loading shared libraries: libhello.so: \
  cannot open shared object file: No such file or directory

Эту проблему можно решить, установив переменную окружения LD_LIBRARY_PATH, хотя это решение будет работать только для текущей сессии терминала, но не в других терминалах, где снова возникнет данная ошибка.

$ export LD_LIBRARY_PATH=`pwd`; ./hello
Hello world!
$ ./hello
Hello world!

Также пути для поиска динамических библиотек не указываться как относительные (./, ../, ...), а могут использовать только абсолютные значения от корня файловой системы /:

$ echo $LD_LIBRARY_PATH
/home/olej/2011_WORK/GlobalLogic/my.EXAMPLES/examples.DRAFT/libraries/auto

Обязательное требование использовать только абсолютные имена пути связано с тем, что для динамических библиотек сборка и использование — это две совершенно различные операции, и при сборке невозможно определить, в каком каталоге будет запущено приложение, чтобы отсчитывать пути от него.

Осталось собрать разделяемую библиотеку, динамически подгружаемую по требованию. Но в этом случае нам потребуется другой файл исходного кода, показанный в листинге 4.

Листинг 4. Исходный код примера с динамически подгружаемой библиотекой (файл hello_main.c)
#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-мя и т.д. Поэтому при разработке плагинов на этот аспект нужно обращать дополнительное внимание.

Листинг 5. Makefile для сборки разделяемой, динамически подгружаемой библиотеки
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

Выполним сборку и запустим полученное приложение:

$ make
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
$ ls
hello  hello_main.c  libhello.so  Makefile
$ ls -l hello
-rwxrwxr-x 1 olej olej 5487 Июл 30 16:06 hello
$ ./hello
Hello world!
$ echo $?
255

Для дополнительного контроля код, возвращаемый приложением в систему, (число 255) равен значению, которое возвращается функцией из динамической библиотеки.

Заключение

В данной статье были рассмотрены различные подходы к созданию статических и динамических библиотек, при этом мы обратили отдельное внимание на динамически загружаемые библиотеки.

В следующей статье мы завершим обсуждение принципов работы с библиотеками в ОС Linux, заглянув "внутрь" библиотеки во время её работы.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=968482
ArticleTitle=Инструменты ОС Linux для разработчиков приложений для ОС Windows. Часть 6. Создание собственных библиотек
publish-date=04152014