Описанные преимущества могут заставить читателей подумать, будто статические архивы бесполезны. Но их существование обусловлено вескими причинами. Тот факт, что обновление совместно используемой библиотеки отражается на всех связанных с нею программах, может на самом деле оказаться недостатком. Некоторые программы тесно связаны с используемыми библиотеками и не должны зависеть от произвольных изменений в системе.
Если библиотеки не должны инсталлироваться в каталог /lib
или /usr/lib
, нужно дважды подумать, стоит ли их делать совместно используемыми. (Библиотеки нельзя помещать в указанные каталоги, если предполагается, что программу будут инсталлировать пользователи, не имеющие привилегий системного администратора.) В частности, прием с флагом -Wl,-rpath
не будет работать, поскольку не известно, где именно окажутся библиотеки. А просить пользователей устанавливать переменную LD_LIBRARY_PATH
— не выход из положения, так как это означает для них выполнение дополнительного (для некоторых — не самого тривиального) действия.
Оценивать преимущества и недостатки двух типов библиотек нужно для каждой создаваемой программы отдельно.
2.3.6. Динамическая загрузка и выгрузка
Иногда на этапе выполнения программы требуется загрузить некоторый код без явной компоновки. Рассмотрим приложение, поддерживающее подключаемые модули: Web-броузер. Архитектура броузера позволяет сторонним разработчикам создавать дополнительные модули, расширяющие функциональные возможности броузера. Модуль реализуется в виде совместно используемой библиотеки и размещается в заранее известном каталоге. Броузер автоматически загружает код из этого каталога.
Для этих целей в Linux существует специальная функция dlopen()
. Например, открыть библиотеку libtest.so
можно следующим образом:
dlopen("libtest.so", RTLD_LAZY)
Второй параметр — это флаг, определяющий способ привязки символических констант в библиотеке. Данная установка подходит в большинстве случаев. Подробнее узнать о ней можно в документации.
Объявление функций работы с динамическими библиотеками находится в файле . Использующие их программы должны компоноваться с флагом -ldl
, обеспечивающим подключение библиотеки libdl
.
Функция dlopen()
возвращает значение типа void*
, используемое в качестве дескриптора динамической библиотеки. Это значение можно передавать функции dlsym()
, которая возвращает адрес функции, загружаемой из библиотеки. Например, если в библиотеке libtest.so
определена функция my_function()
, то она вызывается следующим образом:
void* handle = dlopen("libtest.so", RTLD_LAZY);
void (*test)() = dlsym(handle, "my_function");
(*test)();
dlclose(handle);
С помощью функции dlsym()
можно также получить указатель на статическую переменную, содержащуюся в совместно используемой библиотеке.
Обе функции, dlopen()
и dlsym()
, в случае неудачного завершения возвращают NULL
. В данной ситуации можно вызвать функцию dlerror()
(без параметров), чтобы получить текстовое описание возникшей ошибки.
Функция dlclose()
выгружает совместно используемую библиотеку. Строго говоря, функция dlopen()
загружает библиотеку лишь в том случае, если она еще не находится в памяти. В противном случае просто увеличивается число ссылок на файл. Аналогичным образом функция dlclose()
сначала уменьшает счетчик ссылок, и только если он становится равным нулю, выгружает библиотеку.
Когда совместно используемая библиотека пишется на C++, имеет смысл объявлять общедоступные функции со спецификатором extern "С"
. Например, если функция my_function()
написана на C++ и находится в совместно используемой библиотеке, а нужно обеспечить доступ к ней с помощью функции dlsym()
, объявите ее следующим образом:
extern "С" void my_function();
Тем самым компилятору C++ будет запрещено подменять имя функции. При отсутствии спецификатора extern "С"
компилятор подставит вместо имени my_function
совершенно другое имя, в котором закодирована информация о данной функции. Компилятор языка С не заменяет имена; он работает с теми именами, которые назначены пользователем.
Выполняющийся экземпляр программы называется процессом . Если на экране отображаются два терминальных окна, то, скорее всего, одна и та же терминальная программа запущена дважды — ей просто соответствуют два процесса. В каждом окне, очевидно, работает интерпретатор команд — это еще один процесс. Когда пользователь вводит команду в интерпретаторе, соответствующая ей программа запускается в виде процесса. По завершении работы программы управление вновь передается процессу интерпретатора.
Читать дальше