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

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

История

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

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

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

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

Использует

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

В C / C ++

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

Резюме

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

(libdl.so, libdl.dylibи т. д. в зависимости от Операционные системы )

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

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

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

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

пустота* sdl_library = dlopen("libSDL.so", RTLD_LAZY);если (sdl_library == НОЛЬ) {   // сообщаем об ошибке ...} еще {   // использовать результат при вызове dlsym}

macOS

Как UNIX библиотека:

пустота* sdl_library = dlopen("libsdl.dylib", RTLD_LAZY);если (sdl_library == НОЛЬ) {   // сообщаем об ошибке ...} еще {   // используем результат при вызове dlsym}

Как MacOS Framework:

пустота* sdl_library = dlopen("/Library/Frameworks/SDL.framework/SDL", RTLD_LAZY);если (sdl_library == НОЛЬ) {   // сообщаем об ошибке ...} еще {   // использовать результат при вызове dlsym}

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

NSBundle *пучок = [NSBundle bundleWithPath:@ "/ Библиотека / Плагины / Plugin.bundle"];NSError *ошибаться = ноль;если ([пучок loadAndReturnError:&ошибаться]){    // Используйте классы и функции в комплекте.}еще{    // Обработка ошибки.}

Windows

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

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

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

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

пустота* инициализатор = dlsym(sdl_library,"SDL_Init");если (инициализатор == НОЛЬ) {   // сообщаем об ошибке ...} еще {   // привести инициализатор к правильному типу и использовать}

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

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

Windows

ФАРПРОК инициализатор = GetProcAddress(sdl_library,"SDL_Init");если (инициализатор == НОЛЬ) {   // сообщаем об ошибке ...} еще {   // привести инициализатор к правильному типу и использовать}

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

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

Windows

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

typedef INT_PTR (*ФАРПРОК)(пустота);

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

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

UNIX (POSIX)

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

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

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

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

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

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

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

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

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

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

пустота* этот процесс = dlopen(НОЛЬ,0);

Windows

HMODULE этот процесс = GetModuleHandle(НОЛЬ);HMODULE this_process_again;GetModuleHandleEx(0,0,&this_process_again);

В Java

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

Учебный класс тип = ClassLoader.getSystemClassLoader().loadClass(имя);Объект объект = тип.newInstance();

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

Учебный класс тип = Учебный класс.forName(имя);Объект объект = тип.newInstance();

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

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

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

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

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

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

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

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

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

  • Зильбершац, Авраам; Галвин, Питер Баер; Ганье, Грег (2005). "Глава 8.1.4" Динамическая загрузка "и Глава 8.1.5" Динамическое связывание и общие библиотеки"". Понятия операционной системы. J. Wiley & Sons. ISBN  978-0-471-69466-3.

внешняя ссылка