Динамическая загрузка
Динамическая загрузка — это механизм, с помощью которого компьютерная программа может во время выполнения загружать библиотеку (или другой двоичный файл ) в память, извлекать адреса функций и переменных, содержащихся в библиотеке, выполнять эти функции или получать доступ к этим переменным и выгружать библиотека по памяти. Это один из трех механизмов, с помощью которых компьютерная программа может использовать другое программное обеспечение внутри программы; остальные — статическое связывание и динамическое связывание . В отличие от статического и динамического связывания, динамическая загрузка позволяет компьютерной программе запускаться при отсутствии этих библиотек, обнаруживать доступные библиотеки и потенциально получать дополнительные функциональные возможности. [1] [2]
История
[ редактировать ]Динамическая загрузка была обычным методом для операционных систем IBM для System/360 , таких как OS/360 , особенно для ввода-вывода подпрограмм , а также для COBOL и PL/I библиотек времени выполнения , и продолжает использоваться в операционных системах IBM для z/Architecture. , например z/OS . Что касается прикладного программиста, загрузка в значительной степени прозрачна, поскольку она в основном обрабатывается операционной системой (или ее подсистемой ввода-вывода). Основные преимущества:
- Исправления ( патчи ) подсистем исправляют все программы сразу, без необходимости их перелинковки
- Библиотеки могут быть защищены от несанкционированной модификации
IBM Стратегическая обработки транзакций система , CICS (начиная с 1970-х годов), широко использует динамическую загрузку как для своего ядра , так и для обычной загрузки прикладных программ . Исправления в прикладных программах можно вносить в автономном режиме, а новые копии измененных программ загружаться динамически без необходимости перезапуска CICS. [3] [4] (который может работать и часто работает 24 часа в сутки, 7 дней в неделю ).
Общие библиотеки были добавлены в Unix в 1980-х годах, но изначально без возможности позволить программе загружать дополнительные библиотеки после запуска. [5]
Использование
[ редактировать ]Динамическая загрузка чаще всего используется при реализации программных плагинов . [1] Например, веб-сервер Apache *.dso
Файлы плагина «динамический общий объект» — это библиотеки , которые загружаются во время выполнения с помощью динамической загрузки. [6] Динамическая загрузка также используется при реализации компьютерных программ , где несколько различных библиотек могут предоставлять необходимые функциональные возможности и где пользователь имеет возможность выбрать, какую библиотеку или библиотеки предоставить.
В С/С++
[ редактировать ]Не все системы поддерживают динамическую загрузку. Unix-подобные операционные системы, такие как macOS , Linux и Solaris, обеспечивают динамическую загрузку с помощью библиотеки языка программирования C «dl». Операционная Windows система обеспечивает динамическую загрузку через Windows API .
Краткое содержание
[ редактировать ]Имя | Стандартный API POSIX/Unix | API Microsoft Windows |
---|---|---|
Включение заголовочного файла | #include <dlfcn.h>
|
#include <windows.h>
|
Определения заголовка | dl
( |
kernel32.dll
|
Загрузка библиотеки | dlopen
|
LoadLibrary LoadLibraryEx |
Извлечение содержимого | dlsym
|
GetProcAddress
|
Выгрузка библиотеки | dlclose
|
FreeLibrary
|
Загрузка библиотеки
[ редактировать ]Загрузка библиотеки осуществляется с помощью LoadLibrary
или LoadLibraryEx
в Windows и с dlopen
в Unix-подобных операционных системах . Ниже приведены примеры:
Большинство Unix-подобных операционных систем (Solaris, Linux, *BSD и т. д.)
[ редактировать ]void* sdl_library = dlopen("libSDL.so", RTLD_LAZY);
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to dlsym
}
macOS
[ редактировать ]В качестве библиотеки Unix :
void* sdl_library = dlopen("libSDL.dylib", RTLD_LAZY);
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to dlsym
}
В качестве платформы macOS :
void* sdl_library = dlopen("/Library/Frameworks/SDL.framework/SDL", RTLD_LAZY);
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to dlsym
}
Или, если фреймворк или пакет содержит код Objective-C:
NSBundle *bundle = [NSBundle bundleWithPath:@"/Library/Plugins/Plugin.bundle"];
NSError *err = nil;
if ([bundle loadAndReturnError:&err])
{
// Use the classes and functions in the bundle.
}
else
{
// Handle error.
}
Окна
[ редактировать ]HMODULE sdl_library = LoadLibrary(TEXT("SDL.dll"));
if (sdl_library == NULL) {
// report error ...
} else {
// use the result in a call to GetProcAddress
}
Извлечение содержимого библиотеки
[ редактировать ]Извлечение содержимого динамически загружаемой библиотеки достигается с помощью GetProcAddress
в Windows и с dlsym
в Unix -подобных операционных системах .
Unix-подобные операционные системы (Solaris, Linux, *BSD, macOS и т. д.)
[ редактировать ]void* initializer = dlsym(sdl_library, "SDL_Init");
if (initializer == NULL) {
// report error ...
} else {
// cast initializer to its proper type and use
}
В macOS при использовании пакетов Objective-C также можно:
Class rootClass = [bundle principalClass]; // Alternatively, NSClassFromString() can be used to obtain a class by name.
if (rootClass)
{
id object = [[rootClass alloc] init]; // Use the object.
}
else
{
// Report error.
}
Окна
[ редактировать ]FARPROC initializer = GetProcAddress(sdl_library,"SDL_Init");
if (initializer == NULL) {
// report error ...
} else {
// cast initializer to its proper type and use
}
Преобразование указателя на библиотечную функцию
[ редактировать ]Результат dlsym()
или GetProcAddress()
должен быть преобразован в указатель соответствующего типа, прежде чем его можно будет использовать.
Окна
[ редактировать ]В Windows преобразование простое, поскольку FARPROC по сути уже является указателем на функцию :
typedef INT_PTR (*FARPROC)(void);
Это может быть проблематично, если необходимо получить адрес объекта, а не функции. Однако обычно все равно требуется извлечь функции, так что обычно это не проблема.
typedef void (*sdl_init_function_type)(void);
sdl_init_function_type init_func = (sdl_init_function_type) initializer;
Юникс (POSIX)
[ редактировать ]Согласно спецификации POSIX, результат dlsym()
это void
указатель. Однако указатель функции не обязательно должен иметь тот же размер, что и указатель объекта данных, и, следовательно, допустимое преобразование между типами void*
а указатель на функцию может оказаться непросто реализовать на всех платформах.
В большинстве используемых сегодня систем указатели на функции и объекты де-факто конвертируемы. Следующий фрагмент кода демонстрирует один обходной путь, который позволяет в любом случае выполнить преобразование во многих системах:
typedef void (*sdl_init_function_type)(void);
sdl_init_function_type init_func = (sdl_init_function_type)initializer;
Приведенный выше фрагмент выдает предупреждение о некоторых компиляторах: warning: dereferencing type-punned pointer will break strict-aliasing rules
. Еще один обходной путь:
typedef void (*sdl_init_function_type)(void);
union { sdl_init_function_type func; void * obj; } alias;
alias.obj = initializer;
sdl_init_function_type init_func = alias.func;
который отключает предупреждение, даже если действует строгое псевдонимирование. При этом используется тот факт, что чтение из члена объединения, отличного от того, в который была записана последняя запись (так называемое « каламбур типов »), является обычным явлением и явно разрешено, даже если действует строгое псевдонимирование, при условии, что доступ к памяти осуществляется через тип объединения. напрямую. [7] Однако в данном случае это не совсем так, поскольку указатель на функцию копируется для использования вне объединения. Обратите внимание, что этот трюк может не работать на платформах, где размер указателей данных и размер указателей функций не совпадают.
Решение проблемы указателя функции в системах POSIX
[ редактировать ]Факт остается фактом: любое преобразование между указателями на функцию и объект данных следует рассматривать как (по своей сути непереносимое) расширение реализации, и что не существует «правильного» способа прямого преобразования, поскольку в этом отношении стандарты POSIX и ISO противоречат друг другу. друг друга.
Из-за этой проблемы документация POSIX по dlsym()
в устаревшем выпуске 6 говорилось, что «в будущей версии может быть добавлена новая функция для возврата указателей на функции, либо текущий интерфейс может быть признан устаревшим в пользу двух новых функций: одна, которая возвращает указатели на данные, и другая, которая возвращает указатели на функции». [8]
В последующей версии стандарта (выпуск 7, 2008 г.) проблема обсуждалась, и был сделан вывод, что указатели функций должны быть конвертированы в void*
для соответствия POSIX. [8] Это требует от производителей компиляторов реализовать рабочий состав для этого случая.
Если содержимое библиотеки можно изменить (т.е. в случае пользовательской библиотеки), то помимо самой функции можно экспортировать указатель на нее. Поскольку указатель на указатель функции сам по себе является указателем объекта, этот указатель всегда можно законно получить, вызвав dlsym()
и последующее преобразование. Однако этот подход требует поддержки отдельных указателей на все функции, которые будут использоваться извне, а преимущества обычно невелики.
Выгрузка библиотеки
[ редактировать ]Загрузка библиотеки приводит к выделению памяти; библиотека должна быть освобождена во избежание утечки памяти . Кроме того, невозможность выгрузить библиотеку может помешать файловой системы операциям с файлом , содержащим библиотеку. Выгрузка библиотеки осуществляется с помощью FreeLibrary
в Windows и с dlclose
в Unix-подобных операционных системах . Однако выгрузка DLL может привести к сбою программы, если объекты в основном приложении ссылаются на память, выделенную внутри DLL. Например, если DLL вводит новый класс и DLL закрывается, дальнейшие операции с экземплярами этого класса из основного приложения, скорее всего, приведут к нарушению доступа к памяти. Аналогично, если DLL вводит фабричную функцию для создания экземпляров динамически загружаемых классов, вызов или разыменование этой функции после закрытия DLL приводит к неопределенному поведению.
Unix-подобные операционные системы (Solaris, Linux, *BSD, macOS и т. д.)
[ редактировать ]dlclose(sdl_library);
Окна
[ редактировать ]FreeLibrary(sdl_library);
Специальная библиотека
[ редактировать ]Реализации динамической загрузки в Unix-подобных операционных системах и Windows позволяют программистам извлекать символы из текущего процесса.
Unix-подобные операционные системы позволяют программистам получать доступ к глобальной таблице символов, которая включает в себя как основной исполняемый файл, так и загружаемые впоследствии динамические библиотеки.
Windows позволяет программистам получать доступ к символам, экспортированным основным исполняемым файлом. Windows не использует глобальную таблицу символов и не имеет API для поиска символа по имени в нескольких модулях.
Unix-подобные операционные системы (Solaris, Linux, *BSD, macOS и т. д.)
[ редактировать ]void* this_process = dlopen(NULL,0);
Окна
[ редактировать ]HMODULE this_process = GetModuleHandle(NULL);
HMODULE this_process_again;
GetModuleHandleEx(0,0,&this_process_again);
На Яве
[ редактировать ]В языке программирования Java классы можно загружать динамически с помощью ClassLoader
объект. Например:
Class type = ClassLoader.getSystemClassLoader().loadClass(name);
Object obj = type.newInstance();
Механизм Reflection также предоставляет средства для загрузки класса, если он еще не загружен. Он использует загрузчик классов текущего класса:
Class type = Class.forName(name);
Object obj = type.newInstance();
Однако не существует простого способа контролируемой выгрузки класса. Загруженные классы могут быть выгружены только контролируемым способом, т.е. когда программист хочет, чтобы это произошло, если загрузчик классов, используемый для загрузки класса, не является загрузчиком системных классов и сам выгружается. При этом необходимо учитывать различные детали, чтобы гарантировать, что класс действительно разгружен. Это делает выгрузку классов утомительной.
Неявная выгрузка классов, т.е. неконтролируемая сборщиком мусора процедура, в Java менялась несколько раз. До Java 1.2. сборщик мусора мог выгрузить класс всякий раз, когда чувствовал, что ему нужно пространство, независимо от того, какой загрузчик классов использовался для загрузки класса. Начиная с Java 1.2 классы, загруженные через системный загрузчик классов, никогда не выгружались, а классы загружались через другие загрузчики классов только тогда, когда этот другой загрузчик классов был выгружен. Начиная с Java 6, классы могут содержать внутренний маркер, указывающий сборщику мусора, что они могут быть выгружены, если сборщик мусора желает это сделать, независимо от загрузчика классов, используемого для загрузки класса. Сборщик мусора может игнорировать эту подсказку.
Аналогично, библиотеки, реализующие собственные методы, загружаются динамически с помощью System.loadLibrary
метод. Нет System.unloadLibrary
метод.
Платформы без динамической нагрузки
[ редактировать ]Несмотря на его распространение в 1980-х годах в Unix и Windows, некоторые системы по-прежнему предпочитали не добавлять динамическую загрузку или даже удалять ее. Например, Plan 9 от Bell Labs и его преемник 9front намеренно избегают динамического связывания, поскольку считают его «вредным». [9] Язык программирования Go , созданный некоторыми из тех же разработчиков, что и Plan 9, также не поддерживал динамическое связывание, но загрузка плагинов доступна начиная с Go 1.8 (февраль 2017 г.). Среда выполнения Go и все библиотечные функции статически скомпонованы в скомпилированный двоичный файл. [10]
См. также
[ редактировать ]- Скомпилируйте и запустите систему
- DLL Ад
- Прямая привязка
- Динамическое связывание (вычисления)
- Динамическая отправка
- Динамическая библиотека
- Динамический компоновщик
- Динамическая библиотека
- ФлексОС
- Компоновщик GNU
- золото (линкер)
- Ленивая загрузка
- Библиотека (вычисления)
- Линкер (вычисления)
- Загрузчик (вычислительный)
- Украшение имени
- Предварительное связывание
- Предварительное связывание
- Переезд (информатика)
- Таблица перемещения
- Резидентное расширение системы (RSX)
- Статическая библиотека
- Программа прекращения пребывания и пребывания (TSR)
Ссылки
[ редактировать ]- ^ Jump up to: а б Autoconf, Automake и Libtool: динамическая загрузка
- ^ «Linux4U: динамическая загрузка ELF» . Архивировано из оригинала 11 марта 2011 г. Проверено 31 декабря 2007 г.
- ^ «Использование процедур, поставляемых CICS, для установки прикладных программ» .
- ^ «Запрос IBM CEMT NEWCOPY или PHASEIN завершается с ошибкой NOT FOR HOLD PROG — Соединенные Штаты» . 15 марта 2013 г.
- ^ Хо, В. Уилсон; Олссон, Рональд А. (1991). «Подход к настоящему динамическому связыванию». Программное обеспечение: практика и опыт . 21 (4): 375–390. CiteSeerX 10.1.1.37.933 . дои : 10.1002/спе.4380210404 . S2CID 9422227 .
- ^ «Поддержка динамических общих объектов (DSO) Apache 1.3» . Архивировано из оригинала 22 апреля 2011 г. Проверено 31 декабря 2007 г.
- ^ GCC 4.3.2 Параметры оптимизации: -fstrict-aliasing
- ^ Jump up to: а б POSIX-документация по
dlopen()
(выпуски 6 и 7). - ^ «Динамическое соединение» . cat-v.org . 9перед . Проверено 22 декабря 2014 г.
- ^ «Перейти к часто задаваемым вопросам» .
Дальнейшее чтение
[ редактировать ]- Зильбершац, Авраам; Гэлвин, Питер Баер; Ганье, Грег (2005). «Глава 8.1.4 «Динамическая загрузка» и Глава 8.1.5 «Динамическое связывание и общие библиотеки» ». Концепции операционной системы . Дж. Уайли и сыновья . ISBN 978-0-471-69466-3 .
Внешние ссылки
[ редактировать ]- Общие ссылки
- Динамическая загрузка в Linux4U
- Поддержка динамических общих объектов (DSO Apache )
- Динамическое связывание C++ на примере
- Пример загрузки динамической библиотеки (полный, но краткий рабочий пример)
- Темы по программированию динамических библиотек от Apple Developer Connection (ориентированы на macOS)
- Unix-API C/C++:
- C/С++ Windows API:
- Java API: