PVOID - Динамически загружаемые (DL) библиотеки

Динамически загружаемые (DL) библиотеки

 Динамически загружаемые библиотеки — это библиотеки, которые загружаются не при запуске программы. Они особенно полезны для реализации плагинов или модулей, потому что они позволяют выполнить загрузку плагина тогда, когда он действительно нужен. Например, система подключаемых модулей аутентификации (PAM) использует DL библиотеки, чтобы позволить администраторам настраивать и перенастраивать аутенфикацию. Они также полезны для реализации интерпретаторов, которым время от времени требуется компилировать свой код в машинный код, а затем использовать скомпилированную версию кода с целью повышения эффективности, и все это без остановки. Например, такой подход может быть полезен для реализации JIT-компиляторов или многопользовательского мира (MUD).

 В Linux DL библиотеки в действительности не являются чем-то особенным с точки зрения формата; они строятся как обычные объектные файлы или обычные общие библиотеки, описанные ранее. Главным отличием является то, что эти библиотеки не загружаются автоматически при компоновке или старте программы; вместо этого существует API для открытия, просмотри символов, обработки ошибок и закрытия библиотеки. Пользователям языка Си необходимо подключить заголовочный файл <dlfcn.h> для использования этого API.

 Интерфейс, используемый в Linux, практически такой же, как в Solaris, который мы назовем «dlopen()» API. Тем не менее, этот интерфейс поддерживается не всеми платформами; HP-UX использует другой механизм shl_load(), а платфорсы Windows используют библиотеки DLL с полностью другим интерфейсом. Если вашей целью является широкая переносимость, вам, вероятно, следует подумать об использовании некоторой библиотеки-обёртки, которая скроет различия между платформами. Одним из подходов является библиотека glib с поддержкой динамической загрузки модулей; она использует основные процедуры динамической загрузки конкретной платформы для реализации переносимого интерфейса для этих функций. Если вам необходима большая функциональность, чем эта, вы можете посмотреть на CORBA Object Request Broker (ORB). Если вы все еще заинтересованы в непосредственном использовании интерфейса, поддерживаемого в Linux и Solaris, читайте дальше.

 Разработчики, использующие C++ и динамически загружаемые библиотеки, также должны обратиться к «C++ dlopen mini-HOWTO».

 

 dlopen()


 Функция dlopen(3) открывает библиотеку и подготавливает ее к использованию. Её прототип на языке Си:

void * dlopen(const char *filename, int flag);

 Если filename начинается с «/» (т.е. абсолютный путь), dlopen() просто попробует использовать её (не будет производить поиск библиотеки). Иначе, dlopen() будет искать библиотеку в следующем порядке:

  1. Список директорий, разделенных двоеточием, в пользовательской переменной окружения LD_LIBRARY_PATH.

  2. Список библиотек, указанных в /etc/ld.so.cache (который генерируется на основе /etc/ld.so.conf).

  3. Директория /lib, а затем /usr.lib. Обратите внимание на этот порядок; он является обратным порядку, используемому старым загрузчиком a.out. Старый загрузчик a,out при загрузке программ сначала производит поиск в /usr/lib, затем в /lib (смотрите ld.so(8)). Это обычно не должно иметь значения, поскольку библиотека должна находиться либов одном каталоге, либов другом (никогда в обоих), а различные библиотеки с одинаковыми именами это катастрофа.

 В dlopen() значение flag должно быт либо RTLD_LAZY, что значит «разрешить неопределенные символы при выполнении кода из динамической библиотеки», либо RTLD_NOW, что означает «разрешить все неопределенные символы до завершения dlopen() и вернуть ошибку, если это не может быть сделано». RTLD_GLOBAL опционально может быть с любым значением flag, что означает, что внешние символы, определенные в библиотеке, будут доступны для последующих загруженных библиотек. Во время отладки вы ,вероятно, захотите использовать RTDL_NOW; использование RTLD_LAZY может привести к загадочным ошибкам, если есть неразрешенные ссылки. Использование RTLD_NOW делает открытие библиотеки дольше (но это ускоряет поиск позже); если это вызывает проблемы с пользовательским интерфейсом, вы можете переключиться на RTLD_LAZY позже.

 Если одна библиотека зависит от другой (т.е. X зависит от Y), то вам необходимо сперва загрузить зависимости (в этом примере, сперва загрузить Y, а затем X).

 Возвращаемое значение функции dlopen() это «дескриптор», который следует считать непрозрачным значением, которое будет использоваться другими процедурами DL библиотеки. dlopen() вернет NULL, если загрузка библиотеки не удалась, и вам необходимо проверять это. Если одна и та же библиотека загружена более одного раза с помощью dlopen(), возвращается один и тот же файловый дескриптор.

 На старых системах, если билиотека экспортирует процедуру с именем _init, то этот код выполняется до выхода из dlopen(). Вы можете использовать этот факт в ваших собственных библиотеках для инициализации процедур инициализации. Однако, библиотеки не должны экспортировать процедуры с именами _init или _fini. Этот механизм является устаревшим и может привести к неопределенному поведению. Вместо этого. библиотеки должны экспортировать процедуры, используя атрибуты функции __attribute__((constructor)) и __attribute__((destructor)) (предполагается, что вы используете gcc). Смотрите секцию Конструктор и деструктор библиотеки для дополнительной информации.

 

 dlerror()


 Ошибки могут быть получены вызовом dlerror(), который возвращает строку, описывающую ошибку последнего вызова dlopen(), dlsym() или dlclose(). Одна странность заключается в том, что после вызова dlerror() следующие вызовы dlerror() будут возвращать NULL, пока не будет обнаружена какая-либо ошибка.

 

 dlsym()


 Нет смысла загружать DL библиотеку, если вы не можете ее использовать. Главная процедура для использования DL библиотеки это dlsym(3), которая ищет значение символа в данной (открытой) библиотеке. Эта функция определена как:

void * dlsym(void *handle, char *symbol);

 handle является значением, возвращаемым из dlopen, а symbol это нуль-терминированная строка. Если вы можете избежать этого, не храните результат dlsym() в указателе типа void*, иначе вам придется выполнять приведение типов при каждом использовании (и вы дадите меньше информации другим разработчикам, пытающимся поддерживать программу).

 dlsym() будет возвращать NULL, если символ не найден. Если вы знаете, что символ никогда не принимает значение NULL или 0, это может быть хоршо, однако существует потенциальная неоднозначность в противном случае: если вы получили NULL, значит ли это, что нет такого символа, или этот NULL является значением символа? Стандартным решением является предварительный вызов dlerror() (для очистки какой-либо ошибки, которая могла существовать), затем вызов dlsym() для получения символа, а затем повторный вызов dlerror(), чтобы посмотреть, не произошла ли ошибка. Фрагмент кода будет выглядеть так:

dlerror(); /* clear error code */
 s = (actual_type) dlsym(handle, symbol_being_searched_for);
 if ((err = dlerror()) != NULL) {
  /* handle error, the symbol wasn't found */
 } else {
  /* symbol found, its value is in s */
 }

 

 dlclose()


 Функция ldclose(), которая закрывает DL библиотеку, является обратной функции dlopen(). Dl библиотека поддерживает счетчик ссылок для динамических файловых дескрипторов, так что динамическая библиотека в действительности не выгружается, пока dlclose не будет вызвана для нее столько же раз, сколько было успешных вызовов dlopen для этой библиотеки. Следовательно, многократная загрузка библиотеки одной и той же программой не является проблемой. Если библиотека выгружена, вызывается ее функция _fini (при наличии) для старых библиотек, но _fini является устаревшим механизмом, на который не следует полагаться. Вместо этого библиотеки должны экспортировать процедуры, используя фтрибуты функции __attribute__((constructor)) и __attribute__((destructor)). Смотрите секцию Конструктор и деструктор библиотеки для дополнительной информации. Примечание: dlclose() возвращает 0 в случае успеха и ненулевое значение в случае ошибки; некоторые страницы руководства Linux не упоминают об этом.

 

 Пример DL библиотеки


 Вот пример из справочной страницы dlopen(3). Этот пример загружает библиотеку math и печатает косинус 2.0, также он проверяет ошибки на каждом шаге (рекомендуется):

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

int main(int argc, char **argv) {
  void *handle;
  double (*cosine)(double);
  char *error;

  handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
  if (!handle) {
    fputs (dlerror(), stderr);
    exit(1);
  }

  cosine = dlsym(handle, "cos");
  if ((error = dlerror()) != NULL)  {
    fputs(error, stderr);
    exit(1);
  }

  printf ("%f\n", (*cosine)(2.0));
  lclose(handle);
}

 Если бы эта программа находилась в файле с именем «foo.c», вы бы могли собрать программу с помощью следующей команды:

gcc -o foo foo.c -ldl

 


Этот раздел является переводом руководства Program Library HOWTO


  <<< Назад Содержание Вперед >>>