Управление загрузкой DLL
Windows вызывает функцию DllMain из DLL в четырех случаях:
- Когда процесс присоединяет DLL
- Когда поток присоединяет DLL
- Когда процесс отсоединяет DLL
- Когда нить отсоединяет DLL
Каждая DLL имеет точку входа. Эта точка входа реализована как функция обратного вызова. Она вызывается системой, когда событие происходит. Функция имеет имя по умолчанию DllMain. DLL, которая не должна быть загружена по определению, может просто возвращать False в функции DllMain() как только она обнаруживает, кто вызывает ее.
В Delphi, DLLProc используется для определения процедуры, которая вызывается каждый раз, когда вызывается точка входа библиотеки DLL. Процедура назначенная на DLLProc получает один параметр целое число (Reason).
Функция API GetModuleFileName() возвращает название вызывающего модуля, если Вы передаете 0 как его первый аргумент. Этот параметр - дескриптор модуля, имя которого Вы хотите знать.
Когда параметр Reason - DLL_PROCESS_ATTACH, устанавливая ExitCode в ненулевое значение, заставляет точку входа возвращать False.
Вот пример кода, который позволяет только MyCallingApp.exe загружать библиотеку (Примечание: у DLL в этом примере нет НИКАКОГО кода кроме процедуры DLLMain):
library OnlyMyDLL; uses SysUtils, Windows; procedure DllMain(reason: integer) ; var buf : array[0..MAX_PATH] of char; loader : string; begin case reason of DLL_PROCESS_ATTACH: begin GetModuleFileName(0, buf, SizeOf(buf)) ; loader := buf; if Pos('MyCallingApp.exe', loader) > 0 then ExitCode := -1 end; DLL_PROCESS_DETACH begin // DLL выгружается... end; end; end; (*DllMain*) begin DllProc := @DllMain; DllProc(DLL_PROCESS_ATTACH) ; // другой код для этой DLL... end.
Пару слов об использовании DllMain: Дело в том, что DllMain вызывается в действительно уникальный момент времени. К этому времени загрузчик ОС нашёл и спроецировал файл с диска, но (по обстоятельствам) в некотором смысле ваш модуль может быть ещё не "полностью рождён". Тут дело тонкое.
В двух словах, когда вызывается DllMain, загрузчик ОС находится в весьма подвешенном состоянии. Во-первых, он держит блокировку на свои внутренние структуры для защиты их от повреждения, а во-вторых, некоторые из ваших зависимостей могут быть ещё не разрешены (т.е. не загружены все DLL, которые требуются вашему модулю). Перед тем, как загрузить модуль, загрузчик ОС просматривает его статические зависимости. Если они также требуют дополнительные зависимости, то он просматривает и их тоже. В результате загрузчик получает последовательность, в которой он будет вызывать DllMain-ы этих модулей, для которых их нужно вызывать. Загрузчик довольно сообразителен и чаще всего, если вы не следуете правилам, описанным в MSDN, то вам это сойдёт с рук - но не всегда (*).
Суть в том, что порядок загрузки вам неизвестен, но что более важно, что порядок основывается только на информации из статического импорта. Если в вашей DllMain происходит динамическая загрузка во время DLL_PROCESS_ATTACH, и вы делаете вызов в чужой модуль, то все ставки уже сделаны. Нет никаких гарантий, что DllMain этого модуля будет вызвана до того, как вы получите через GetProcAddress функцию в этом модуле и вызовите её. Результаты будут полностью непредсказуемыми - например, глобальные переменные остаются не инициализированными. Наиболее вероятно, что вы получите AV.
Другой сценарий: вы создаёте новый поток в DLL_THREAD_ATTACH и ожидаете, пока он закончит инициализацию с помощью какой-либо техники инициализации. Такие действия держат ваш поток в DllMain пока загрузчик ОС держит свою блокировку. В сумме это может привести к deadlock-у.
В целом: если что-то - что угодно - пойдёт не так в DllMain хотя бы одного модуля, то весь процесс обречён.
Комментарии