Динамическая загрузка - это механизм, с помощью которого компьютерная программа может во время выполнения загружать библиотеку (или другой двоичный файл ) в память, извлекать адреса функций и переменных, содержащихся в библиотеке, выполнять эти функции или обращаться к этим переменным и выгружать библиотека по памяти. Это один из трех механизмов, с помощью которых компьютерная программа может использовать какое-либо другое программное обеспечение; два других - статическая и динамическая . В отличие от статического связывания и динамического связывания, динамическая загрузка позволяет компьютерной программедля запуска в отсутствие этих библиотек, чтобы обнаружить доступные библиотеки и потенциально получить дополнительную функциональность. [1] [2]
История
Динамическая загрузка была обычным методом для операционных систем IBM для System / 360, таких как OS / 360 , особенно для подпрограмм ввода-вывода , а также для библиотек времени выполнения COBOL и PL / I , и продолжает использоваться в операционных системах IBM для z / Architecture. , например z / OS . Что касается прикладного программиста, загрузка в значительной степени прозрачна, поскольку она в основном обрабатывается операционной системой (или ее подсистемой ввода-вывода). Основные преимущества:
- Исправления ( патчи ) подсистем фиксировали сразу все программы, без необходимости повторно связывать их
- Библиотеки могут быть защищены от несанкционированного изменения
Система обработки стратегических транзакций IBM , CICS (1970-е гг.), Широко использует динамическую загрузку как для ядра, так и для обычной загрузки прикладных программ . Исправления в прикладных программах можно было вносить в автономном режиме, а новые копии измененных программ загружались динамически без необходимости перезапуска CICS [3] [4] (который может и часто работает круглосуточно ).
Общие библиотеки были добавлены в Unix в 1980-х годах, но изначально не позволяли программе загружать дополнительные библиотеки после запуска. [5]
Использует
Динамическая загрузка наиболее часто используется при реализации программных плагинов . [1] Например, файлы подключаемых модулей «динамических общих объектов» веб-сервера Apache *.dso
представляют собой библиотеки, которые загружаются во время выполнения с динамической загрузкой. [6] Динамическая загрузка также используется при реализации компьютерных программ, в которых несколько разных библиотек могут обеспечивать необходимую функциональность и где пользователь имеет возможность выбрать, какую библиотеку или библиотеки предоставить.
В C / C ++
Не все системы поддерживают динамическую загрузку. UNIX-подобные операционные системы, такие как macOS , Linux и Solaris, обеспечивают динамическую загрузку с помощью библиотеки языка программирования C «dl». Для Windows операционная система обеспечивает динамическую загрузку через Windows API , .
Резюме
Имя | Стандартный POSIX / UNIX API | Microsoft Windows API |
---|---|---|
Включение файла заголовка | #include | #include |
Определения для заголовка | dl ( | kernel32.dll |
Загрузка библиотеки | dlopen | LoadLibrary LoadLibraryEx |
Извлечение содержимого | dlsym | GetProcAddress |
Выгрузка библиотеки | dlclose | FreeLibrary |
Загрузка библиотеки
Загрузка библиотеки выполняется в WindowsLoadLibrary
или LoadLibraryEx
в Windows, а также dlopen
в UNIX-подобных операционных системах . Ниже приведены примеры:
Большинство UNIX-подобных операционных систем (Solaris, Linux, * BSD и т. Д.)
недействительными * sdl_library = dlopen ( "libSDL.so" , RTLD_LAZY ); if ( sdl_library == NULL ) { // сообщить об ошибке ... } else { // использовать результат при вызове dlsym }
macOS
В качестве библиотеки UNIX :
недействительными * sdl_library = dlopen ( "libSDL.dylib" , RTLD_LAZY ); if ( sdl_library == NULL ) { // сообщить об ошибке ... } else { // использовать результат при вызове dlsym }
В качестве фреймворка macOS :
недействительными * sdl_library = dlopen ( "/Library/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 { // Обрабатываем ошибку. }
Окна
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 { // Сообщить об ошибке. }
Окна
Инициализатор FARPROC = GetProcAddress ( sdl_library , "SDL_Init" ); if ( initializer == NULL ) { // сообщаем об ошибке ... } else { // приводим инициализатор к его правильному типу и используем }
Преобразование указателя библиотечной функции
Результат dlsym()
или GetProcAddress()
должен быть преобразован в указатель соответствующего типа, прежде чем его можно будет использовать.
Окна
В случае Windows преобразование несложно, поскольку FARPROC по сути уже является указателем на функцию:
typedef INT_PTR ( * FARPROC ) ( недействительно );
Это может быть проблематично, если нужно получить адрес объекта, а не функции. Однако обычно все равно нужно извлекать функции, так что обычно это не проблема.
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 ) инициализатор ;
Выше фрагмент кода будет выдавать предупреждение о некоторых компиляторах: warning: dereferencing type-punned pointer will break strict-aliasing rules
. Другой обходной путь:
typedef void ( * sdl_init_function_type ) ( void ); объединение { sdl_init_function_type func ; void * obj ; } псевдоним ; псевдоним . obj = инициализатор ; sdl_init_function_type init_func = псевдоним . 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 и т. Д.)
недействительный * this_process = dlopen ( NULL , 0 );
Окна
HMODULE this_process = GetModuleHandle ( NULL );HMODULE this_process_again ; GetModuleHandleEx ( 0 , 0 , & this_process_again );
В Java
В языке программирования Java , классы могут быть загружены динамически с помощью ClassLoader
объекта. Например:
Тип класса = ClassLoader . getSystemClassLoader (). loadClass ( имя ); Объект obj = тип . newInstance ();
Механизм отражения также предоставляет средства для загрузки класса, если он еще не загружен. Он использует загрузчик классов текущего класса:
Класс Тип = Класс . forName ( имя ); Объект obj = тип . 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 ад
- Прямая привязка
- Динамическое связывание (вычисления)
- Динамическая отправка
- Динамическая библиотека
- Динамический компоновщик
- Библиотека с динамической компоновкой
- FlexOS
- Компоновщик GNU
- Библиотека (вычислительная)
- Компоновщик (вычисления)
- Загрузчик (вычисления)
- Украшение имени
- Предварительная привязка
- Предварительная ссылка
- Переезд (информатика)
- Таблица переезда
- Статическая библиотека
- золото (линкер)
- предварительная ссылка
Рекомендации
- ^ a b Autoconf, Automake и Libtool: динамическая загрузка
- ^ "Linux4U: Динамическая загрузка ELF" . Архивировано из оригинала на 2011-03-11 . Проверено 31 декабря 2007 .
- ^ «Использование процедур, поставляемых CICS, для установки прикладных программ» .
- ^ «Запрос IBM СЕМТ NEWCOPY или PHASEIN терпит неудачу с НЕ ДЕРЖАТЬ PROG - Соединенные Штаты» . 2013-03-15.
- ^ Хо, У. Уилсон; Олссон, Рональд А. (1991). «Подход к подлинному динамическому связыванию». Программное обеспечение - практика и опыт . 21 (4): 375–390. CiteSeerX 10.1.1.37.933 . DOI : 10.1002 / spe.4380210404 .
- ^ Поддержка Apache 1.3 Dynamic Shared Object (DSO)
- ^ GCC 4.3.2 Параметры оптимизации: -fstrict-aliasing
- ^ a b Документация по POSIXdlopen() (выпуски 6 и 7).
- ^ «Динамическое связывание» . cat-v.org . 9фронт . Проверено 22 декабря 2014 .
- ^ «Перейти FAQ» .
дальнейшее чтение
- Зильбершац, Авраам; Гэлвин, Питер Баер; Гань, Грег (2005). «Глава 8.1.4« Динамическая загрузка »и Глава 8.1.5« Динамическое связывание и разделяемые библиотеки » ». Понятия операционной системы . J. Wiley & Sons . ISBN 978-0-471-69466-3.
Внешние ссылки
- Общие ссылки
- Динамическая загрузка в Linux4U
- Поддержка динамического общего объекта (DSO) в Apache
- Динамическое связывание C ++ на примере
- Пример загрузки динамической библиотеки (полный, но краткий рабочий пример)
- Темы программирования динамических библиотек из Apple Developer Connection (для macOS)
- C / C ++ UNIX API:
- dlopen
- dlsym
- dlclose
- C / C ++ Windows API:
- LoadLibrary
- GetProcAddress
- FreeLibrary
- DLL с отложенной загрузкой
- Java API:
- ClassLoader
- Класс