пользователей: 30398
предметов: 12406
вопросов: 234839
Конспект-online
РЕГИСТРАЦИЯ ЭКСКУРСИЯ

Динамические библиотеки. Базовые понятия. Неявное и явное связывание DLL и EXE

Динамически подключаемые библиотеки (dynamic-link libraries, DLL) — краеугольный камень операционной системы Windows, начиная с самой первой ее версии.

В DLL содержатся все функции Windows API:

Kernel32.dll (управление памятью, процессами и потоками),

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

GDI32.dll (графика и вывод текста),

AdvAPI32.dll (функции для защиты объектов, работы с реестром и регистрации событий),

ComDlg32.dll (стандартные диалоговые окна вроде File Open и File Save),

ComCrl32 dll (стандартные элементы управления) и т.д. 

Почему нужно применять DLL?

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

Возможность использования разных языков программирования.

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

Экономия памяти. Если одну и ту же DLL использует несколько приложений, в оперативной памяти может храниться только один ее экземпляр, доступный этим приложениям.

Разделение ресурсов. DLL могут содержать такие ресурсы, как шаблоны диалоговых окон, строки, значки и битовые карты (растровые изображения). Эти ресурсы доступны любым программам.

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

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

Реализация специфических возможностей. Определенная функциональность в Windows доступна только при использовании DLL. Например, это относится к загружаемым Web-браузером ActiveX-элементам, позволяющим создавать Web-страницы с более богатой функциональностью.

DLL и адресное пространство процесса:

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

Файлы с исходным кодом компилируются и компонуются так же, как и при создании ЕХЕ-файла. Но, создавая DLL, Вы должны указывать компоновщику ключ /DLL. Тогда компоновщик записывает в конечный файл информацию, по которой загрузчик операционной системы определяет, что данный файл – DLL, а не приложение.

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

Методы явного или неявного связывания различаются моментом загрузки и проецирования DLL на адресное пространство процесса.

В случае использования неявного связывания это делает загрузчик приложения Windows при его запуске.

Если искомый DLL не найдет ОС прервет запуск исполняемого файла с характерным сообщением.

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

Обработка ситуации, когда DLL не будет найден, полностью ложится на программиста.

Как только DLL спроецирована на адресное пространство вызывающего процесса, ее функции доступны всем потокам этого процесса.

Фактически библиотеки при этом теряют почти всю индивидуальность: для потоков код и данные DLL – просто дополнительные код и данные, оказавшиеся в адресном пространстве процесса.

Когда поток вызывает из DLL какую-то функцию, та считывает свои параметры из стека потока и размещает в этом стеке собственные локальные переменные.

Кроме того, любые созданные кодом DLL объекты принадлежат вызывающему потоку или процессу – DLL ничем не владеет.

 

Неявное связывание DLL и EXE.

Когда некий модуль (например, EXE) обращается к функциям и переменным, находящимся в DLL, в этом процессе участвует несколько файлов и компонентов. 

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

 

Неявное связывание DLL и EXE. Создание DLL

1.Нужно подготовить заголовочный файл с прототипами функций, структурами и идентификаторами, экспортируемыми из DLL. Этот файл включается в исходный код всех модулей Вашей DLL. Этот же файл понадобится и при сборке исполняемого модуля (или модулей), который использует функции и переменные из DLL.
2.Нужно написать на С/С++ модуль (или модули) исходного кода с телами функций и определениями переменных, которые должны находиться в DLL. Так как эти модули исходного кода не нужны для сборки исполняемого модуля, они могут остаться коммерческой тайной компании-разработчика.
3.Компилятор преобразует исходный код модулей DLL в OBJ-файлы (по одному на каждый модуль). 
4.Компоновщик собирает все OBJ-модули в единый загрузочный DLL-модуль, в который в конечном итоге помещаются двоичный код и переменные (глобальные и статические), относящиеся к данной DLL Этот файл потребуется при компиляции исполняемого модуля.
5.Если компоновщик обнаружит, что DLL экспортирует хотя бы одну переменную или функцию, то создаст и LIB-файл. Этот файл совсем крошечный, поскольку в нем нет ничего, кроме списка символьных имен функций и переменных, экспортируемых из DLL. Этот LIB-файл тоже понадобится при компиляции ЕХЕ-файла.
6.Создав DLL, можно перейти к сборке исполняемого модуля.
7.Во все модули исходного кода, где есть ссылки на внешние функции, переменные, структуры данных или идентификаторы, надо включить заголовочный файл, предоставленный разработчиком DLL.
 
Неявное связывание DLL и EXE. Создание EXE
1.Нужно написать на С/С++ модуль (или модули) исходного кода с телами функций и определениями переменных, которые должны находиться в ЕХЕ-файле. Естественно, ничто не мешает ссылаться на функции и переменные, определенные в заголовочном файле DLL-модуля.
2.Компилятор преобразует исходный код модулей EXE в OBJ-файлы (по одному на каждый модуль).
3.Компоновщик собирает все OBJ-модули в единый загрузочный ЕХЕ-модуль, в который в конечном итоге помещаются двоичный код и переменные (глобальные и статические), относящиеся к данному EXE. В нем также создается раздел импорта, где перечисляются имена всех необходимых DLL-модулей.
4.Кроме того, для каждой DLL в этом разделе указывается, па какие символьные имена функций и переменных ссылается двоичный код исполняемого файла. Эти сведения потребуются загрузчику операционной системы.
 

При запуске ЕХЕ - файла загрузчик операционной системы создает для его процесса виртуальное адресное пространство и проецирует на него исполняемый модуль. Далее загрузчик анализирует раздел импорта и пытается спроецировать все необходимые DLL на адресное пространство процесса.

Поскольку в разделе импорта указано только имя DLL (без пути), загрузчику приходится самому искать ее на дисковых устройствах в компьютере пользователя. Поиск DLL осуществляется в следующей последовательности:

Каталог, содержащий ЕХЕ - файл.

Текущий каталог процесса.

Системный каталог Windows.

Основной каталог Windows.

Каталоги, указанные в переменной окружения PATH.

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

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

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

C этого момента ссылка в коде на импортируемый идентификатор приводит к выборке его адреса из раздела импорта вызывающего модуля, открывая таким образом доступ к импортируемой переменной или функции.

Явное связывание DLL и EXE:

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

HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);

Функция выполняет поиск DLL с именем lpFileName и проецирует его на адресное пространство процесса, или, если DLL с таким именем уже загружено, увеличивает счетчик использований на единицу. Значение типа HMODULE, возвращаемое функцией, сообщает описатель модуля. Если спроецировать DLL на адресное пространство процесса не удалось, функции возвращают NULL.

Для выгрузки библиотеки нужно использовать

BOOL WINAPI FreeLibrary(HMODULE hModule);

Функция уменьшает счетчик использований на единицу. Если счетчик равен 0, ОС отключает DLL. Попытка обратится к любой функции из данной DLL будет завершаться ошибкой доступа.

Чтобы определить, спроецирована ли DLL на адресное пространство процесса, поток может вызывать функцию

HMODULE WINAPI GetModuleHandle(LPCTSTR lpModuleName);

Функция проверяет наличие DLL с именем lpModuleName и возвращает ее описатель, иначе NULL.

После проецирования DLL необходимо получить адрес экспортируемого идентификатора. Это делается с помощью функции

FARPROC WINAPI GetProcAddress(HMODULE hModule, LPCSTR  lpProcName);

Функция ищет в таблице экспортируемых идентификаторов модуля hModule  идентификатор с именем lpProcName и возвращает его действительный адрес или NULL, в случае, если имя не найдено. 


26.12.2015; 23:49
хиты: 0
рейтинг:0
для добавления комментариев необходимо авторизироваться.
  Copyright © 2013-2024. All Rights Reserved. помощь