Шаг 71.
Создание динамически подключаемых библиотек

    На этом шаге мы рассмотрим алгоритм создания и использования DLL.

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


    Пример 1. Простейшая DLL-библиотека.
.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:

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

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

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


    Пример 2. Вызов динамической библиотеки. Явное связывание.
 .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 ничем не отличается от трансляции обычных программ:

    На следующем шаге мы рассмотрим неявное связывание.




Предыдущий шаг Содержание Следующий шаг