logo
Методичка по курс_Windows

3 Динамически подключаемые библиотеки.

Динамически подключаемые библиотеки (dynamic-link libraries, DLL) — краеугольный камень операционной системы Windows, начиная с самой первой ее версии. Использование динамических библиотек - это способ осуществления модульности в период выполнения программы. Динамическая библиотека (Dynamic Link Library - DLL) позволяет упростить и саму разработку программного обеспечения. Вместо того чтобы каждый раз перекомпилировать огромные ЕХЕ-программы, достаточно перекомпилировать лишь отдельный динамический модуль. Кроме того, доступ к динамической библиотеке возможен сразу из нескольких исполняемых модулей, что делает многозадачность более гибкой. В DLL содержатся все функции Windows API. Три самые важные DLL: Kernel32.dll (управление памятью, процессами и потоками), User32.dll (поддержка пользовательского интерфейса, в том числе функции, связанные с созданием окон и передачей сообщений) и GDI32.dll (графика и вывод текста).

В Windows есть и другие DLL, функции которых предназначены для более специализированных задач. Например, в AdvAPI32.dll содержатся функции для защиты объектов, работы с реестром и регистрации событий, в ComDlg32.dll ~ стандартные диалоговые окна (вроде File Open и File Save), a ComCrl32 dll поддерживает стандартные элементы управления.

Вот некоторые из причин, по которым стоит применять DLL:

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

• Возможность использования разных языков программирования. У Вас есть выбор, на каком языке писать ту или иную часть приложения. Так, пользовательский интерфейс приложения Вы скорее всего будете создавать на Microsoft Visual Basic, но прикладную логику лучше всего реализовать на С++. Программа на Visual Basic может загружать DLL, написанные на С++, Коболе, Фортране и др.

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

• Экономия памяти. Если одну и ту же DLL использует несколько приложений, в оперативной памяти может храниться только один ее экземпляр, доступный этим приложениям. Пример — DLL-версия библиотеки C/C++. Ею пользуются многие приложения. Если всех их скомпоновать со статически подключаемой версией этой библиотеки, то код таких функций, как sprintf, strcpy, malloc и др., будет многократно дублироваться в памяти. Но если они компонуются с DLL-версией библиотеки C/C++, в памяти будет присутствовать лишь одна копия кода этих функций, что позволит гораздо эффективнее использовать оперативную память.

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

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

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

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

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

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

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

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

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

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

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 - сообщает, что некий поток, созданный данным процессом, в адресное пространство которого загружена данная динамическая библиотека, уничтожается.

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

Программа на рис. 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. 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.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 ничем не отличается от трансляции обычных программ.

MASM32.

ml /c /coff /DMASM dllex.asm

link /subsystem:windows dllex.obj

TASM32.

tasm32 /ml dllex.asm

tlink32 -aa dllex.obj