stringtranslate.com

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

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

История

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

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

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

Использование

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

В С/С++

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

Краткое содержание

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

Загрузка библиотеки осуществляется с помощью 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 ( "/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 = ноль ; if ([ Bundle loadAndReturnError :& err ]) { // Используем классы и функции из пакета. } else { // Обработка ошибки. }           

Окна

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

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

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

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

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

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

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

Окна

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

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

Результат 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 ) инициализатор ;      

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 ; пустота * объект ; } псевдоним ; псевдоним . объект = инициализатор ; sdl_init_function_type init_func = псевдоним . функция ;               

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

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

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

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

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

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

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

Загрузка библиотеки приводит к выделению памяти; библиотека должна быть освобождена во избежание утечки памяти . Кроме того, невозможность выгрузить библиотеку может помешать операциям файловой системы с файлом , содержащим библиотеку. Выгрузка библиотеки осуществляется в FreeLibraryWindows и UNIX dlclose-подобных операционных системах . Однако выгрузка 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объекта. Например:

Тип класса = ClassLoader . getSystemClassLoader (). Класс нагрузки ( имя ); Объект объект = тип . новыйэкземпляр ();      

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

Тип класса = Класс . forName ( имя ); Объект объект = тип . новыйэкземпляр ();      

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

Неявная выгрузка классов, т.е. неконтролируемая сборщиком мусора процедура, в 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]

Смотрите также

Рекомендации

  1. ^ ab Autoconf, Automake и Libtool: динамическая загрузка
  2. ^ «Linux4U: динамическая загрузка ELF» . Архивировано из оригинала 11 марта 2011 г. Проверено 31 декабря 2007 г.
  3. ^ «Использование процедур, поставляемых CICS, для установки прикладных программ» .
  4. ^ «Запрос IBM CEMT NEWCOPY или PHASEIN завершается с ошибкой NOT FOR HOLD PROG — США» . 15 марта 2013 г.
  5. ^ Хо, В. Уилсон; Олссон, Рональд А. (1991). «Подход к настоящему динамическому связыванию». Программное обеспечение: практика и опыт . 21 (4): 375–390. CiteSeerX 10.1.1.37.933 . дои : 10.1002/спе.4380210404. S2CID  9422227. 
  6. ^ «Поддержка динамических общих объектов (DSO) Apache 1.3» . Архивировано из оригинала 22 апреля 2011 г. Проверено 31 декабря 2007 г.
  7. ^ GCC 4.3.2 Параметры оптимизации: -fstrict-aliasing
  8. ^ документация POSIX по dlopen() (проблемы 6 и 7).
  9. ^ «Динамическое связывание». cat-v.org . 9перед . Проверено 22 декабря 2014 г.
  10. ^ "Часто задаваемые вопросы" .

дальнейшее чтение

Внешние ссылки