Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
375
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

Заметьте, тип параметра pszSymbolName — PCSTR, а не PCTSTR. Это значит, что функция GetProcAddress принимает только ANSI-строки — ей нельзя передать Unicodeстроку А причина в том, что идентификаторы функций и переменных в разделе экспорта DLL всегда хранятся как ANSI-строки.

Вторая форма пapaмeтpa pszSymbolName позволяет указывать порядковый номер нужной функции.

FARPROC pfn = GetProcAddress(hinstDll, MAKEINTRESOURCE(2));

Здесь подразумевается, что Вам известен порядковый номер (2) искомого идентификатора, присвоенный ему автором данной DLL И вновь повторю, что Microsoft

настоятельно не рекомендует пользоваться порядковыми номерами; поэтому Вы редко встретите второй вариант вызова GetProсAddress.

При любом способе Вы получаете адрес содержащегося в DLL идентификатора. Если идентификатор не найден, GetProcAddress возвращает NULL.

Учтите, что первый способ медленнее, так как системе приходится проводить поиск и сравнение строк. При втором способе, если Вы передаете порядковый номер, не присвоенный ни одной из экспортируемых функций, GetProcAddress может вернуть значение, отличное от NULL В итоге Ваша программа, ничео не подозревая, получит неправильный адрес. Попытка вызова функции по этому адресу почти наверняка приведет к нарушению доступа Я и сам — когда только начинал программировать под Windows и не очень четко понимал эти вещи — несколько раз попадал в эту ловушку. Так что будьте внимательны. (Вот Вам, кстати, и еще одна причина, почему от использования порядковых номеров следует отказаться в пользу символьных имен — идентификаторов.)

Функция входа/выхода

В DI,T, может быть лишь одна функция входа/выхода Система вызывает ее в некоторых ситуациях (о чем речь еще впереди) сугубо в информационных целях, и обычно она используется ULL для инициализации и очистки ресурсов в конкретных процессах или потоках Если Вашей DLL подобные уведомления не нужны, Вы не обязаны реализовывать эту функцию. Пример — DLL, содержащая только ресурсы. Но если же уведомления необходимы, функция должна выглядеть так:

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad)

{

switch (fdwReason)

{

case DLL_PROCESS_ATTACH:

// DLL проецируется на адресное пространство процесса

break;

case DLL_THREAD_ATTACH:

// создается поток break;

case DLL_THREAD_DETACH:

// поток корректно завершается

break;

case DLL PROCESS_DETACH

// DLL отключается от адресного пространства процесса

break;

}

return(TRUE);

// используется только для DLL_PROCESS_ATTACH

}

NOTE:

При вызове DllMain надо учитывать регистр букв Многие случайно вызывают DLLMain, и это вполне объяснимотермин DLL обычно пишется заглавными буквами. Если Вы назовете функцию входа/выхода не DllMain, а как-то иначе . (пусть даже только один символ будет набран в другом регистре), компиляция и компоновка Вашего кода пройдет без проблем, но система проигнорирует такую функцию входа/выхода, и Ваша DLL никогда не будет инициализирована.

Параметр hinstDll содержит описатель экземпляра DLL, Как и hinstExe функции (w)WinMain, это значение — виртуальный адрес проекции файла DLL на адресное пространство процесса. Обычно последнее значение сохраняется в глобальной переменной, чтобы его можно было использовать и при вызовах функций, загружающих ресурсы (типа DialogBox или LoadString), Последний параметр, fImpLoad, отличен от 0, если DLL загружена неявно, и равен 0, если она загружена явно.

Параметр fdwReason сообщает о причине, по которой система вызвала эту функцию. Он принимает одно из четырех значений: DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH или DLL_THREAD_DETACH. Мы рассмотрим их в следующих разделах.

NOTE:

Не забывайте, что DLL инициализируют себя, используя функции DllMain. К моменту выполнения Вашей DllMain другие DLL в том же адресном пространстве могут не успеть выполнить свои функции DllMain, т. e. они окажутся неинициализированными. Поэтому Вы должны избегать обращений из DllMain к функциям, импортируемым из других DLL. Кроме того, не вызывайте из DllMain функции LoadLibrary(Ex) и FreeLibrary, так как это может привести к взаимной блокировке.

В документации Platform SDK утверждается, что DllMain должна выполнять лишь простые виды инициализации — настройку локальной памяти потока (см. главу 21), создание объектов ядра, открытие файлов и т. д. Избегайте обращений к функциям, связанным с User, Shell, ODBC, COM, RPC и сокетами (а также к функциям, которые их вызывают), потому что соответствующие DLL могут быть еще не инициализированы.

Кроме того, подобные функции могут вызывать LoadLibrary(Ex) и тем самым приводить к взаимной блокировке.

Аналогичные проблемы возможны и при создании глобальных или статических С++- объектов, поскольку их конструктор или деструктор вызывается в то же время, что и Ваша

DllMain.

Уведомление DLL_PROCESS_ATTACH

Система вызывает DllMain с этим значением параметра fdwReason сразу после того, как DLL спроецирована на адресное пространство процесса. А это происходит, только когда образ DLL-файла проецируется в первый раз. Если затем поток вызовет LoadLibrary(Ex) для уже спроецированной DLL, система просто увеличит счетчик числа пользователей этой DLL; так что DllMain вызывается со значением DLL_PROCESS_ATTACH лишь раз.

Обрабатывая DLL_PROCESS_ATTACH, библиотека должна выполнить в процессе инициализацию, необходимую ее функциям. Например, в DLL могут быть функции, которым нужна своя куча (создаваемая в адресном пространстве процесса). В этом случае DllMain могла бы создать такую кучу, вызвав HeapCreate при обработке уведомления DLL_PROCESS_ATTACH, а описатель созданной кучи сохранить в глобальной переменной, доступной функциям DLL

При обработке уведомления DLL_PROCESSATTACH значение, возвращаемое функцией DllMain, указывает, корректно ли прошла инициализация DLL. Например, если вызов HeapCreate закончился благополучно, следует вернуть TRUE. А если кучу создать не удалось — FALSE. Для любых других значений fdwReason — DLL_PROCESS_DETACH,

DLL_THREAD_ATTACH или DLL_THREAD_DETACH — значение, возвращаемое

DllMain, системой игнорируется.

Конечно, где-то в системе должен быть поток, отвечающий за выполнение кода DllMain. При создании нового процесса система выделяет для него адресное пространство, куда проецируется ЕХЕ-файл и всс необходимые ему DLL-модули Далее создается первичный поток процесса, используемый системой для вызова DllMain из каждой DLL со значением DLL_PROCESS_ATTACH. Когда все спроецированные DLL ответят на это уведомление, система заставит первичный поток процесса выполнить стартовый код из библиотеки С/С++, а потом — входную функцию ЕХЕ-файла (main, wmain, WinMain или wWinMain). Если DllMain хотя бы одной из DLL вернет FALSE, сообщая об ошибке при инициализации, система завершит процесс, удалив из его адресного пространства образы всех файлов, после этого пользователь увидит окно с сообщением о том, что процесс запустить не удалось. Ниже показаны соответствующие окна для Windows 2000 и

Windows 98,

Теперь посмотрим, что происходит при явной загрузке DLL Когда поток вызывает LoadLibrary(Ex), система отыскивает указанную DLL и проецирует ее на адресное иросчранство процесса Затем вызывает DllMain со значением DLL_PROCESS_ATTACH, используя поток, вызвавший LoadLibrary(Ex) Как только DllMain обработает унедомление, произойдет возврат из LoadLibrary(Ex), и поток продолжит работу в обычном режиме. Если DllMain вернет FALSE (неудачная инициализация), система автоматически отключит образ файла DLL от адресного пространства процесса, а вызов

LoadI.ibrary(Ex) даст NULL.

Уведомление DLL_PROCESS_DETACH

При отключении DLL от адресного пространства процесса вызывается ее функция DllMain со значением DLLPROCESS_DETACH в параметре fdwReason. Обрабатывая это значение, DLL должна провести очистку в данном процессе Например, вызвать HeapDestroy, чтобы разрушить кучу, созданную ею при обработке уведомления DLL__PROCESS_ATTACH. Oбpaтите внимание: если функция DllMain вернула FALSE, получив уведомление DLL_PROCESS_ATTACH, то ее нельзя вызывать с уведомлением DLL_PROCESS_DETACH. Если DLL отключается из-за завершения процесса, то за выполнение кода DllMain отвечает поток, вызвавший ExitProcess (обычно эчо первичный поток приложения). Когда Ваша входная функция возвращает управление стартовому коду из библиотеки С/С++, тот явно вызывает ExitProcess и завершает процесс

Если DLL отключается в результате вызова FreeLibrary или FreeLibraryAndExitThread, код DllMain выполняется потоком, вызвавшим одну из этих функций. D случае обращения к FreeLibrary управление не возвращается, пока DllMain не закончит обработку уведомления DLL_PROCESS_DETACH.

Учтите также, что DLL может помешать завершению процесса, если, например, ее DllMain входит в бесконечный цикл, получив уведомление DLL_PROCESS_DETACH Операционная система уничтожает процесс только после того, как все DLL-модули обработают уведомление DLL_PROCESS_DETACH

Рис. 20-2. Операции, выполняемые системой при вызове потоком функции LoadLibrary

Если процесс завершается в результате вызова TerminateProcess, система не вызывает DllMain со значением DLL_PROCESS_DETACH. А значит, ни одна DLL, спроецированная на адресное пространство процесса, не получит шанса на очистку до