Из Википедии, бесплатной энциклопедии
  (Перенаправлено с Dlopen )
Перейти к навигации Перейти к поиску

Динамическая загрузка - это механизм, с помощью которого компьютерная программа может во время выполнения загружать библиотеку (или другой двоичный файл ) в память, извлекать адреса функций и переменных, содержащихся в библиотеке, выполнять эти функции или обращаться к этим переменным и выгружать библиотека по памяти. Это один из трех механизмов, с помощью которых компьютерная программа может использовать какое-либо другое программное обеспечение; два других - статическая и динамическая . В отличие от статического связывания и динамического связывания, динамическая загрузка позволяет компьютерной программедля запуска в отсутствие этих библиотек, чтобы обнаружить доступные библиотеки и потенциально получить дополнительную функциональность. [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 *.dso представляют собой библиотеки, которые загружаются во время выполнения с динамической загрузкой. [6] Динамическая загрузка также используется при реализации компьютерных программ, в которых несколько разных библиотек могут обеспечивать необходимую функциональность и где пользователь имеет возможность выбрать, какую библиотеку или библиотеки предоставить.

На C / C ++ [ править ]

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

Резюме [ править ]

Загрузка библиотеки [ править ]

Загрузка библиотеки выполняется в 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 {  // Обрабатываем ошибку. }

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 ) ( недействительно );

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

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 );

Windows [ править ]

FreeLibrary ( sdl_library );

Специальная библиотека [ править ]

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

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

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

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

недействительный *  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  =  тип . 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
  • Библиотека (вычислительная)
  • Компоновщик (вычисления)
  • Загрузчик (вычисления)
  • Украшение имени
  • Предварительная привязка
  • Предварительная ссылка
  • Переезд (информатика)
  • Таблица переезда
  • Статическая библиотека
  • золото (линкер)
  • предварительная ссылка

Ссылки [ править ]

  1. ^ a b Autoconf, Automake и Libtool: динамическая загрузка
  2. ^ "Linux4U: Динамическая загрузка ELF" . Архивировано из оригинала на 2011-03-11 . Проверено 31 декабря 2007 .
  3. ^ «Использование процедур, предоставленных CICS, для установки прикладных программ» .
  4. ^ "Запрос IBM CEMT NEWCOPY или PHASEIN завершился неудачно с НЕ ДЛЯ УДЕРЖИВАНИЯ ПРОГ - США" . 2013-03-15.
  5. ^ Хо, У. Уилсон; Олссон, Рональд А. (1991). «Подход к подлинному динамическому связыванию». Программное обеспечение - практика и опыт . 21 (4): 375–390. CiteSeerX 10.1.1.37.933 . DOI : 10.1002 / spe.4380210404 . 
  6. ^ Поддержка Apache 1.3 Dynamic Shared Object (DSO)
  7. ^ GCC 4.3.2 Параметры оптимизации: -fstrict-aliasing
  8. ^ a b Документация по POSIXdlopen() (выпуски 6 и 7).
  9. ^ «Динамическое связывание» . cat-v.org . 9фронт . Проверено 22 декабря 2014 .
  10. ^ "Перейти 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
    • Класс