Assembler для Windows


Глава 3. Создание динамических библиотек

I

Использование динамических библиотек (по-другому - библиотек динамической компоновки) - это способ осуществления модульности в период выполнения программы. Динамическая библиотека (Dynamic Link Library - DLL) позволяет упростить и саму разработку программного обеспечения. Вместо того чтобы каждый раз перекомпилировать огромные ЕХЕ-программы, достаточно перекомпилировать лишь отдельный динамический модуль. Кроме того, доступ к динамической библиотеке возможен сразу из нескольких исполняемых модулей, что делает многозадачность более гибкой. Структуре исполняемых модулей будет посвящена отдельная глава, но уже сейчас могу сказать, что структура DLL-модуля практически такая же, как и ЕХЕ-модуля. Тот, кто программировал под MS DOS, должен быть знаком с понятием оверлея. По своей функциональности динамическая библиотека очень похожа на оверлей, но название "динамическая библиотека" более удачно42.

При написании ЕХЕ-модулей Вы уже познакомились с тем, как определять импортируемые функции. Достаточно объявить эти функции как EXTERN. При создании динамической библиотеки Вам придется указывать и импортируемые, и экспортируемые функции.

Для того чтобы двигаться дальше, введу такое понятие, как связывание. Собственно, я уже ввел это понятие, когда рассматривал работу редактора связей. Во время трансляции связываются имена, указанные в программе как внешние, (EXTERN) с соответствующими именами из библиотек, которые указываются при помощи директивы IMPORTLIB. Такое связывание называется ранним (или статическим). Напротив, в случае с динамической библиотекой связывание происходит во время выполнения модуля. Такое связывание называется поздним (или динамическим). При этом позднее связывание может происходить в автоматическом режиме в начале запуска программы и при помощи специальных API-функций (см. ниже), по желанию программиста. При этом говорят о явном и неявном связывании. Сказанное иллюстрирует Рис. 3.3.1. Заметим также, что использование динамической библиотеки экономит дисковое пространство, т.к. представленная в библиотеке процедура содержится лишь один раз, в отличие от процедур, помещаемых в модули из статических библиотек43.

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

Рис. 3.3.1. Иллюстрация понятия связывания в ассемблере.

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

В любой динамической библиотеке следует определить точку входа (процедура входа). По умолчанию за точку входа принимают метку, указываемую за директивой END (например, END START). При загрузке динамической библиотеки и выгрузке динамической библиотеки автоматически вызывается процедура входа. Заметим при этом, что каким бы способом ни была загружена динамическая библиотека (явно или неявно), выгрузка динамической библиотеки из памяти будет происходить автоматически при закрытии процесса или потока. В принципе, процедура входа может быть использована для некоторой начальной инициализации переменных. Довольно часто эта процедура остается пустой. При вызове процедуры входа в нее помещаются три параметра:

  • 1-й параметр. Идентификатор DLL-модуля.
  • 2-й параметр. Причина вызова (см. ниже).
  • 3-й параметр. Резерв.

Рассмотрим подробнее второй параметр процедуры входа. Вот четыре возможных значения этого параметра:

DLL_PROCESS_DETACH equ 0
DLL_PROCESS_ATTACH equ 1
DLL_THREAD_ATTACH equ 2
DLL_THREAD_DETACH equ 3

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

DLL_THREAD_ATTACH - сообщает, что Текущий процесс создает новый поток. Такое сообщение посылается всем динамическим библиотекам, загруженным к этому времени процессом.

DLL_PROCESS_DETACH - сообщает, что динамическая библиотека выгружается из адресного пространства процесса.

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

42 Оверлей (Overlay) в переводе означает перекрытие - указание на то, что в оверлейную область памяти могут загружаться по очереди разные части оверлея, перекрывая друг друга.

43 Вообще говоря, библиотеки, используемые нами для программирования в Windows, такие как import32.lib, user32.lib и т.п., правильнее называть не статическими библиотеками, а библиотеками импорта. В них нет программного кода, а лишь информация, используема для трансляции.


II

Перейдем теперь к разбору программных примеров динамических библиотек. На Рис. 3.3.2 приводится пример простейшей динамической библиотеки. Данная динамическая библиотека, по сути, ничего не делает. Просто при загрузке библиотеки, ее выгрузки, а также вызове процедуры DLLP1 будет вызвано обычное Windows-сообщение. Обратите внимание, как определяется процесс загрузки и выгрузки библиотеки. Заметим также, что процедура входа должна возвращать ненулевое значение. Процедура DLLP1 обрабатывает также один параметр, передаваемый через стек обычным способом.

.386P
; плоская модель
IFDEF MASM
.MODEL FLAT, stdcall
ELSE
.MODEL FLAT
ENDIF
PUBLIC DLLP1
; константы
; сообщения, приходящие при открытии
; динамической библиотеки
DLL_PROCESS_DETACH equ 0
DLL_PROCESS_ATTACH equ 1
DLL_THREAD_ATTACH equ 2
DLL_THREAD_DETACH equ 3
IFDEF MASM
; MASM
; прототипы внешних процедур
EXTERN MessageBoxA@16:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; TASM
EXTERN MessageBoxA:NEAR
MessageBoxA@16 = MessageBoxA
includelib c:\tasm32\lib\import32.lib
ENDIF
;--------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
TEXT1 DB 'Вход в библиотеку',0
TEXT2 DB 'Выход из библиотеки',0
MS DB 'Сообщение из библиотеки',0
TEXT DB 'Вызов процедуры из DLL',0
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
; [EBP+10H] ; резервный параметр
; [EBP+0CH] ; причина вызова
; [EBP+8] ; идентификатор DLL-модуля
DLLENTRY:
MOV EAX,DWORD PTR [EBP+0CH]
CMP EAX,0
JNE D1
; закрытие библиотеки
PUSH 0
PUSH OFFSET MS
PUSH OFFSET TEXT2
PUSH 0
CALL MessageBoxA@16
JMP _EXIT
D1:
CMP EAX,1
JNE _EXIT
; открытие библиотеки
PUSH 0
PUSH OFFSET MS
PUSH OFFSET TEXT1
PUSH 0
CALL MessageBoxA@16
_EXIT:
MOV EAX,1
RET 12
;———————————————————
; [EBP+8] ; параметр процедуры
DLLP1 PROC EXPORT
PUSH EBP
MOV EBP,ESP
CMP DWORD PTR [EBP+8],1
JNE _EX
PUSH 0
PUSH OFFSET MS
PUSH OFFSET TEXT
PUSH 0
CALL MessageBoxA@16
_EX:
POP EBP
RET 4
DLLP1 ENDP
_TEXT ENDS
END DLLENTRY

Puc. 3.3.2. Простейшая DLL-библиотека.

Программа на Рис. 3.3.2 может быть оттранслирована как с помощью MASM32, так и TASM32 (см. ниже). На этом стоит остановиться более подробно. Прежде всего обратите внимание, что за процедурой, вызываемой из другого модуля, мы указали ключевое слово EXPORT. Это слово необходимо для правильной трансляции в MASM. Для TASM этого не нужно, но, к счастью, этот транслятор просто не замечает наличия какого-либо слова после PROC. Зато для TASM процедура DLLP1 должна быть определена как PUBLIC, кроме того, для трансляции в пакете TASM необходимо подготовить DEF-файл и указать его в командной строке TLINK32. Для создания динамических библиотек в строке link следует указать ключ /DLL, а в строке tlink32 -Tpd (no умолчанию работает ключ -Tpe). Ключ /ENTRY:DLLENTRY в строке link можно опустить, так как точка входа определяется из директивы END DLLENTRY.

Трансляция динамической библиотеки на Рис. 3.3.2.
MASM32.

ml /c /coff /DMASM dll1.asm
link /subsystem:windows /DLL /ENTRY:DLLENTRY dll1.obj
TASM32.
tasm32 /ml dll1.asm
tlink32 -aa -Tpd dll1.obj,,,,dll1.def
Содержимое dll1.def:
EXPORTS DLLP1

Ниже на Рис. 3.3.3 представлена программа, которая загружает динамическую библиотеку, показанную на Рис. 3.3.2. Это пример позднего связывания. Библиотека должна быть вначале загружена при помощи функции LoadLibrary. Затем определяется адрес процедуры с помощью функции GetProcAddress, после чего можно осуществлять вызов. Как и следовало ожидать, MASM помещает в динамическую библиотеку вместо DLLP1 имя _DLLP1@0, тогда как TASM помещает имя без искажения. Это мы учитываем в нашей программе. Мы учитываем также возможность ошибки при вызове функций LoadLibrary и GetProcAddress. В этой связи укажем, как (в какой последовательности) ищет библиотеку функция LoadLibrary:

  1. Поиск в каталоге, откуда была запущена программа.
  2. Поиск в текущем каталоге.
  3. В системном директории (GetSystemDirectory).
  4. В директории Windows (GetWindowsDirectory).
  5. В каталогах, указанных в окружении (PATH).

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

.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
; прототипы внешних процедур
IFDEF MASM
;MASM
EXTERN GetProcAddress@8:NEAR
EXTERN LoadLibraryA@4:NEAR
EXTERN FreeLibrary@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN MessageBoxA@16:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; TASM
EXTERN GetProcAddress:NEAR
EXTERN LoadLibraryA:NEAR
EXTERN FreeLibrary:NEAR
EXTERN ExitProcess:NEAR
EXTERN MessageBoxA:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\tasm32\lib\import32.lib
GetProcAddress@8 = GetProcAddress
LoadLibraryA@4 = LoadLibraryA
FreeLibrary@4 = FreeLibrary
ExitProcess@4 = ExitProcess
MessageBoxA@16 = MessageBoxA
ENDIF
;-----------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
TXT DB 'Ошибка динамической библиотеки',0
MS DB 'Сообщение',0
LIBR DB 'DLL1.DLL',0
HLIB DD ?
IFDEF MASM
NAMEPROC DB '_DLLP1@0',0
ELSE
NAMEPROC DB 'DLLP1',0
ENDIF
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; загрузить библиотеку
PUSH OFFSET LIBR
CALL LoadLibraryA@4
CMP EAX,0
JE _ERR
MOV HLIB,EAX
; получить адрес процедуры
PUSH OFFSET NAMEPROC
PUSH HLIB
CALL GetProcAddress@8
CMP EAX,0
JNE YES_NAME
; сообщение об ошибке
_ERR:
PUSH 0
PUSH OFFSET MS
PUSH OFFSET TXT
PUSH 0
CALL MessageBoxA@16
JMP _EXIT
YES_NAME:
PUSH 1 ; параметр
CALL EAX
; закрыть библиотеку
PUSH HLIB
CALL FreeLibrary@4
; библиотека автоматически закрывается также
; при выходе из программы
; выход
_EXIT:
PUSH 0
CALL ExitProcess@4
_TEXT ENDS
END START

Рис. 3.3.3. Вызов динамической библиотеки. Явное связывание.

Трансляция программы на Рис. 3.3.3 ничем не отличается от трансляции обычных программ.

MASM32.

ml /c /coff /DMASM dllex.asm
link /subsystem:windows dllex.obj
TASM32.
tasm32 /ml dllex.asm
tlink32 -aa dllex.obj

III

Хотя я указал, что, на мой взгляд, неявное связывание менее гибко, я нашел необходимым привести пример неявного связывания. Мы здесь рассмотрим только вызывающую программу, так как вызываемая программа, естественно не меняется. Как видите, текст программы стал несколько проще. Здесь важно заметить, что, во-первых, необходимо объявить вызываемую из динамической библиотеки процедуру как внешнюю, а, во-вторых, подключить статическую библиотеку DLLP1.LIB.

.386P
; плоская модель
IFDEF MASM
.MODEL FLAT, stdcall
ELSE
.MODEL FLAT
ENDIF
; константы
; прототипы внешних процедур
includelib dll1.lib
IFDEF MASM
; MASM
EXTERN DLLP1@0:NEAR
EXTERN ExitProcess@4:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; директивы компоновщику для подключения библиотек
includelib c:\tasm32\lib\import32.lib
EXTERN DLLP1:NEAR
EXTERN ExitProcess:NEAR
DLLP1@0 = DLLP1
ExitProcess@4 = ExitProcess
ENDIF
;-----------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
PUSH 1 ; параметр
CALL DLLP1@0
; выход
_EXIT:
PUSH 0
CALL ExitProcess@4
_TEXT ENDS
END START

Рис. 3.3.4. Вызов динамической библиотеки. Неявное связывание.

У читателя, скорее всего, возникнет вопрос, откуда появляется библиотека DLLP1.LIB. Здесь все достаточно просто. Транслятор MASM создает библиотеку автоматически, а в трансляторе TASM есть утилита IMPLIB, которая создает статическую библиотеку непосредственно из динамической библиотеки.

Трансляция программы на Рис. 3.3.4.

MASM32.

ml /c /coff /DMASM dllex.asm
link /subsystem:windows dllex.obj
TASM32.
tasm32 /ml dllex.asm
implib dll1.lib dll1.dll
tlink32 -aa dllex.obj

Ранее было сказано, что возможным механизмом связывания является определения адреса процедуры через порядковый номер. Изложим схему того, как это можно сделать. Сначала Вы должны сопоставить процедуре, которая будет вызываться из динамической библиотеки, некое двухбайтное число. Это делается посредством строки в DEF-файле: EXPORTS DLLP1@1. Здесь процедуре DLLP1 сопоставляется номер 1. DEF-файлы мы уже использовали при трансляции в TASM. Точно также их можно использовать и в MASM (ключ /DEF:имя для программы link). После этого производится трансляция, и динамическая библиотека готова. Теперь при вызове функции GetProcAddress вторым параметром следует указать порядковый номер, точнее двойное слово, младшее слово которого есть порядковый номер, а старшее слово равно нулю. И все будет работать точно также, как и раньше. Лично я не вижу особой необходимости использовать такой подход.

IV

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

; динамическая библиотека DLL2.ASM
.386P
; плоская модель
IFDEF MASM
.MODEL FLAT, stdcall
ELSE
.MODEL FLAT
ENDIF
PUBLIC DLLP1
; константы
; сообщения, приходящие при открытии
; динамической библиотеки
DLL_PROCESS_DETACH equ 0
DLL_PROCESS_ATTACH equ 1
DLL_THREAD_ATTACH equ 2
DLL_THREAD_DETACH equ 3
IFDEF MASM
; MASM
; прототипы внешних процедур
EXTERN MessageBoxA@16:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; TASM
EXTERN MessageBoxA:NEAR
MessageBoxA@16 = MessageBoxA
includelib c:\tasm32\lib\import32.lib
ENDIF
;--------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
TEXT DB "Строка в динамической библиотеке",0
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
; [EBP+10H] ; резервный параметр
; [EBP+0CH] ; причина вызова
; [EBP+8] ; идентификатор DLL-модуля
DLLENTRY:
MOV EAX,DWORD PTR [EBP+0CH]
CMP EAX,0
JNE D1
; закрытие библиотеки
JMP _EXIT
D1:
CMP EAX,1
JNE _EXIT
; открытие библиотеки
_EXIT:
MOV EAX,1
RET 12
;---------------------
; адреса параметров
; [EBP+8]
; [EBP+0CH]
DLLP1 PROC EXPORT
PUSH EBP
MOV EBP,ESP
PUSH 0
PUSH DWORD PTR [EBP+0CH]
PUSH DWORD PTR [EBP+8]
PUSH 0
CALL MessageBoxA@16
POP EBP
LEA EAX,TEXT
RET 8
DLLP1 ENDP
_TEXT ENDS
END DLLENTRY
; основной модуль DLLEX2.ASM, вызывающий
; процедуру из динамической библиотеки
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
; прототипы внешних процедур
IFDEF MASM
; MASM
EXTERN GetProcAddress@8:NEAR
EXTERN LoadLibraryA@4:NEAR
EXTERN FreeLibrary@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN MessageBoxA@16:NEAR
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; TASM
includelib c:\tasm32\lib\import32.lib
EXTERN GetProcAddress:NEAR
EXTERN LoadLibraryA:NEAR
EXTERN FreeLibrary:NEAR
EXTERN ExitProcess:NEAR
EXTERN MessageBoxA:NEAR
GetProcAddress@8 = GetProcAddress
LoadLibraryA@4 = LoadLibraryA
FreeLibrary@4 = FreeLibrary
ExitProcess@4 = ExitProcess
MessageBoxA@16 = MessageBoxA
ENDIF
;----------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
TXT DB 'Ошибка динамической библиотеки',0
MS DB 'Сообщение',0
LIBR DB 'DLL2.DLL',0
HLIB DD ?
MS1 DB 'Сообщение из библиотеки',0
TEXT DB 'Строка содержится в основном модуле',0
IFDEF MASM
NAMEPROC DB '_DLLP1@0',0
ELSE
NAMEPROC DB 'DLLP1',0
ENDIF
_DATA ENDS
; сегмент кода

_TEXT SEGMENT DWORD PUBLIC USE32 'CODE' ; [EBP+10H] ; резервный параметр ; [EBP+0CH] ; причина вызова ; [EBP+8] ; идентификатор DLL-модуля START: ; загрузить библиотеку PUSH OFFSET LIBR CALL LoadLibraryA@4 CMP EAX,0 JE _ERR MOV HLIB,EAX ; получить адрес PUSH OFFSET NAMEPROC PUSH HLIB CALL GetProcAddress@8 CMP EAX,0 JNE YES_NAME ; сообщение об ошибке _ERR: PUSH 0 PUSH OFFSET MS PUSH OFFSET TXT PUSH 0 CALL MessageBoxA@16 JMP _EXIT YES_NAME: PUSH OFFSET MS1 PUSH OFFSET TEXT CALL EAX PUSH 0 PUSH OFFSET MS PUSH EAX PUSH 0 CALL MessageBoxA@16 ; закрыть библиотеку PUSH HLIB CALL FreeLibrary@4 ; библиотека автоматически закрывается также ; при выходе из программы ; выход _EXIT: PUSH 0 CALL ExitProcess@4 _TEXT ENDS END START

Рис. 3.3.5. Основной модуль и динамическая библиотека. Передача параметров.

Трансляция программы на Рис. 3.3.5.
MASM32.

ml /c /coff /DMASM dllex2.asm
link /subsystem:windows dllex2.obj
TASM32.
tasm32 /ml dllex2.asm
tlink32 -aa -Tpd dllex2.obj

Ниже мы рассмотрим весьма интересный пример (Рис. 3.3.6). Основной процесс использует ресурсы загруженной им динамической библиотеки. Я уже говорил, что файлы шрифтов, по сути, являются динамическими библиотеками. Не правда ли удобно: ресурсы можно поместить отдельно от основной программы в динамическую библиотеку, загружая их по мере необходимости? Наша программа вначале загружает иконку из ресурсов динамической библиотеки и устанавливает ее на окно. Если Вы будете щелкать правой кнопкой мыши, направив курсор на окно, то будет вызываться процедура из динамической библиотеки, которая будет поочередно устанавливать то один, то другой значок на окно.

// файл dll3.rc
// идентификаторы
#define IDI_ICON1 3
#define IDI_ICON2 10
// определили иконку
IDI_ICON1 ICON "ico1.ico"
IDI_ICON2 ICON "ico2.ico"
; динамическая библиотека DLL3.ASM
.386P
PUBLIC SETIC
; плоская модель
IFDEF MASM
.MODEL FLAT, stdcall
ELSE
.MODEL FLAT
ENDIF
; константы
WM_SETICON equ 80h
IFDEF MASM
; MASM
; прототипы внешних процедур
EXTERN LoadIconA@8:NEAR
EXTERN PostMessageA@16:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; TASM
; прототипы внешних процедур
EXTERN LoadIconA:NEAR
EXTERN PostMessageA:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\tasm32\lib\import32.lib
LoadIconA@8 = LoadIconA
PostMessageA@16 = PostMessageA
ENDIF
;-------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
PRIZ DB 0
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
DLLENTRY:
MOV EAX,1
RET 12
; [EBP+8]
; [EBP+0CH]
SETIC PROC EXPORT
PUSH EBP
MOV EBP,ESP
; выбрать, какую иконку устанавливать
CMP PRIZ,0
JZ IC_1
MOV PRIZ,0
PUSH 3
JMP CONT
IC_1:
MOV PRIZ,1
PUSH 10
CONT:
; загрузить иконку из ресурсов библиотеки
PUSH DWORD PTR [EBP+0CH] ; идентификатор
; динамической
; библиотеки
CALL LoadIconA@8
; установить значок окна
PUSH EAX
PUSH 0
PUSH WM_SETICON
PUSH DWORD PTR [EBP+08H] ; дескриптор окна
CALL PostMessageA@16
POP EBP
RET 8
SETIC ENDP
_TEXT ENDS
END DLLENTRY
// файл dllex3.rc
// определение констант
#define WS_SYSMENU 0x00080000L
#define WS_MINIMIZEBOX 0x00020000L
#define WS_MAXIMIZEBOX 0x00010000L
#define DS_3DLOOK 0x0004L
// определение диалогового окна
DIAL1 DIALOG 0, 0, 340, 120
STYLE WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | DS_3DLOOK
CAPTION "Диалоговое окно с иконкой из динамической библиотеки"
FONT 8, "Arial"
{
}
; основной модуль DLLEX3.ASM, вызывающий
; процедуру из динамической библиотеки
.386P
; плоская модель
IFDEF MASM
.MODEL FLAT, stdcall
ELSE
.MODEL FLAT
ENDIF
; константы
; сообщение приходит при закрытии окна
WM_CLOSE equ 10h
WM_INITDIALOG equ 110h
WM_SETICON equ 80h
WM_LBUTTONDOWN equ 201h
; прототипы внешних процедур
IFDEF MASM
; MASM
EXTERN PostMessageA@16:NEAR
EXTERN GetProcAddress@8:NEAR
EXTERN LoadLibraryA@4:NEAR
EXTERN FreeLibrary@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN LoadIconA@8:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; директивы компоновщику для подключения библиотек
includelib c:\tasm32\lib\import32.lib
EXTERN PostMessageA:NEAR
EXTERN GetProcAddress:NEAR
EXTERN LoadLibraryA:NEAR
EXTERN FreeLibrary:NEAR
EXTERN ExitProcess:NEAR
EXTERN GetModuleHandleA:NEAR
EXTERN DialogBoxParamA:NEAR
EXTERN EndDialog:NEAR
EXTERN LoadIconA:NEAR
PostMessageA@16 = PostMessageA
LoadIconA@8 = LoadIconA
EndDialog@8 = EndDialog
GetModuleHandleA@4 = GetModuleHandleA
DialogBoxParamA@20 = DialogBoxParamA
GetProcAddress@8 = GetProcAddress
LoadLibraryA@4 = LoadLibraryA
FreeLibrary@4 = FreeLibrary
ExitProcess@4 = ExitProcess
ENDIF
;------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
LIBR DB 'DLL3.DLL',0
HLIB DD ?
HINST DD ?
PA DB "DIAL1",0
IFDEF MASM
NAMEPROC DB "_SETIC@0",0
ELSE
NAMEPROC DB "SETIC",0
ENDIF
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить дескриптор приложения
PUSH 0
CALL GetModuleHandleA@4
; создать диалог
MOV [HINST], EAX
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
; выход
_EXIT:
PUSH 0
CALL ExitProcess@4
; процедура окна
; расположение параметров в стеке
; [EBP+014Н] ; LPARAM
; [EBP+10Н] ; WAPARAM
; [EBP+0CH] ; MES
; [EBP+8] ; HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
;-------------------
CMP DWORD PTR [EBP+0CH],WM_CLOSE
JNE L1
; закрыть библиотеку
; библиотека автоматически закрывается также
; при выходе из программы
PUSH HLIB
CALL FreeLibrary@4
PUSH 0
PUSH DWORD PTR [EBP+08H]
CALL EndDialog@8
JMP FINISH
L1:
CMP DWORD PTR [EBP+0CH],WM_INITDIALOG
JNE L2
; загрузить библиотеку
PUSH OFFSET LIBR
CALL LoadLibraryA@4
MOV HLIB,EAX
; загрузить иконку
PUSH 3 ; идентификатор иконки
PUSH [HLIB] ; идентификатор процесса
CALL LoadIconA@8
; установить иконку
PUSH EAX
PUSH 0 ; тип иконки (маленькая)
PUSH WM_SETICON
PUSH DWORD PTR [EBP+08H]
CALL PostMessageA@16
JMP FINISH
L2:
CMP DWORD PTR [EBP+0CH],WM_LBUTTONDOWN
JNE FINISH
; получить адрес процедуры из динамической библиотеки
PUSH OFFSET NAMEPROC
PUSH HLIB
CALL GetProcAddress@8
; вызвать процедуру с двумя параметрами
PUSH [HLIB]
PUSH DWORD PTR [EBP+08H]
CALL EAX
FINISH:
POP EDI
POP ESI
POP EBX
POP EBP
MOV EAX,0
RET 16
WNDPROC ENDP
_TEXT ENDS
END START

Рис. З.З.6. Пример загрузки ресурса из динамической библиотеки.

Трансляция программ на Рис. 3.3.6.
MASM32:

ml /c /coff /DMASM dllex3.asm
rc dllex3.rc
link /subsystem:windows dllex3.obj dllex3.res
ml /c /coff /DMASM dll3.asm
rc dll3.rc
link /subsystem:windows /DLL /ENTRY:DLLENTRY dll3.obj dll3.res
TASM32:
tasm32 /ml dllex3.asm
brcc32 dllex3.rc
tlink32 -aa -Tpe dllex3.obj,,,,,dllex3.res
tasm32 /ml dll3.asm
brcc32 dll3.rc
tlink32 -aa -Tpd dll3.obj,,,,dll3.def,dll3.res

Содержимое файла dll3.def:

EXPORTS SETIC

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

V

Рассмотрим теперь вопрос о том, как используют динамическую библиотеку различные экземпляры приложения или разные процессы. Если Вы немного знакомы с принципом функционирования операционной системы Windows, то, возможно, такая постановка вопроса у Вас вызовет недоумение. "У каждого приложения свое адресное пространство, куда загружается динамическая библиотека", - скажете Вы. Конечно, это не совсем рационально, но зато безопасно. О памяти мы еще подробно будем говорить в гл. 3.6, здесь же заметим, что, вообще говоря, приложение может инициализировать так называемую разделяемую память. Мы вернемся к этому вопросу еще неоднократно, сейчас же рассмотрим этот вопрос чисто технически, применительно к динамическим библиотекам. Рассмотрим конкретную ситуацию. Запускаемое приложение загружает динамическую библиотеку и вызывает процедуру из динамической библиотеки, которая меняет данные, расположенные опять же в динамической библиотеке. Запустим теперь второй экземпляр приложения. Оно загружает еще один экземпляр динамической библиотеки. Могут быть ситуации, когда желательно, чтобы второе запущенное приложение "знало", что по команде первого приложения данные уже изменились. Ясно, что в этом случае данные, которыми оперирует динамическая библиотека, должны быть общими. Технически это делается очень просто. У редактора связей LINK есть опция /section: имя, атрибуты, которая позволяет объявить явно свойства данной секции. Мы будем говорить о секциях далее, здесь же достаточно сказать, секция - это просто сегмент в старом понимании. В редакторе связей TLINK32 то же действие можно осуществить с помощью файла .DEF.

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

; динамическая библиотека DLL4.ASM
.386P
; плоская модель
IFDEF MASM
.MODEL FLAT, stdcall
ELSE
.MODEL FLAT
ENDIF
PUBLIC DLLP1
IFDEF MASM
; MASM
; прототипы внешних процедур
EXTERN MessageBoxA@16:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; TASM
EXTERN MessageBoxA:NEAR
MessageBoxA@16 = MessageBoxA
includelib c:\tasm32\lib\import32.lib
ENDIF
;--------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
TEXT DB "В динамической библиотеке",0
MS DB "Сообщение",0
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
; [EBP+10H] ; резервный параметр
; [EBP+0CH] ; причина вызова
; [EBP+8] ; идентификатор DLL-модуля
DLLENTRY:
MOV EAX,1
RET 12
;------------------
;адреса параметров
DLLP1 PROC EXPORT
PUSH EBP
MOV EBP,ESP
PUSH 0
PUSH OFFSET MS
PUSH OFFSET TEXT
PUSH 0
CALL MessageBoxA@16
; изменим строку, расположенную в разделяемой памяти
MOV TEXT,'И'
MOV TEXT+1,'з'
POP EBP
RET
DLLP1 ENDP
_TEXT ENDS
END DLLENTRY
; основной модуль DLLEX4.ASM, вызывающий
; процедуру из динамической библиотеки
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
; прототипы внешних процедур
IFDEF MASM
; MASM
EXTERN GetProcAddress@8:NEAR
EXTERN LoadLibraryA@4:NEAR
EXTERN FreeLibrary@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN MessageBoxA@16:NEAR
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
ELSE
; директивы копоновщику для подключения библиотек
includelib c:\tasm32\lib\import32.lib
EXTERN GetProcAddress:NEAR
EXTERN LoadLibraryA:NEAR
EXTERN FreeLibrary:NEAR
EXTERN ExitProcess:NEAR
EXTERN MessageBoxA:NEAR
GetProcAddress@8 = GetProcAddress
LoadLibraryA@4 = LoadLibraryA
FreeLibrary@4 = FreeLibrary
ExitProcess@4 = ExitProcess
MessageBoxA@16 = MessageBoxA
ENDIF
;----------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
TXT DB 'Ошибка динамической библиотеки',0
MS DB 'Сообщение',0
LIBR DB 'DLL4.DLL',0
HLIB DD ?
IFDEF MASM
NAMEPROC DB '_DLLP1@0',0
ELSE
NAMEPROC DB 'DLLP1',0
ENDIF
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
; [EBP+10H] ; резервный параметр
; [EBP+0CH] ; причина вызова
; [EBP+8] ; идентификатор DLL-модуля
START:
; загрузить библиотеку
PUSH OFFSET LIBR
CALL LoadLibraryA@4
CMP EAX,0
JE _ERR
MOV HLIB,EAX
; получить адрес
PUSH OFFSET NAMEPROC
PUSH HLIB
CALL GetProcAddress@8
CMP EAX,0
JNE YES_NAME
; сообщение об ошибке
_ERR:
PUSH 0
PUSH OFFSET MS
PUSH OFFSET TXT
PUSH 0
CALL MessageBoxA@16
JMP _EXIT
YES_NAME:
CALL EAX
PUSH 0
PUSH OFFSET MS
PUSH OFFSET MS
PUSH 0
CALL MessageBoxA@16
; закрыть библиотеку
; библиотека автоматически закрывается также
; при выходе из программы
PUSH OFFSET NAMEPROC
PUSH HLIB
CALL FreeLibrary@4
; выход
_EXIT:
PUSH 0
CALL ExitProcess@4
_TEXT ENDS
END START

Рис. 3.3.7. Пример использования разделяемой памяти в динамической библиотеке.

Трансляция программ на Рис. 3.3.7.
MASM32.

ml /c /coff /DMASM dll4.asm
link /subsystem:windows /DLL /section:.data,SRW dll4.obj
ml /c /coff /DMASM dllex4.asm
link /subsystem:windows dllex4.obj

Атрибуты опции SECTION: S-SHARED, R-READ, W-WRITE.

TASM32.

tasm32 /ml dll4.asm
tlink32 -aa -Tpd dll4.obj,,,,dll4.def
tasm32 /ml dllex4.asm
tlink32 -aa dllex4.obj

Содержимое DEF-файла:

SECTIONS .DATA SHARED
EXPORTS DLLP1