На этом шаге мы рассмотрим алгоритм создания и использования DLL.
Перейдем теперь к разбору программных примеров динамически подключаемых библиотек. В примере 1 приводится текст простейшей DLL. Данная DLL, по сути, ничего не делает. Просто при загрузке библиотеки, при ее выгрузке, а также при обращении к процедуре 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 ;Директивы компоновщику для подключения библиотек. ;Для компоновщика LINK.EXE. includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib ELSE ;TASM EXTERN MessageBoxA@: NEAR MessageBoxA@16 = MessageBoxA ;Для компоновщика TLINK32.EXE. 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] - Резервный параметр. ; [ЕВР+0СН] - Причина вызова. ; [ЕВР+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
Программа из примера 1может быть оттранслирована как с помощью MASM32, так и TASM32. На этом стоит остановиться более подробно. Прежде всего обратите внимание, что за процедурой, вызываемой из другого модуля, мы указали ключевое слово EXPORT. Это слово необходимо для правильной трансляции в MASM. Для TASM этого не нужно, но, к счастью, этот транслятор просто не замечает наличия какого-либо слова после PROC. Зато для TASM процедура DLLP1 должна быть определена как PUBLIC, кроме того, для трансляции в пакете TASM необходимо подготовить DEF-файл и указать его в командной строке TLINK32. Для создания DLL в строке link следует указать ключ /DLL, а в строке tlink32 -Tpd (по умолчанию работает ключ -Tре). Ключ /ENTRY :DLLENTRY в строке link можно опустить, так как точка входа определяется из директивы END DLLENTRY.
Трансляция динамической библиотеки из примера 1:
ML /с /coff /DMASM pr71_1.asm LINK /SUBSYSTEM:WINDOWS /DLL /ENTRY:DLLENTRY pr71_1.obj
TASM32 /ml pr71_1.asm TLINK32 -aa -Tpd pr71_1.obj,,,,pr71_1.def Содержимое файла pr71_1.def: EXPORTS DLLP1
В примере 2 представлена программа, которая загружает динамическую библиотеку, приведенную в примере 1. Это пример позднего связывания. Библиотека должна быть вначале загружена при помощи функции LoadLibrary. Затем определяется адрес процедуры с помощью функции GetProcAddress, после чего можно осуществлять вызов. Как и следовало ожидать, MASM помещает в динамическую библиотеку вместо DLLP1 имя _DLLP1@0, тогда как TASM помещает имя без искажения. Это мы учитываем в нашей программе. Мы учитываем также возможность ошибки при вызове функций LoadLibrary и GetProcAddress. В этой свяязи укажем, как (в какой последовательности) ищет библиотеку функция LoadLibrary.
В конце программы мы выгружаем из памяти DLL, что, кстати, могли бы и не делать, т.к. по выходе из программы эта процедура выполняется автоматически.
.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 ;Директивы компоновщику для подключения библиотек. ;Для компоновщика LINK.EXE. includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib ELSE ;TASM 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 ;Для компоновщика TLINK32.EXE. includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------ ;Сегмент данных. _DATA SEGMENT DWORD PUBLIC USE32 'DATA' TXT DB 'Ошибка динамической библиотеки',0 MS DB 'Сообщение',0 LIBR DB 'pr71_1.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
Результат работы приложения изображен на рисунке 1:
Рис.1. Результат работы приложения
Трансляция программы примера 2 ничем не отличается от трансляции обычных программ:
ML /с /coff /DMASM pr71_2.asm LINK /SUBSYSTEM:WINDOWS pr71_2.obj
TASM32 /ml pr71_2.asm TLINK32 -aa pr71_2.obj
На следующем шаге мы рассмотрим неявное связывание.