Как мы уже отметили, DLL-библиотека представляет собой обычную программу на языке ассемблера. Выбор примера для демонстрации разработки и использования DLL-библиотеки неслучаен. Тем самым мы подтвердим тезис о том, что обычная программа и DLL-библиотека имеют много общего. С точки зрения структуры DLL-библиотека является набором функций, переменных и констант, а также необязательного кода инициализации, которые оформлены в соответствии с требованиями ассемблера. Ниже приведен пример DLL-библиотеки для нашей задачи.
;maket_dll.asm - текст DLL-библиотеки. :Содержит одну функцию - WriteCon
locals
.model flat.STDCALL ;модель памяти flat.
Объявление внешними используемых в данной программе функций Win32 (ASCII):
:обьявление процедуры WriteCon общедоступной publicdll WriteCon
.data
.code
DllMainproc
arg №h I nst: dword. @@event: dword. @
ret
DllMainendp
WriteCon ргос :см. дискету и prg05_ll.asm из главы 5 arg@@adr_str:dword.@@len_str:dword
ret
endp WriteCon endDllMain
Хорошо видно, что DLL-библиотека является действительно обычным файлом ассемблера. Есть все, даже имя точки входа, указываемое в последней директиве END. Но здесь и начинаются странности. На самом деле это не обычная точка входа, которую мы привыкли указывать в любой программе на ассемблере, а адрес команды в DLL-библиотеке, получающей управление в строго определенных случаях. Эта команда является первой в цепочке команд, составляющих так называемый код инициализации DLL-библиотеки. Назначение этого кода — выполнить необходимые действия по инициализации DLL-библиотеки при наступлении определенных событий. Наличие этого кода в DLL-библиотеке необязательно, и при его отсутствии нет необходимости указывать соответствующую метку в заключительной директиве END. Если все же код инициализации присутствует в DLL-библиотеке, то он должен быть разработан с учетом определенных требований.
- Во-первых, этот код должен быть рассчитан на то, что он получает управление в одном из четырех случаев. О наступлении каждого из этих случаевоперационная система извещает DLL-библиотеку путем передачи ей одного из четырех предопределенных значений — флагов. Значения этих флагов перечислены в файле winnt.h. Рассмотрим эти флаги и возможные действия при их поступлении в DLL-библиотеку.
- DLLPR0CESSATTACH-1 — передается операционной системой DLL-библиотеке при проецировании последней в адресное пространство процесса. Передача этого флага DLL-библиотеке производится всего один раз, обычно при загрузке приложения, использующего данную DLL-библиотеку. Если позже другой поток процесса попытается загрузить эту же библиотеку, то система попросту увеличит ее счетчик использования без посылки флага DLLPROCESSATTACH. Получив данный флаг, DLL-библиотека должна выполнить действия по созданию необходимой среды функционирования для своих функций. Например, обеспечить их кучей.
- DLL_THREAD_ATTACH=2 — передается операционной
системой DLL-библиотеке при создании нового потока в процессе. Этим
DLL-библиотеке предоставляется возможность нужным образом обработать
факт создания нового потока. Следует иметь в виду, что этот процесс не
является обратимым, то есть если DLL-библиотека загружается в процесс,
когда в нем уже функционируют потоки, то ни одному из них не посылается
флаг DLL_THREAD_ATTACH.
# DLL_THREAD_DETACH=3 — передается операционной системой DLL-библиотеке при выгрузке потоком DLL-библиотеки.
# DLL_PROCESS_DETACH=0 — передается операционной системой DLL-библиотеке при выгрузке DLL-библиотеки из адресного пространства процесса. Логично, что при этом требуется провести завершающие действия по освобождению всех ресурсов, которыми владеет DLL-библиотека. Обычно эти действия являются обратными по отношению к предпринятым при инициализации библиотеки (см. флаг DLLPROCESSATTACH).
Во-вторых, имя точки входа DLL-библиотеки может быть любым. Главное, чтобы при наличии кода инициализации это имя было указано в директиве END.
В-третьих, оформление кода инициализации в виде отдельной процедуры необязательно. Главное, выполнить два основных действия кода инициализации DLL-библиотеки (при его наличии): - # вернуть единицу в регистре ЕАХ;
# удалить из стека три параметра, которые передаются DLL-библиотеке при передаче описанных выше флагов: hlnstDLL — дескриптор DLL-библиотеки, назначенный ей системой при ее загрузке в адресное пространство процесса; - vent — значение флага, передаваемого в DLL-библиотеку; f ImpLoad — параметр не равен 0, если библиотека загружена неявно (см. ниже), и равен 0 в обратном случае.
Структура полного варианта инициализациониого кода выглядит так:
includeWindowConA.inc;проверьте присутствие значений флагов в этом файле"
DllMain ргос
arg hlnstDLL:dword. event:dword,fImpLoad:dword
cmp [event].DLL_PROCESS_ATTACH
jne m выполняем действия для DLL_PROCESS_ATTACH
cmp [event].DLL_THREAD_ATTACH
jnem :выполняем действия для DLL_THREAD_ATTACH
cmp [event]. DLL_THREAD_DETACH
jnem выполняем действия для DLL_THREAD_DETACH
cmp [event].DLL_PROCESS_DETACH
jnem
выполняем действия для DLL_PROCESS_DETACH m: moveax.l
ret DllMainendp
Минимальный вариант может выглядеть так, как это сделано в нашем примере:
DllMain ргос
arg hlnstDLL:dword. event:dword,fImpLoad:dword
m: mov eax.l
ret DllMainendp
Или так:
DllMain: m: moveax.l ret 12
He забывайте, что директива arg приводит к тому, что в код,
генерируемый транслятором, вставляются команды ENTERD и LEAVED (см. выше
разделы «Реализация рекурсивных процедур» и «Реализация вложенных
процедур»). Кроме этого, команда RET процедуры дополняется значением,
равным сумме длин параметров, указанных в директиве ARG . Исполнение
такой команды приводит к удалению из стека количества байт, равного
этому сформированному значению.
Что касается кода функций (процедур), составляющих DLL-библиотеку, то
для их написания используются обычные правила разработки программ.
Описание данных также ничем не отличается от обычной программы
ассемблера. Ведь в конечном итоге код и данные процедур DLL-библиотеки
оказываются в адресном пространстве процесса наравне с его кодом и
данными.
Последнее, что необходимо отметить, — все экземпляры данных и имена
процедур, которые должны быть видны вне пределов DLL-библиотеки,
объявляются общими с использованием одной из директив PUBLIC или
PUBLICDLL.