Символьные имена, используемые в разделяемых объектах, не обязательно должны быть уникальными среди различных разделяемых объектов, загружаемых в одну и ту же программу; обычно они таковыми и не являются. Различные разделяемые объекты, написанные для одного и того же интерфейса, обычно используют точки входа с одинаковыми именами. Для обычных разделяемых библиотек такая практика будет истинным бедствием, а для разделяемых объектов, динамически загружаемых во время выполнения, именно так и поступают.
Пожалуй, чаще всего разделяемые объекты, загружаемые во время выполнения, применяются при создании интерфейса для некоторого общего средства, которое может иметь множество различных реализаций. Рассмотрим, к примеру, процедуру сохранения графического файла. Приложение может иметь один внутренний формат для управления его графикой, однако существует много других форматов файлов, в которых приложению понадобится сохранить графические данные, и еще больше форматов создано для разнообразных частных ситуаций [21]. Обобщенный интерфейс для сохранения графического файла, который экспортируется разделяемыми объектами, загружаемыми во время выполнения, позволяет программистам добавлять новые форматы графических файлов в приложение без повторной его компиляции. Если интерфейс хорошо документирован, то даже независимые разработчики, не имеющие исходного кода приложения, смогут включать новые форматы графических файлов.
Точно так же используется и код каркаса (framework), который предлагает только интерфейс, а не реализацию. Например, каркас РАМ (Pluggable Authentication Modules — подключаемые модули аутентификации) предлагает обобщенный интерфейс для методов аутентификации с запросом и подтверждением, например, с участием имен пользователей и паролей. Сам процесс аутентификации осуществляется посредством модулей, а решение о выборе модуля аутентификации для отдельно взятого приложения принимается во время выполнения (а не во время компиляции) за счет обращения к конфигурационным файлам. Этот интерфейс имеет хорошее описание и является стабильным, а новые модули можно внедрять и использовать в любой момент без повторной компиляции каркаса или приложения. Каркас загружается в виде разделяемой библиотеки, а код в этой разделяемой библиотеке загружает и выгружает модули, обеспечивающие методы аутентификации.
Процесс динамической загрузки заключается в открытии библиотеки, поиске любого количества символов, обработке любых возникающих ошибок и закрытии библиотеки. Все функции динамической загрузки объявляются в одном заголовочном файле, , и определяются в libdl
(чтобы воспользоваться функциями динамической загрузки скомпонуйте приложение с -ldl
).
Функция dlerror()
возвращает строку, описывающую самую последнюю ошибку, которая возникла в одной из трех других функций динамической загрузки:
const char * dlerror(void);
Каждый раз при возврате значения она очищает состояние ошибки. Если не будет создано другое состояние ошибки, она продолжит выполнение, чтобы вернуть NULL
вместо строки. Объяснение этого необычного поведения можно найти в описании функции dlsym()
.
Функция dlopen()
открывает библиотеку. Этот процесс включает поиск библиотечного файла, открытие файла и выполнение некоторой предварительной обработки. Переменные окружения и параметры, переданные функции dlopen()
, определяют детали этого процесса.
void * dlopen(const char * filename, int flag);
Если filename
является абсолютным путем (то есть начинается с символа /
), то функции dlopen()
не нужно производить поиск библиотеки. Это обычный способ применения функции dlopen()
в коде приложения. Если filename
является простым именем файла, то функция dlopen()
произведет поиск библиотеки filename
в перечисленных ниже местах.
• Набор каталогов, разделенных двоеточием, который определен в переменной окружения LD_ELF_LIBRARY_PATH
, или, если ее не существует, в переменной LD_LIBRARY_PATH
.
• Библиотеки, определенные в файле /etc/ld.so.cache
. Этот файл генерируется программой ldcoding
, регистрирующей каждую библиотеку, которую она находит в каталоге, указанном в /etc/ld.so.conf
, во время ее выполнения.
• /usr/lib
• /lib
Если filename равен NULL
, то функция dlopen()
открывает экземпляр текущего исполняемого файла. Это полезно только в редких случаях. В случае сбоя функция dlopen()
возвращает NULL
.
Читать дальше