Библиотека с динамической компоновкой

редактировать
Библиотека динамической компоновки
Dll png.png
Расширение имени файла .dll
Тип интернет-носителя application / vnd.microsoft.portable-executable
Uniform Type Identifier (UTI) com.microsoft.windows-dynamic-link-library
Magic number MZ
РазработаноMicrosoft
Контейнер для Общая библиотека

Библиотека с динамической компоновкой (DLL ) - это реализация Microsoft концепции общей библиотеки в Microsoft Операционные системы Windows и OS/2 . Эти библиотеки обычно имеют расширение файла DLL, OCX(для библиотек, содержащих элементы управления ActiveX ) или DRV(для устаревших системных драйверов ). Форматы файлов для DLL такие же, как и для файлов Windows EXE, то есть Portable Executable (PE) для 32-бит и 64- бит Windows и Новый исполняемый файл (NE) для 16-битной Windows. Как и в случае с EXE, библиотеки DLL могут содержать код, данные и ресурсы в любой комбинации.

Файлы данных с тем же форматом файла , что и DLL, но с разными расширениями файлов и, возможно, содержащими только разделы ресурсов, могут называться ресурсными DLL. Примеры таких DLL включают библиотеки icon, иногда с расширением ICL, и файлы font с расширениями FONи FOT.

Содержание

  • 1 Фон
  • 2 Функции
    • 2.1 Управление памятью
    • 2.2 Импорт библиотек
    • 2.3 Разрешение и привязка символов
    • 2.4 Явное связывание во время выполнения
    • 2.5 Отложенная загрузка
  • 3 Особенности компилятора и языка
    • 3.1 Delphi
    • 3.2 Microsoft Visual Basic
    • 3.3 C и C ++
  • 4 Примеры программирования
    • 4.1 Использование импорта DLL
    • 4.2 Использование явного связывания во время выполнения
      • 4.2.1 Microsoft Visual Basic
      • 4.2.2 Delphi
      • 4.2.3 C
      • 4.2.4 Python
  • 5 Компонентная объектная модель
  • 6 Перехват DLL
  • 7 См. Также
  • 8 Ссылки
  • 9 Внешние ссылки

Предпосылки

В первых версиях Microsoft Windows программы запускались вместе в одном адресном пространстве. Каждая программа должна была взаимодействовать, передавая центральный процессор другим программам, так что графический интерфейс пользователя (GUI) мог работать в многозадачном режиме и быть максимально отзывчивым. Все операции на уровне операционной системы обеспечивались базовой операционной системой: MS-DOS. Все услуги более высокого уровня были предоставлены библиотеками Windows «Библиотека динамической компоновки». Drawing API, Интерфейс графического устройства (GDI) был реализован в DLL с именем GDI.EXE, пользовательский интерфейс - в USER.EXE. Эти дополнительные уровни поверх DOS должны были быть общими для всех запущенных программ Windows, не только для того, чтобы Windows могла работать на машине с объемом оперативной памяти менее мегабайта, но и для того, чтобы программы могли взаимодействовать друг с другом. Код в GDI должен был переводить команды рисования в операции на определенных устройствах. На дисплее ему приходилось манипулировать пикселями в буфере кадра. При рисовании на принтере вызовы API должны были быть преобразованы в запросы к принтеру. Хотя можно было бы обеспечить жестко запрограммированную поддержку для ограниченного набора устройств (например, дисплея Color Graphics Adapter, языка команд принтера HP LaserJet ), Microsoft выбрала другой подход. GDI будет работать, загружая разные фрагменты кода, называемые «драйверы устройств », для работы с разными устройствами вывода.

Та же самая архитектурная концепция, которая позволяла GDI загружать различные драйверы устройств, - это концепция, которая позволяла оболочке Windows загружать различные программы Windows, а также для этих программ вызывать вызовы API из общего ПОЛЬЗОВАТЕЛЯ и Библиотеки GDI. Это понятие было «динамическое связывание».

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

Для тех ранних версий Windows (от 1.0 до 3.11) библиотеки DLL были основой всего графического интерфейса. Таким образом, драйверы дисплея представляли собой просто библиотеки DLL с расширением.DRV, которые предоставляли пользовательские реализации одного и того же API рисования через унифицированный интерфейс драйвера устройства (DDI) и интерфейсы API рисования (GDI) и GUI (USER). были просто вызовами функций, экспортированными GDI и USER, системными библиотеками DLL с расширением.EXE.

Идея создания операционной системы из набора динамически загружаемых библиотек является основной концепцией Windows, сохраняющейся с 2015 года. Библиотеки DLL предоставляют стандартные преимущества разделяемых библиотек, такие как модульность. Модульность позволяет вносить изменения в код и данные в одной автономной DLL, совместно используемой несколькими приложениями, без каких-либо изменений в самих приложениях.

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

. В Windows 1.x, 2.x и 3.x все приложения Windows совместно используют то же адресное пространство и та же память. DLL загружалась в это адресное пространство только один раз; с этого момента к нему обращались все программы, использующие библиотеку. Данные библиотеки были общими для всех программ. Это могло использоваться как косвенная форма межпроцессного взаимодействия, или это могло случайно повредить различные программы. С введением 32-битных библиотек в Windows 95 каждый процесс выполнялся в собственном адресном пространстве. Хотя код DLL может быть общим, данные являются частными, за исключением случаев, когда общие данные явно запрашиваются библиотекой. При этом большая часть Windows 95, Windows 98 и Windows Me была создана из 16-битных библиотек, что ограничивало производительность Pentium. Pro микропроцессор при запуске и в конечном итоге ограничивал стабильность и масштабируемость версий Windows на базе DOS.

Хотя библиотеки DLL являются ядром архитектуры Windows, у них есть несколько недостатков, которые в совокупности называются «ад DLL ». С 2015 года Microsoft продвигает .NET Framework как одно из решений проблем адского DLL, хотя теперь они продвигают решения на основе виртуализации, такие как Microsoft Virtual PC и Microsoft Application Virtualization., потому что они обеспечивают превосходную изоляцию между приложениями. Альтернативным решением для смягчения последствий ада DLL была реализация параллельной сборки.

Возможности

Поскольку библиотеки DLL по сути такие же, как EXE, выбор которых создается как часть связывания процесс предназначен для ясности, так как из них можно экспортировать функции и данные.

Невозможно напрямую выполнить DLL, так как для загрузки операционной системы через точку входа требуется EXE, отсюда и существование таких утилит, как RUNDLL.EXE или RUNDLL32.EXE, которые предоставляют запись точка и минимальная структура для библиотек DLL, которые содержат достаточно функций для выполнения без особой поддержки.

DLL предоставляют механизм для совместно используемого кода и данных, позволяя разработчику совместно используемого кода / данных обновлять функциональные возможности без необходимости повторного связывания или повторной компиляции приложений. С точки зрения разработки приложений Windows и OS / 2 можно рассматривать как набор обновляемых DLL, позволяющих приложениям для одной версии ОС работать в более поздней версии, при условии, что поставщик ОС гарантирует, что интерфейсы и функциональность совместимы.

DLL выполняются в области памяти вызывающего процесса и с теми же правами доступа, что означает небольшие накладные расходы при их использовании, но также отсутствие защиты для вызывающего EXE, если в DLL есть какая-либо ошибка.

Управление памятью

В Windows API файлы DLL организованы в разделы. Каждый раздел имеет свой собственный набор атрибутов, таких как доступный для записи или только для чтения, исполняемый (для кода) или неисполняемый (для данных) и т. Д.

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

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

Если DLL сжимается некоторыми исполняемыми упаковщиками (например, UPX ), все разделы ее кода помечаются как чтение и запись, и общий доступ к ним будет отключен. Разделы кода для чтения и записи, как и разделы частных данных, являются частными для каждого процесса. Таким образом, библиотеки DLL с разделами общих данных не должны сжиматься, если они предназначены для одновременного использования несколькими программами, поскольку каждый экземпляр программы должен нести свою собственную копию DLL, что приведет к увеличению потребления памяти.

Библиотеки импорта

Подобно статическим библиотекам, библиотеки импорта для DLL отмечаются расширением файла.lib. Например, kernel32.dll, основная динамическая библиотека для основных функций Windows, таких как создание файлов и управление памятью, связана через kernel32.lib. Обычный способ отличить библиотеку импорта от правильной статической библиотеки - по размеру: библиотека импорта намного меньше, так как она содержит только символы, относящиеся к реальной DLL, которые должны обрабатываться во время компоновки. Тем не менее, оба являются файлами формата Unix ar.

Связывание с динамическими библиотеками обычно осуществляется путем связывания с библиотекой импорта при построении или связывании для создания исполняемого файла. Созданный исполняемый файл затем содержит таблицу адресов импорта (IAT), с помощью которой ссылаются на все вызовы функций DLL (каждая упомянутая функция DLL содержит свою собственную запись в IAT). Во время выполнения IAT заполняется соответствующими адресами, которые указывают непосредственно на функцию в отдельно загруженной DLL.

В Cygwin / MSYS и MinGW библиотекам импорта обычно дается суффикс .dll. a, сочетающий в себе суффикс Windows DLL и суффикс ar Unix. Формат файла похож, но символы, используемые для обозначения импорта, отличаются (_head_foo_dll vs __IMPORT_DESCRIPTOR_foo). Хотя его цепочка инструментов GNU Binutils может генерировать библиотеки импорта и связываться с ними, связываться с DLL напрямую быстрее. Экспериментальный инструмент в MinGW под названием genlib может использоваться для создания библиотек импорта с символами в стиле MSVC.

Разрешение и привязка символов

Каждая функция, экспортируемая DLL, идентифицируется числовым порядковым номером и, необязательно, именем. Точно так же функции могут быть импортированы из DLL по порядковому номеру или по имени. Порядковый номер представляет собой позицию указателя адреса функции в таблице адресов экспорта DLL. Обычно внутренние функции экспортируются только по порядковому номеру. Для большинства функций Windows API в разных выпусках Windows сохраняются только имена; порядковые номера могут быть изменены. Таким образом, нельзя надежно импортировать функции Windows API по порядковому номеру.

Импорт функций по порядковому номеру обеспечивает лишь немного лучшую производительность, чем импорт по имени: таблицы экспорта библиотек DLL упорядочиваются по имени, поэтому для поиска функции можно использовать двоичный поиск. Индекс найденного имени затем используется для поиска порядкового номера в таблице порядковых номеров экспорта. В 16-битной Windows таблица имен не сортировалась, поэтому затраты на поиск имени были гораздо более заметными.

Также возможно привязать исполняемый файл к определенной версии DLL, то есть разрешать адреса импортированных функций во время компиляции. Для связанного импорта компоновщик сохраняет отметку времени и контрольную сумму библиотеки DLL, к которой привязан импорт. Во время выполнения Windows проверяет, используется ли та же версия библиотеки, и если да, Windows обходит обработку импорта. В противном случае, если библиотека отличается от той, к которой была привязана, Windows обрабатывает импорт обычным образом.

Связанные исполняемые файлы загружаются несколько быстрее, если они запускаются в той же среде, для которой они были скомпилированы, и точно в то же время, если они запускаются в другой среде, поэтому нет никаких недостатков для привязки импорта. Например, все стандартные приложения Windows привязаны к системным библиотекам DLL соответствующей версии Windows. Хорошая возможность привязать импорт приложения к его целевой среде - во время установки приложения. Это удерживает библиотеки «связанными» до следующего обновления ОС. Однако он изменяет контрольную сумму исполняемого файла, поэтому это не то, что можно сделать с помощью подписанных программ или программ, которые управляются инструментом управления конфигурацией, который использует контрольные суммы (например, MD5 контрольные суммы) для управления версиями файлов. Поскольку в более поздних версиях Windows отказались от фиксированных адресов для каждой загруженной библиотеки (по соображениям безопасности), возможность и ценность привязки исполняемого файла уменьшаются.

Явное связывание во время выполнения

DLL-файлы могут быть явно загружены во время выполнения, процесс, называемый Microsoft просто как динамическое связывание во время выполнения, с помощью LoadLibrary(или LoadLibraryEx) API-функция. API-функция GetProcAddressиспользуется для поиска экспортированных символов по имени, а FreeLibrary- для выгрузки DLL. Эти функции аналогичны dlopen, dlsymи dlcloseв стандартном API POSIX.

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

Отложенная загрузка

Обычно приложение, связанное с библиотекой импорта DLL, не запускается, если DLL не может быть найдена, потому что Windows не запустит приложение, пока не сможет найти все библиотеки DLL, которые могут понадобиться приложению. Однако приложение может быть связано с библиотекой импорта, чтобы разрешить отложенную загрузку динамической библиотеки. В этом случае операционная система не будет пытаться найти или загрузить DLL при запуске приложения; вместо этого компоновщик включает в приложение заглушку, которая пытается найти и загрузить DLL через LoadLibrary и GetProcAddress при вызове одной из его функций. Если DLL не может быть найдена или загружена или вызываемая функция не существует, приложение сгенерирует исключение , которое может быть обнаружено и обработано соответствующим образом. Если приложение не обрабатывает исключение, оно будет перехвачено операционной системой, которая завершит программу с сообщением об ошибке.

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

Особенности компилятора и языка

Delphi

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

Delphi не нужны файлы LIBдля импорта функций из DLL; для связи с DLL ключевое слово externalиспользуется в объявлении функции для обозначения имени DLL, за которым следует nameдля обозначения символа (если он отличается) или index, чтобы определить индекс.

Microsoft Visual Basic

В Visual Basic (VB) поддерживается только связывание во время выполнения; но помимо использования функций API LoadLibraryи GetProcAddress, объявления импортированных функций разрешены.

При импорте функций DLL через объявления, VB выдаст ошибку времени выполнения, если файл DLLне может быть найден. Разработчик может обнаружить ошибку и обработать ее соответствующим образом.

При создании библиотек DLL в VB среда IDE разрешает создание только библиотек DLL ActiveX, однако были созданы методы, позволяющие пользователю явно указать компоновщику включить файл.DEF, который определяет порядковый номер и имя каждая экспортируемая функция. Это позволяет пользователю создавать стандартную DLL Windows с помощью Visual Basic (версии 6 или ниже), на которую можно ссылаться с помощью оператора «Declare».

C и C ++

Microsoft Visual C ++ (MSVC) предоставляет несколько расширений для стандартного C ++, которые позволяют напрямую указывать функции как импортированные или экспортированные в коде C ++; они были приняты другими компиляторами Windows C и C ++, включая версии Windows GCC. Эти расширения используют атрибут __declspecперед объявлением функции. Обратите внимание, что когда доступ к функциям C осуществляется из C ++, они также должны быть объявлены как extern «C»в коде C ++, чтобы сообщить компилятору, что следует использовать связь C.

Помимо указания импортированные или экспортированные функции с использованием атрибутов __declspec, они могут быть перечислены в разделе IMPORT или EXPORTS файла , используемого проектом. Файл DEFобрабатывается компоновщиком, а не компилятором, и поэтому он не является специфическим для C ++.

Компиляция DLL создаст файлы DLLи LIB. Файл LIB(библиотека импорта) используется для компоновки с DLL во время компиляции; это не требуется для связывания во время выполнения. Если DLL не является сервером модели компонентных объектов (COM), файл DLLдолжен быть помещен в один из каталогов, перечисленных в переменной среды PATH, в системном каталоге по умолчанию или в том же каталоге, что и программа, использующая его. Библиотеки DLL COM-сервера регистрируются с помощью regsvr32.exe, который помещает расположение библиотеки DLL и ее глобальный уникальный идентификатор (GUID ) в реестр. Затем программы могут использовать эту DLL, ища ее GUID в реестре , чтобы найти ее местоположение, или создать экземпляр COM-объекта косвенно, используя его идентификатор класса и идентификатор интерфейса.

Примеры программирования

Использование импорта DLL

В следующих примерах показано, как использовать привязки, зависящие от языка, для импорта символов для связывания с DLL во время компиляции.

Пример программы Delphi

{$ APPTYPE CONSOLE}; // импортируем функцию, складывающую два числа function AddNumbers (a, b: Double): Double; StdCall; внешний 'Example.dll'; // основная программа var R: Double; начало R: = AddNumbers (1, 2); Writeln ('Результат был:', R); конец.

C

Файл Example.lib должен быть включен (при условии, что Example.dll сгенерирован) в проект (опция «Добавить существующий элемент» для проекта!) Перед статической компоновкой. Файл Example.lib автоматически создается компилятором при компиляции DLL. Невыполнение вышеуказанного оператора вызовет ошибку связывания, поскольку компоновщик не будет знать, где найти определение AddNumbers. DLL-файл Example.dll, возможно, также придется скопировать в место, где будет создан EXE-файл, с помощью следующего кода.

#include #include // Функция импорта, которая добавляет два числа extern «C» __declspec (dllimport) double AddNumbers (double a, double b); int main (int argc, char * argv) {двойной результат = AddNumbers (1, 2); printf ("Результат был:% f \ n", результат); возврат 0; }

Использование явного связывания во время выполнения

В следующих примерах показано, как использовать средства загрузки и связывания во время выполнения с помощью привязок Windows API для конкретных языков.

Обратите внимание, что все четыре примера уязвимы для атак с предварительной загрузкой DLL, поскольку example.dll может быть разрешен в место, непредусмотренное автором (текущий рабочий каталог находится перед местоположениями системной библиотеки), а значит, и к вредоносной версии библиотеки. См. Справку Microsoft по безопасной загрузке библиотек: следует использовать SetDllDirectoryWв kernel32, чтобы удалить поиск в текущем каталоге перед загрузкой любых библиотек.

Microsoft Visual Базовый

Параметр Явное объявление функции AddNumbers Lib "Example.dll" _ (ByVal a как Double, ByVal b как Double) как Double Sub Main () Dim Result As Double Result = AddNumbers (1, 2) Debug.Print "The результат был: "Результат End Sub

Пример программы Delphi

; {$ APPTYPE CONSOLE} использует Windows; var AddNumbers: function (a, b: integer): Double; StdCall; LibHandle: HMODULE; begin LibHandle: = LoadLibrary ('example.dll'); если LibHandle <>0, то AddNumbers: = GetProcAddress (LibHandle, 'AddNumbers'); если присвоено (AddNumbers), то Writeln ('1 + 2 =', AddNumbers (1, 2)); Readln; конец.

C

#include #include // Сигнатура функции DLL typedef double (* importFunction) (double, double); int main (int argc, char ** argv) {importFunction addNumbers; двойной результат; HINSTANCE hinstLib; // Загрузка файла DLL hinstLib = LoadLibrary (TEXT ("Example.dll")); if (hinstLib == NULL) {printf ("ОШИБКА: невозможно загрузить DLL \ n"); возврат 1; } // Получить указатель на функцию addNumbers = (importFunction) GetProcAddress (hinstLib, "AddNumbers"); if (addNumbers == NULL) {printf ("ОШИБКА: не удалось найти функцию DLL \ n"); FreeLibrary (hinstLib); возврат 1; } // Вызов функции. результат = addNumbers (1, 3); // Выгрузка DLL-файла FreeLibrary (hinstLib); // Отображаем результат printf («Результат был:% f \ n», результат); возврат 0; }

Python

Привязка ctypes Python будет использовать POSIX API в системах POSIX.

import ctypes my_dll = ctypes.cdll.LoadLibrary ("Example.dll") # Следующая спецификация метода "restype" необходима, чтобы # Python понял, какой тип возвращается функцией. my_dll.AddNumbers.restype = ctypes.c_double p = my_dll.AddNumbers (ctypes.c_double (1.0), ctypes.c_double (2.0)) print ("Результат был:", p)

Модель объектных компонентов

Модель компонентных объектов (COM) определяет двоичный стандарт для размещения реализации объектов в файлах DLL и EXE. Он предоставляет механизмы для поиска и версии этих файлов, а также независимое от языка и машиночитаемое описание интерфейса. Размещение COM-объектов в DLL более легкое и позволяет им совместно использовать ресурсы с клиентским процессом. Это позволяет COM-объектам реализовывать мощные внутренние интерфейсы для простых интерфейсов с графическим интерфейсом пользователя, таких как Visual Basic и ASP. Их также можно запрограммировать с помощью языков сценариев.

Захват DLL

Из-за уязвимости, широко известной как захват DLL, подмена DLL, предварительная загрузка DLL или установка двоичного файла, многие программы загрузит и выполнит вредоносную DLL, содержащуюся в той же папке, что и файл данных, открытый этими программами. Уязвимость была обнаружена Георгием Гунински в 2000 году. В августе 2010 года она получила всемирную известность после того, как система безопасности ACROS снова открыла ее и многие сотни программ были признаны уязвимыми. Программы, запускаемые из небезопасных мест, то есть доступных для записи папок, таких как «Загрузки» или «Временный каталог», почти всегда подвержены этой уязвимости.

См. Также

Ссылки

  • Харт, Джонсон. Третье издание системного программирования Windows. Addison-Wesley, 2005. ISBN 0-321-25619-0.
  • Rector, Brent et al. Программирование Win32. Addison-Wesley Developers Press, 1997. ISBN 0-201-63492-9.

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

Последняя правка сделана 2021-05-18 07:25:34
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте