Динамическая загрузка

редактировать

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

Содержание
  • 1 История
  • 2 Использует
  • 3 В C / C ++
    • 3.1 Резюме
    • 3.2 Загрузка библиотеки
      • 3.2.1 Большинство UNIX-подобных операционных систем (Solaris, Linux, * BSD и т. Д.)
      • 3.2.2 macOS
      • 3.2.3 Windows
    • 3.3 Извлечение содержимого библиотеки
      • 3.3.1 UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и т. Д.)
      • 3.3.2 Windows
    • 3.4 Преобразование указателя на библиотечную функцию
      • 3.4.1 Windows
      • 3.4.2 UNIX (POSIX)
      • 3.4.3 Решение проблемы указателя на функцию в системах POSIX
    • 3.5 Выгрузка библиотеки
      • 3.5.1 UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и т. Д.)
      • 3.5.2 Windows
    • 3.6 Специальная библиотека
      • 3.6.1 UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и т. Д.)
      • 3.6.2 Windows
  • 4 В Java
  • 5 Платформы без динамической загрузки
  • 6 См. Также
  • 7 Ссылки
  • 8 Дополнительная литература
  • 9 Внешние ссылки
История

Динамическая загрузка была обычным методом для операционных систем IBM для System / 360, например, OS / 360, особенно для подпрограмм I/O , а также для COBOL и PL / I библиотеки времени выполнения и продолжает использоваться в операционных системах IBM для z / Architecture, таких как z / OS. Что касается прикладного программиста, загрузка в значительной степени прозрачна, поскольку она в основном обрабатывается операционной системой (или ее подсистемой ввода-вывода). Основными преимуществами являются:

  • Исправления (патчи ) для подсистем, фиксирующие сразу все программы, без необходимости их повторного связывания
  • Библиотеки могут быть защищены от несанкционированного изменения

IBM стратегическая система обработки транзакций, CICS (1970-е гг.) Широко использует динамическую загрузку как для своего ядра, так и для обычной прикладной программы загрузка. Исправления в прикладных программах можно было делать в автономном режиме, а новые копии измененных программ загружались динамически без необходимости перезапуска CICS (который может и часто выполняет 24/7 ).

Общие библиотеки были добавлены в Unix в 1980-х годах, но изначально без возможности разрешить программе загружать дополнительные библиотеки после запуска.

Использует

Наиболее часто используется динамическая загрузка в реализации программных плагинов. Например, файлы подключаемых модулей «динамического общего объекта» веб-сервера Apache *.dsoпредставляют собой библиотеки, которые загружаются во время выполнения с динамической загрузкой. Динамическая загрузка также используется при реализации компьютерных программ, где несколько разных библиотек могут обеспечивать необходимую функциональность и где пользователь имеет возможность выбрать, какую библиотеку или библиотеки предоставить.

В C / C ++

Не все системы поддерживают динамическую загрузку. UNIX-подобные операционные системы, такие как macOS, Linux и Solaris, обеспечивают динамическую загрузку с помощью языка программирования C Библиотека "dl". Операционная система Windows обеспечивает динамическую загрузку через Windows API.

Сводка

ИмяСтандартный POSIX / UNIX API Microsoft Windows API
Включение файла заголовка#include #include
Определения для заголовкаdl

(libdl.so, libdl.dylibи т. Д. В зависимости от OS )

kernel32. dll
Загрузка библиотекиdlopenLoadLibrary. LoadLibraryEx.
Извлечение содержимогоdlsymGetProcAddress
Выгрузка библиотекиdlcloseFreeLibrary

Загрузка библиотека

Загрузка библиотеки выполняется с помощью LoadLibraryили LoadLibraryExв Windows и с помощью dlopenв UNIX-подобные операционные системы. Примеры:

Большинство UNIX-подобных операционных систем (Solaris, Linux, * BSD и т. Д.)

void * sdl_library = dlopen ("libSDL.so", RTLD_LAZY); if (sdl_library == NULL) {// сообщить об ошибке...} else {// использовать результат при вызове dlsym}

macOS

В качестве библиотеки UNIX :

void * sdl_library = dlopen ("libsdl.dylib", RTLD_LAZY); if (sdl_library == NULL) {// сообщить об ошибке...} else {// использовать результат в вызове dlsym}

В качестве платформы macOS :

void * sdl_library = dlopen ("/ Библиотека / Frameworks / SDL.framework / SDL ", RTLD_LAZY); if (sdl_library == NULL) {// сообщить об ошибке...} else {// использовать результат в вызове dlsym}

Или, если платформа или пакет содержит код Objective-C:

NSBundle * bundle = [NSBundle bundleWithPath: @ "/ Library / Plugins / Plugin.bundle"]; NSError * err = nil; if ([bundle loadAndReturnError: err]) {// Используем классы и функции в комплекте. } else {// Обрабатываем ошибку. }

Windows

HMODULE sdl_library = LoadLibrary (ТЕКСТ ("SDL.dll")); if (sdl_library == NULL) {// сообщить об ошибке...} else {// использовать результат в вызове GetProcAddress}

Извлечение содержимого библиотеки

Извлечение содержимого динамически загружаемой библиотеки - это достигается с помощью GetProcAddressв Windows и с dlsymв UNIX -подобных операционных системах.

UNIX-подобных операционных системах ( Solaris, Linux, * BSD, macOS и т. Д.)

void * initializer = dlsym (sdl_library, "SDL_Init"); if (initializer == NULL) {// сообщаем об ошибке...} else {// приводим инициализатор к его правильному типу и используем}

В macOS при использовании пакетов Objective-C также можно:

Класс rootClass = [основной класс пакета]; // В качестве альтернативы можно использовать NSClassFromString () для получения класса по имени. если (rootClass) {идентификатор объекта = [[rootClass alloc] инициализация]; // Используем объект. } else {// Сообщить об ошибке. }

Windows

инициализатор FARPROC = GetProcAddress (sdl_library, "SDL_Init"); if (initializer == NULL) {// сообщаем об ошибке...} else {// приводим инициализатор к его правильному типу и используем}

Преобразование указателя библиотечной функции

Результат dlsym ()или GetProcAddress ()необходимо преобразовать в указатель соответствующего типа, прежде чем его можно будет использовать.

Windows

В случае Windows преобразование несложно, поскольку FARPROC по сути уже является указателем на функцию:

typedef INT_PTR (* FARPROC) (void);

Это может быть проблематично, если нужно получить адрес объекта, а не функции. Однако обычно все равно нужно извлекать функции, так что обычно это не проблема.

typedef void (* sdl_init_function_type) (void); sdl_init_function_type init_func = (sdl_init_function_type) инициализатор;

UNIX (POSIX)

Согласно спецификации POSIX, результатом dlsym ()является указатель void. Однако не требуется, чтобы указатель функции имел даже тот же размер, что и указатель объекта данных, и поэтому допустимое преобразование между типом void *и указателем на функцию может быть нелегко реализовать на всех платформах..

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

typedef void (* sdl_init_function_type) (void); sdl_init_function_type init_func = (sdl_init_function_type) инициализатор;

Приведенный выше фрагмент выдаст предупреждение для некоторых компиляторов: предупреждение: разыменование указателя с перфорацией типа нарушит правила строгого алиасинга. Другой способ обхода:

typedef void (* sdl_init_function_type) (void); объединение {sdl_init_function_type func; void * obj; } псевдоним; alias.obj = инициализатор; sdl_init_function_type init_func = alias.func;

, который отключает предупреждение, даже если действует строгий псевдоним. При этом используется тот факт, что чтение из члена объединения, отличного от того, в который была записана последняя запись (так называемый «type punning »), является обычным и явно разрешено, даже если действует строгий псевдоним, при условии, что память доступен напрямую через тип union. Однако в данном случае это не совсем так, поскольку указатель функции копируется для использования вне объединения. Обратите внимание, что этот трюк может не работать на платформах, где размер указателей данных и размер указателей функций не совпадают.

Решение проблемы указателя на функцию в системах POSIX

Факт остается фактом: любое преобразование между указателями функций и объектов данных следует рассматривать как расширение реализации (изначально непереносимое), и что нет «Правильный» способ прямого преобразования существует, поскольку в этом отношении стандарты POSIX и ISO противоречат друг другу.

Из-за этой проблемы в документации POSIX по dlsym ()для устаревшей проблемы 6 указано, что «будущая версия может либо добавить новую функцию для возврата указателей функций, либо текущий интерфейс. может быть устаревшим в пользу двух новых функций: одна возвращает указатели данных, а другая - указатели функций ".

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

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

Выгрузка библиотеки

Загрузка библиотеки приводит к выделению памяти; библиотека должна быть освобождена, чтобы избежать утечки памяти. Кроме того, невозможность выгрузки библиотеки может помешать операциям файловой системы над файлом, который содержит библиотеку. Выгрузка библиотеки выполняется с помощью FreeLibraryв Windows и с помощью dlcloseв UNIX-подобных операционных системах. Однако выгрузка DLL может привести к сбою программы, если объекты в основном приложении ссылаются на память, выделенную в DLL. Например, если DLL представляет новый класс, а DLL закрывается, дальнейшие операции с экземплярами этого класса из основного приложения, скорее всего, вызовут нарушение доступа к памяти. Аналогичным образом, если DLL представляет фабричную функцию для создания экземпляров динамически загружаемых классов, вызов или разыменование этой функции после закрытия DLL приводит к неопределенному поведению.

UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и т. Д.)

dlclose (sdl_library);

Windows

FreeLibrary (sdl_library);

Специальная библиотека

Реализации динамической загрузки в UNIX-подобных операционных системах и Windows позволяют программистам извлекать символы из текущего выполняемого процесса.

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

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

UNIX-подобные операционные системы (Solaris, Linux, * BSD, macOS и т. Д.)

void * this_process = dlopen (NULL, 0);

Windows

HMODULE this_process = GetModuleHandle (NULL); HMODULE this_process_again; GetModuleHandleEx (0,0, this_process_again);
В Java

В языке программирования Java, классы могут быть динамически загружены с помощью объекта ClassLoader . Например:

Тип класса = ClassLoader.getSystemClassLoader (). LoadClass (имя); Объект obj = type.newInstance ();

Механизм отражения также предоставляет средства для загрузки класса, если он еще не загружен. Он использует загрузчик классов текущего класса:

Тип класса = Class.forName (name); Объект obj = type.newInstance ();

Однако нет простого способа выгрузить класс управляемым способом. Загруженные классы могут быть выгружены только контролируемым образом, то есть когда программист хочет, чтобы это произошло, если загрузчик классов, используемый для загрузки класса, не является загрузчиком системного класса и сам выгружается. При этом необходимо учитывать различные детали, чтобы гарантировать, что класс действительно выгружен. Это делает выгрузку классов утомительной.

Неявная выгрузка классов, то есть неконтролируемая сборщиком мусора, несколько раз менялась в Java. До Java 1.2. сборщик мусора мог выгружать класс всякий раз, когда считал, что ему нужно пространство, независимо от того, какой загрузчик классов использовался для загрузки класса. Начиная с Java 1.2, классы, загруженные через системный загрузчик классов, никогда не выгружались, а классы загружались через другие загрузчики классов только тогда, когда этот другой загрузчик классов был выгружен. Начиная с Java 6 классы могут содержать внутренний маркер, указывающий сборщику мусора, что они могут быть выгружены, если сборщик мусора пожелает это сделать, независимо от загрузчика классов, используемого для загрузки класса. Сборщик мусора может проигнорировать эту подсказку.

Аналогичным образом библиотеки, реализующие собственные методы, динамически загружаются с помощью метода System.loadLibrary. Не существует метода System.unloadLibrary.

Платформы без динамической загрузки

Несмотря на его распространение в 1980-х годах через UNIX и Windows, некоторые системы все же предпочли не добавлять или даже удалять динамическую загрузку. Например, Plan 9 от Bell Labs и его преемник 9front намеренно избегают динамического связывания, поскольку считают его «вредным». Язык программирования Go, созданный некоторыми из тех же разработчиков, что и Plan 9, также не поддерживает динамическое связывание, но загрузка плагинов доступна с Go 1.8 (февраль 2017 г.). Среда выполнения Go и любые библиотечные функции статически связаны в скомпилированный двоичный файл.

См. Также
  • значок Портал компьютерного программирования
Ссылки
Дополнительная литература
  • Зильбершатц, Авраам; Галвин, Питер Баер; Ганье, Грег (2005). «Глава 8.1.4« Динамическая загрузка »и Глава 8.1.5« Динамическое связывание и разделяемые библиотеки »». Понятия операционной системы. Дж. Вайли и сыновья. ISBN 978-0-471-69466-3.
Внешние ссылки
Последняя правка сделана 2021-05-18 07:27:21
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте