Динамические библиотеки
Теория:
Память будет быстро пожираться, если ваша программа достаточно велика. У Windows есть решение этой проблемы: динамические библиотеки (dynamic link libraries). Динамическая библиотека - это что-то вроде сборника общих функций. Windows не будет загружать несколько копий DLL в память; даже если одновременно выполняются несколько экземпляров вашей программы, будет только одна копия DLL в памяти. Здесь я должен остановиться и разъяснить чуть поподробнее. В реальности, у всех процессов, использующих одну и ту же dll есть своя копия этой библиотеки, однако Windows делает так, чтобы все процессы разделяли один и тот же код этой dll. Впрочем, секция данных копируется для каждого процесса.
Программа линкуется к DLL во время выполнения в отличии от того, как это осуществлялось в старых статических библиотеках. Вы также можете выгрузить DLL во время выполнения, если она вам больше не нужна. Если программа одна использует эту DLL, тогда та будет выгружена немедленно. Hо если ее еще используют какие-то другие программы, DLL останется в памяти, пока ее не выгрузит последняя из использующих ее программ.
Как бы то ни было, перед линкером стоит сложная задача, когда он проводит фиксирование адресов в конечном исполняемом файле. Так как он не может "извлечь" функции и вставить их в финальный исполняемый файл, он должен каким-то образом сохранить достаточно информации о DLL и используемых функциях в выходном файле, чтобы тот смог найти и загрузить верную DLL во время выполнения.
И тут в дело вступают библиотеки импорта. Библиотека импорта содержит информацию о DLL, которую она представляет. Линкер может получить из нее необходимую информацию и вставить ее в исполняемый файл.
Когда Windows загружает программу в память, она видит, что программа требует DLL, поэтому ищет библиотеку и мэппирует ту в адресное пространство процесса и выполняет фиксацию адресов для вызовов функций в DLL.
Вы можете загрузить DLL самостоятельно, не полагаясь на Windows-загрузчик.
В этом случае вам не потребуется библиотека импорта, поэтому вы сможете загружать и использовать любую DLL, даже если к ней не прилагается библиотеки импорта. Тем не менее, вам все pавно нужно знать какие функции находятся внутри нее, сколько параметров они принимают и тому подобную информацию.
Когда вы поручаете Windows загружать DLL, если та отсутствует, Windows выдаст сообщение "Тpебуемый .DLL-файл, xxxxx.dll отсутствует" и все! Ваша программ не может сделать ничего, что изменить это, даже если ваша dll не является необходимой. Если же вы будете загружать DLL самостоятельно и библиотека не будет найдена, ваша программа может выдать пользователю сообщение, уведомляющее об этом, и продолжить работу.
Вы можете вызывать *недокументированные* функции, которые не включены в библиотеки импорта, главное, чтобы у вас было достаточно информации об этих функциях.
Если вы используете LoadLibrary, вам придется вызывать GetprocAddress для каждой функции, которую вы заходите вызвать. GetprocAddress получает адрес входной точки функции в определенной DLL. Поэтому ваш код будет чуть-чуть больше и медленнее, но не намного.
Теперь, рассмотрев преимущества и недостатки использования LoadLibrary, мы подробно рассмотрим как создать DLL.
Следующий код является каркасом DLL.
;----------------------------------------------------------------------------
; DLLSkeleton.asm
;----------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
* * *
ret
DllEntry Endp
;----------------------------------------------------------------------------
; Это функция-пустышка - она ничего не делает, но она показывает
; как вставляют функции в DLL.
;----------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp
End DllEntry
;----------------------------------------------------------------------------
; DLLSkeleton.def
;----------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction
Вышеприведенная программа - это каркас DLL. Каждая DLL должна иметь стартовую функцию. Windows вызывает эту функцию каждый pаз, когда: DLL загружена в первый раз DLL выгружена Создается тред в том же процессе Тред разрушен в том же процессе
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
Вы можете назвать стартовую функцию как пожелаете, главное чтобы был END <имя_стартовой_функции>. Эта функция получает три параметра, только первые два из них важны.
hInstDLL - это хэндл модуля DLL. Это не тоже самое, что хэндл процесса. Вам следует сохранить это значение, так как оно понадобится вам позже. Вы не сможете ее получить в дальнейшем легко.
reason может иметь одно из следующих четырех значений:
DLL_PROCESS_ATTACH - DLL получает это значение, когда впервые загружается в адресное пространство процесса. Вы можете использовать эту возможность для того, чтобы осуществить инициализацию.
DLL_PROCESS_DETACK - DLL получает это значение, когда выгружается из адресного пространства процесса. Вы можете использовать эту возможность для того, чтобы "почистить" за собой: освободить память и так далее.
DLL_THREAD_ATTACK - DLL получает это значение, когда процесс создает новую ветвь.
DLL_THREAD_DETACK - DLL получает это значение, когда ветвь в процессе уничтожена.
Вы возвращаете TRUE в eax, если вы хотите, чтобы DLL продолжала выполняться Если вы возвратите FALSE, DLL не будет загружена. Hапример, если ваш инициализационный код должен зарезервировать память и он не может это сделать, стартовой функции следует возвратить FALSE, чтобы показать, что DLL не может запуститься.
Вы можете поместить ваши функции в DLL следом за стартовой функцией или до нее. Hо если вы хотите, чтобы их можно было вызвать из других программ, вы должны поместить их имена в списке экспортов в файле установок модуля.
DLL требуется данный файл на стадии разработки. Мы сейчас посмотрим, что это такое.
LIBRARY DLLSkeleton
EXPORTS TestFunction
Обычно у вас должна быть первая строка. Ключевое слово LIBRARY определяет внутреннее имя модуля DLL. Желательно, чтобы оно совпадало с именем файла.
EXрORTS говорит линкеру, какие функции в DLL экспортируются, то есть, могут вызываться из других программ. В прилагающемся примере нам нужно, чтобы другие модули могли вызывать TestFunction, поэтому мы указываем здесь ее имя.
Другое отличие заключается в параметрах, передаваемых линкеру. Вы должны указать /DLL и /DEF:<имя вашего def-файла>.
Link /DLL /SUBSYSTEM:WINDOWS/DEF:DLLSkeleton.def /LIBpATH:c:\masm32\lib DLLSkeleton.obj
Параметры ассемблера те же самые, обычно /c /coff /Cp. После компиляции вы получите .dll и .lib. Последний файл - это библиотека импорта, которую вы можете использовать, чтобы прилинковать к другим программам функции из соответствующей .dll.
Далее я покажу вам как использовать LoadLibrary, чтобы загрузить DLL.
;----------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0
.data?
hLib dd ? ; хэндл библиотеки (DLL)
TestHelloAddr dd ? ; адрес функции TestHello
.code
start:
invoke LoadLibrary,addr LibName
;-------------------------------------------------------------------------------
; Вызываем LoadLibrary и передаем имя желаемой DLL. Если вызов проходит успешно,
; будет возвращен хэндл библиотеки (DLL). Если нет, то будет возвращен NULL.
; Вы можете передать хэндл библиотеки функции GetрrocAddress или любой другой
; функции, которая требует его в качестве одного из параметров.
;-------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetprocAddress,hLib,addr FunctionName
;------------------------------------------------------------------------------
; Когда вы получаете хэндл библиотеки, вы передаете его GetрrocAddress вместе
; с именем функции в этой dll, которую вы хотите вызвать. Она возвратит адрес
; функции, если вызов пройдет успешно. В противном случае, она возвратит NULL.
; Адреса функций не изменятся, пока вы не перезагрузите библиотеку. Поэтому
; их можно поместить в глобальные переменные для будущего использования.
;------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;----------------------------------------------------------------------------
; Затем мы вызываем функцию с помощью call и переменной, содержащей адрес
; функции в качестве операнда.
;----------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;------------------------------------------------------------------------------
; Когда вам больше не требуется библиотека, выгрузте ее с помощью FreeLibrary.
;------------------------------------------------------------------------------
.endif
invoke Exitprocess,NULL
end start
Как вы можете видеть, использование LoadLibrary чуть сложнее, но гораздо гибче.