Утилита libtool

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

В нашем калькуляторе мы содержимое исходника calculate.c целиком включали в исполняемый файл. Следовательно, если мы захотим те же функции использовать в другой программе, мы должны писать их заново. Можно, конечно, воспользоваться широко известными командами Выделить-Копировать-Вставить. Но, всё равно, это – лишние телодвижения, лишнее увеличение в размерах исполняемого файла вследствие засорения его рутинными функциями. Да и самого исходника может не оказаться под рукой. А, может быть, я захочу внести в эти функции улучшение. Это значит, придётся переделывать все программы, в которых эти функции используются, чтобы во всех этих программах были новые версии функций. Нельзя ли, чтобы они находились где-нибудь в одном месте, и их можно было брать оттуда для всех программ, когда они понадобятся?

Как вы, наверное, догадываетесь, мы будем делать из исходника calculate.c библиотеку, которую потом будем подключать к исполняемой программе, полученной из одного main.c.

Итак, берёмся за очередную переделку нашего калькулятора.

Простейший способ создать библиотеку из файла calculate.c следующий:

gcc -c calculate.c

ar cru libcalculate.a calculate.o

ranlib libcalculate.a

Здесь gcc с флагом -c получает известный уже нам объектный файл, команда ar создаёт архив, в виде которых библиотеки и существуют. А команда ranlib генерирует индекс функций, содержащихся в библиотеке, и сохраняет этот индекс в её же файле. Эту процедуру всегда рекомендуется проделывать, поскольку индекс ускоряет процедуру связывания библиотеки с программой.

В дальнейшем этот файл можно вручную поместить в стандартный каталог библиотек (это /usr/lib и /usr/local/lib) и можно связывать её с помощью флага -lcalculate (обратите внимание, что название библиотек должно начинаться с префикса lib-).

Но понятно, что это нерациональный способ в отношении производительности труда. Давайте рассмотрим более практичные варианты. Для этого мы познакомимся с ещё одним компонентом набора инструментов GNU – libtool. Его предназначение – автоматизировать рутинные операции по работе с библиотеками и обеспечивать разработчику удобство при их написании и тестировании.

Создайте новый каталог проекта libtooldemo. Перенесите в него файлы calculate.c и main.c из C-версии калькулятора. Только обязательно удалите из каждого из них три строчки, требующие подключения заголовочного файла config.h. Пока мы пользоваться этим заголовочным файлом не будем.

#ifdef HAVE_CONFIG_H

#include <config.h>

#endif

Вышеуказанные строки должны быть удалены!

А что же файл calculate.h?

О нём особый разговор. Наша библиотека будет компилироваться компилятором C. Но вызываться она может и из программы,написанной на С++. А компилятор C++ имеет такую особенность – он изменяет названия функций при компиляции. Следовательно, название функции в вызывающей программе будет не совпадать с названием функции в библиотеке.

Чтобы избежать такой ситуации, надо в заголовочном файле, включаемом в код исполняемой программы явно указать, что библиотечная функция написана на C. Таким образом, наш новый заголовочный файл calculate.h будет выглядеть так:

///////////////////////////////////////

// calculate.h

#ifndef CALCULATE_H_

#define CALCULATE_H_

#ifdef __cplusplus

extern"C" {

#endif /*__cplusplus*/

float Calculate(float Numeral, char Operation[4]);

#ifdef __cplusplus

}

#endif /*__cplusplus*/

#endif /*CALCULATE_H_*/

Давайте создадим подключаемую библиотеку из исходника calculate.c. Для этого вначале создадим объектный файл.

libtool gcc -c calculate.c

Посмотрите, что появилось в каталоге проекта. Это, во-первых, уже знакомый нам объектный файл calculcate.o. Во-вторых, это скрипт calculate.lo, сгенерированный утилитой libtool. Он будет указывать, как собрать библиотеку из объектных файлов. Больше ничего не видите? Тогда наберите:

cd .libs

И вы обнаружите, что перешли во вложенный каталог .libs, который не виден в файловом менеджере, потому что по умолчанию в нём не видны все файлы и папки, названия которых начинаются с точки. В этом вложенном каталоге находится другая версия объектного файла calculate.o. Разница между ними в том, то один имеет позиционно-независимый код, а другой – позиционно-зависимый. Но позвольте пока не вдаваться в подробности об этом. Утилита libtool сама решит за нас, какой вариант лучше взять для формирования библиотеки при данной конфигурации системы.

Наконец сформируем саму библиотеку.

libtool gcc -rpath /usr/local/lib -o libcalculate.la calculate.lo

В проекте появился файл libcalculate.la. Это скрипт, который понадобится программе libtool. Сама же библиотека находится во вложенном каталоге .libs. Если вы туда зайдёте, вы обнаружите файл с расширением статической библиотеки libcalculate.a и файлы с расширением динамической библиотеки libcalculate.so (мы уже в своё время упоминали что эти типы библиотек имеют именно такие расширения файлов).

Содержимое файла libcalculate.a связывается с программой при компоновке, но библиотечных функций этот файл не содержит. Она содержит лишь ссылку на другой файл – libcalculate.so, который содержит библиотечные функции и связывается с программой динамически, то есть, по требованию в процессе выполнения программы.

Опция -rpath с указанием пути к стандартному каталогу библиотек указывает компоновщику, что необходимо создать библиотеку с динамическим связыванием, то есть предусмотреть оба – и a-файл и so-файл, и что они при инсталляции будут помещаться именно в тот каталог, который указан после -rpath. Путь к этому каталогу будет сохранён в a-файле, и именно по этому пути будет осуществляться поиск динамической библиотеки.

Можно указать компоновщику, что нужно создать статическую библиотеку. Для этого следует воспользоваться опцией -static.

libtool gcc -static -o libcalculate.la calculate.lo

Как вы видите, опции -rpath здесь нет. Она и не нужна, так как все библиотечные функции будут включены в исполняемый файл и нет необходимости что-либо откуда-либо вызывать динамически.

В каталоге .libs вы so-файла уже не найдёте, так как всё его содержимое включено в статически подключаемый a-файл.

Давайте теперь создадим исполняемый файл, который будет связан с этой библиотекой.

libtool gcc -o kalkul main.c libcalculate.la -lm

Можно при желании сделать так, чтобы математические функции входили в нашу библиотеку. Для этого перекомпонуем её, с указанием обязательного включения libm.

libtool gcc -rpath /usr/local/lib -o libcalculate.la calculate.lo -lm

Тогда к исполняемой программе можно уже не подключать libm.

libtool gcc -o kalkul main.c libcalculate.la

Поскольку мы исполняемый файл kalkul формировали с помощью libtool, он также помещается в каталог .libs, а в каталоге проекта формируется скрипт с таким же названием kalkul. Но это не должно вас беспокоить. Эти скрипты создаются в целях обеспечения главного удобства libtool. Главное удобство в том, что она позволяет одновременно в одном каталоге работать и с исполняемыми программами и с необходимыми им библиотеками. Попробуйте запустить исполняемую программу. Программа без проблем найдёт и откроет библиотеку, хотя эта библиотека расположена не в системном каталоге. Это позволяет избежать лишнего обращения к системному каталогу на этапе разработки.

Она также даёт возможность одномоментно установить все файлы библиотеки в системный каталог. Давайте установим библиотеку libcalculate в каталог /usr/local/lib (предварительно зайдя от имени суперпользователя).

su

libtool cp libcalculate.la /usr/local/lib

exit

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

Обратите внимание, что установка должна осуществляться именно в тот каталог, который был указан в опции -rpath. В этот каталог будет скопирован не сам скрипт libcalculate.la, а статическая и динамическая библиотеки libcalculate.a и libcalculate.so.

Теперь также с помощью libtool сделаем по-новому исполняемый файл.

libtool gcc -o kalkul main.c -lcalculate

Скопируем его в системный каталог.

su

libtool cp kalkul /usr/local/bin

exit

Запустим его.

kalkul

И вот здесь скорее всего многих из вас ждёт неприятный сюрприз – программа первый раз не запустится. Она выдаст сообщение:

error while loading shared libraries: libcalculate.so.0: cannot open shared object file: No such file or directory

То есть, линкер знает путь /usr/local/lib, а загрузчик динамических библиотек его не знает. Это свойство большинства современных дистрибутивов. Путь к загружаемым библиотекам придётся добавлять в систему вручную. Это ужасно неудобно. Человек, впервые столкнувшийся с этой проблемой, будет долго думать, в чём дело. Сделано так, вероятно, в целях безопасности, чтобы «левая» библиотека, случайно установленная невнимательным пользователем, не вступила в конфликт с системными библиотеками. Разумеется, вы должны очень подробно описать эту ситуацию для пользователя в файле README.

Добавим вручную нужный нам путь. Для этого надо задать системной переменной LD_LIBRARY_PATH значение /usr/local/lib (по умолчанию эта переменная в большинстве версий Linux вообще отсутствует).

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

Снова запускаем kalkul. Наконец-то он заработал!

Замести следы своих экспериментов можно следующим образом:

su

libtool rm /usr/local/lib/libcalculate.la

libtool rm /usr/local/bin/kalkul

exit