Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6639
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

658 Часть IV. Динамически подключаемые библиотеки

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

Чтобы DLL отложенной загрузки можно было выгружать, вы должны сделать две вещи. Во-первых, при сборке исполняемого файла задать ключ /Delay: unload компоновщика. А во-вторых, немного изменить исходный код и поместить в точке выгрузки DLL вызов функции __FUnloadDelayLoadedDLL2:

BOOL __FUnloadDelayLoadedDLL2(PCSTR szDll);

Ключ /Delay:unload заставляет компоновщик создать в файле дополнительный раздел. В нем хранится информация, необходимая для сброса уже вызывавшихся DLL-функций, чтобы к ним снова можно было обратиться через

__delayLoadHelper. Вызывая __FUnloadDelayLoadedDLL2, вы передаете имя вы-

гружаемой DLL. После этого она просматривает раздел выгрузки (unload section) и сбрасывает адреса всех DLL-функций. И, наконец, __FUnloadDelayLoadedDLL2 вызывает FreeLibrary, чтобы выгрузить эту DLL.

Обратите внимание на несколько важных моментов. Во-первых, ни при каких условиях не вызывайте сами FreeLibrary для выгрузки DLL, иначе сброса адреса DLL-функции не произойдет, и впоследствии любое обращение к ней приведет к нарушению доступа. Во-вторых, при вызове __FUnloadDelayLoadedDLL2 в имени DLL нельзя указывать путь, а регистры всех букв должны быть точно такими же, как и при передаче компоновщику в ключе /DelayLoad; в ином случае вызов __FUnloadDelayLoadedDLL2 закончится неудачно. В-третьих, если вы вообще не собираетесь выгружать DLL отложенной загрузки, не задавайте ключ /Delay:unload — тогда вы уменьшите размер своего исполняемого файла. И, наконец, если вы вызовете __FUnloadDelayLoadedDLL2 из модуля, собранного без ключа /Delay:unload, ничего страшного не случится: __FUnloadDelayLoadedDLL2

проигнорирует вызов и просто вернет FALSE.

Другая особенность DLL отложенной загрузки в том, что вызываемые вами функции по умолчанию связываются с адресами памяти, по которым они, как считает система, будут находиться в адресном пространстве процесса. (О связывании мы поговорим чуть позже.) Поскольку связываемые разделы DLL отложенной загрузки увеличивают размер исполняемого файла, вы можете запретить их создание, указав ключ /Delay:nobind компоновщика. Однако связывание, как правило, предпочтительно, поэтому при сборке большинства приложений этот ключ использовать не следует.

И последняя особенность DLL отложенной загрузки. Она, кстати, наглядно демонстрирует характерное для Майкрософт внимание к деталям. Функция __delayLoadHelper2 может вызывать предоставленные вами функ- ции-ловушки (hook functions), и они будут получать уведомления о том, как

Глава 20. DLL - более сложные методы программирования.docx 659

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

Чтобы получать уведомления или изменить поведение __delauLoadHelper2, нужно внести два изменения в спой исходный код. Во-первых, вы должны написать функцию-ловушку по образу и подобию DliHook, код которой показан на рис. 20-6. Моя функция DliHook не влияет на характер работы

__delayLoadHelper2. Если вы хотите изменить поведение __delayLoadHelper2,

начните с DliHook и модифицируйте ее код так, как вам требуется. Потом передайте ее адрес функции __delayLoadHelper2.

В статически подключаемой библиотеке DelayImp.lib определены две гло-

бальные переменные типа PfnDliHook: __pfnDliNotifyHook2 и __pfnDliFailureHook2:

typedef FARPR0C (WINAPI *PfnDliHook)( unsigned dliNotify, PDelayLoadInfo pdli);

Как видите, это тип данных, соответствующий функции, и он совпадает с прототипом моей DliHook. В DelayImp.lib эти две переменные инициализируются значением NULL, которое сообщает __delayLoadHelper2, что никаких функцийловушек вызывать не требуется. Чтобы ваша функция-ловушка все же вызывалась, вы должны присвоить ее адрес одной из этих переменных. В своей программе я просто добавил на глобальном уровне две строки:

PfnDliHook _pfnDliNotifyHook2 = DliHook;

PfnDliHook _pfnDliFailureHook2 = DliHook;

Так что __delayLoadHelper фактически работает с двумя функциями обратного вызова: одна вызывается для уведомлений, другая—для сообщений об ошибках. Поскольку их прототипы идентичны, а первый параметр, dliNotify, сообщает о причине вызова функции, я всегда упрощаю себе жизнь, создавая одну функцию и настраивая на нее обе переменные.

Совет. Утилита Dependency Walker (www.DependencyWalker.com) генери-

рует список зависимостей периода компоновки (статические, а также связанные с отложенной загрузкой DKK, а также отслеживает вызовы LoadLibrary/GetProcAddress в период выполнения.

Программа-пример DelayLoadApp

Эта программа (20-DelayLoadApp.exe) показывает, как использовать все преимущества DLL отложенной загрузки. Для демонстрации нам понадобится небольшой DLL-файл; он находится в каталоге 20-DelayLoadLib внутри архива, доступного на Web-сайте поддержки этой книги.

660 Часть IV. Динамически подключаемые библиотеки

Так как программа загружает модуль «20 DelayLoadLib» с задержкой, загрузчик не проецирует его на адресное пространство процесса при запуске. Периодически вызывая функцию IsModuleLoaded, программа выводит окно, которое информирует, загружен ли модуль в адресное пространство процесса. При первом запуске модуль «20 DelayLoadLib» не загружается, о чем и сообщается в окне

(рис. 20-4).

Рис. 20-4. DelayLoadApp сообщает, что модуль «20 DelayLoadLib» незагружен

Далее программа вызывает функцию, импортируемую из DLL, и это заставляет __delayLoadHelper автоматически загрузить нужную DLL. Когда функция вернет управление, программа выведет окно, показанное на рис. 20-5.

Рис. 20-5. DelayLoadApp сообщает, что модуль «20 DelayLoadLib» загружен

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

Далее вызывается __FUnloadDelayLoadedDLL2, и модуль «20 DelayLoadLib» выгружается из памяти. После очередного вызова IsModuleLoaded на экране появляется окно, показанное на рис. 20-4. Наконец, вновь вызывается импортируемая функция, что приводит к повторной загрузке модуля «20 DelayLoadLib», а IsModuleLoaded открывает окно, как на рис. 20-5.

Если все нормально, то программа будет работать, как я только что рассказал. Однако, если перед запуском программы вы удалите модуль «20 DelayLoadLib» или если в этом модуле не окажется одной из импортируемых функций, будет возбуждено исключение. Из моего кода видно, как корректно выйти из такой ситуации.

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

Глава 20. DLL - более сложные методы программирования.docx 661

DelayLoadApp.cpp

/****************************************************************************** Module: DelayLoadApp.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <Windowsx.h>

#include <tchar.h> #include <StrSafe.h>

///////////////////////////////////////////////////////////////////////////////

#include

<Delayimp.h> // For error handling & advanced features

#include

"..\20-DelayLoadLib\DelayLoadLib.h"

// My DLL function prototypes

 

 

 

///////////////////////////////////////////////////////////////////////////////

//Statically link __delayLoadHelper2/__FUnloadDelayLoadedDLL2 #pragma comment(lib, "Delayimp.lib")

//Note: it is not possible to use #pragma comment(linker, "")

//for /DELAYLOAD and /DELAY

//The name of the Delay-Load module (only used by this sample app) TCHAR g_szDelayLoadModuleName[] = TEXT("20-DelayLoadLib");

///////////////////////////////////////////////////////////////////////////////

// Forward function prototype

LONG WINAPI DelayLoadDllExceptionFilter(PEXCEPTION_POINTERS pep);

///////////////////////////////////////////////////////////////////////////////

void IsModuleLoaded(PCTSTR pszModuleName) {

HMODULE hmod = GetModuleHandle(pszModuleName); char sz[100];

#ifdef UNICODE

StringCchPrintfA(sz, _countof(sz), "Module \"%S\" is %Sloaded.", pszModuleName, (hmod == NULL) ? L"not " : L"");

#else

662 Часть IV. Динамически подключаемые библиотеки

StringCchPrintfA(sz, _countof(sz), "Module \"%s\" is %sloaded.", pszModuleName, (hmod == NULL) ? "not " : "");

#endif

chMB(sz);

}

///////////////////////////////////////////////////////////////////////////////

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) {

// Wrap all calls to delay-load DLL functions inside SEH __try {

int x = 0;

//If you're in the debugger, try the new Debug.Modules menu item to

//see that the DLL is not loaded prior to executing the line below IsModuleLoaded(g_szDelayLoadModuleName);

x = fnLib(); // Attempt to call delay-load function

// Use Debug.Modules to see that the DLL is now loaded IsModuleLoaded(g_szDelayLoadModuleName);

x = fnLib2(); // Attempt to call delay-load function

//Unload the delay-loaded DLL

//NOTE: Name must exactly match /DelayLoad:(DllName)

PCSTR pszDll = "20-DelayLoadLib.dll";

__FUnloadDelayLoadedDLL2(pszDll);

// Use Debug.Modules to see that the DLL is now unloaded IsModuleLoaded(g_szDelayLoadModuleName);

x = fnLib(); // Attempt to call delay-load function

// Use Debug.Modules to see that the DLL is loaded again IsModuleLoaded(g_szDelayLoadModuleName);

}

__except (DelayLoadDllExceptionFilter(GetExceptionInformation())) { // Nothing to do in here, thread continues to run normally

}

Глава 20. DLL - более сложные методы программирования.docx 663

// More code can go here...

return(0);

}

///////////////////////////////////////////////////////////////////////////////

LONG WINAPI DelayLoadDllExceptionFilter(PEXCEPTION_POINTERS pep) {

// Assume we recognize this exception

LONG lDisposition = EXCEPTION_EXECUTE_HANDLER;

//If this is a Delay-load problem, ExceptionInformation[0] points

//to a DelayLoadInfo structure that has detailed error info PDelayLoadInfo pdli =

PDelayLoadInfo(pep->ExceptionRecord->ExceptionInformation[0]);

//Create a buffer where we construct error messages

char sz[500] = { 0 };

switch (pep->ExceptionRecord->ExceptionCode) {

case VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND): // The DLL module was not found at runtime

StringCchPrintfA(sz, _countof(sz), "Dll not found: %s", pdli->szDll); break;

case VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND):

// The DLL module was found, but it doesn't contain the function if (pdli->dlp.fImportByName) {

StringCchPrintfA(sz, _countof(sz), "Function %s was not found in %s", pdli->dlp.szProcName, pdli->szDll);

} else {

StringCchPrintfA(sz, _countof(sz), "Function ordinal %d was not found

in %s",

pdli->dlp.dwOrdinal, pdli->szDll);

}

break;

default:

// We don't recognize this exception lDisposition = EXCEPTION_CONTINUE_SEARCH; break;

}

664 Часть IV. Динамически подключаемые библиотеки

if (lDisposition == EXCEPTION_EXECUTE_HANDLER) {

// We recognized this error and constructed a message, show it chMB(sz);

}

return(lDisposition);

}

///////////////////////////////////////////////////////////////////////////////

// Skeleton DliHook function that does nothing interesting FARPROC WINAPI DliHook(unsigned dliNotify, PDelayLoadInfo pdli) {

FARPROC fp = NULL; // Default return value

//NOTE: The members of the DelayLoadInfo structure pointed

//to by pdli shows the results of progress made so far.

switch (dliNotify) { case dliStartProcessing:

//Called when __delayLoadHelper2 attempts to find a DLL/function

//Return 0 to have normal behavior or nonzero to override

//everything (you will still get dliNoteEndProcessing)

break;

case dliNotePreLoadLibrary:

//Called just before LoadLibrary

//Return NULL to have __delayLoadHelper2 call LoadLibary

//or you can call LoadLibrary yourself and return the HMODULE fp = (FARPROC) (HMODULE) NULL;

break;

case dliFailLoadLib:

//Called if LoadLibrary fails

//Again, you can call LoadLibary yourself here and return an HMODULE

//If you return NULL, __delayLoadHelper2 raises the

//ERROR_MOD_NOT_FOUND exception

fp = (FARPROC) (HMODULE) NULL; break;

case dliNotePreGetProcAddress:

//Called just before GetProcAddress

//Return NULL to have __delayLoadHelper2 call GetProcAddress,

//or you can call GetProcAddress yourself and return the address fp = (FARPROC) NULL;

break;

Глава 20. DLL - более сложные методы программирования.docx 665

case dliFailGetProc:

//Called if GetProcAddress fails

//You can call GetProcAddress yourself here and return an address

//If you return NULL, __delayLoadHelper2 raises the

//ERROR_PROC_NOT_FOUND exception

fp = (FARPROC) NULL; break;

case dliNoteEndProcessing:

//A simple notification that __delayLoadHelper2 is done

//You can examine the members of the DelayLoadInfo structure

//pointed to by pdli and raise an exception if you desire break;

}

return(fp);

}

///////////////////////////////////////////////////////////////////////////////

// Tell __delayLoadHelper2 to call my hook function PfnDliHook __pfnDliNotifyHook2 = DliHook; PfnDliHook __pfnDliFailureHook2 = DliHook;

//////////////////////////////// End of File //////////////////////////////////

DelayLoadLib.cpp

/****************************************************************************** Module: DelayLoadLib.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#include "..\CommonFiles\CmnHdr.h" /* See Appendix A. */ #include <Windowsx.h>

#include <tchar.h>

///////////////////////////////////////////////////////////////////////////////

#define DELAYLOADLIBAPI extern "C" __declspec(dllexport) #include "DelayLoadLib.h"

///////////////////////////////////////////////////////////////////////////////

666 Часть IV. Динамически подключаемые библиотеки

int fnLib() {

return(321);

}

///////////////////////////////////////////////////////////////////////////////

int fnLib2() {

return(123);

}

//////////////////////////////// End of File //////////////////////////////////

DelayLoadLib.h

/****************************************************************************** Module: DelayLoadLib.h

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

******************************************************************************/

#ifndef DELAYLOADLIBAPI

#define DELAYLOADLIBAPI extern "C" __declspec(dllimport) #endif

///////////////////////////////////////////////////////////////////////////////

DELAYLOADLIBAPI int fnLib();

DELAYLOADLIBAPI int fnLib2();

//////////////////////////////// End of File //////////////////////////////////

Переадресация вызовов функций

Запись о переадресации вызова функции (function forwarder) — это строка в разделе экспорта DLL, которая перенаправляет вызов к другой функции, находящейся в другой DLL. Например, запустив утилиту DumpBin из Visual C++ для Kernel32.dll в Windows Vista, вы среди прочей информации увидите и следующее.

C:\Windows\System32>DumpBin - Exports Kernel32.dll

(часть вывода опущена)

7549 CloseThreadpoolIo (forwarded to NTDLL.TpReleaseIoCoropletion)

764A CloseThreadpoolTimer (forwarded to NTDLL.TpReleaseTimer)

774B CloseThreadpoolWait (forwarded to NTDLL.TpReleaseWait)

784C CloseThreadpoolWork (forwarded to NTDLL.TpReleaseWork)

(остальное тоже опущено)

Глава 20. DLL - более сложные методы программирования.docx 667

Здесь есть четыре переадресованные функции. Всякий раз, когда ваше прило-

жение вызывает CloseThreadpoolIo, CloseThreadpoolTimer, CloseThreadpoolWait, or CloseThreadpoolWork, его EXE-модуль динамически связывается с Kernel32.dll. При запуске EXE-модуля загрузчик загружает Kernel32.dll и, обнаружив, что переадресуемые функции на самом деле находятся в NTDLL. dll, загружает и эту DLL. Обращаясь к CloseThreadpoolIo, программа фактически вызывает функцию

TpReleaseIoCompletion из NTDLL.dll. А функции CloseThreadpoolIo вообще нет!

При вызове CloseThreadpoolIo (см. ниже) функция GetProcAddress просмотрит раздел экспорта Kernel32.dll и, выяснив, что CloseThreadpoolIo — переадресуемая функция, рекурсивно вызовет сама себя для поиска RtlAllocateHeap в разделе экс-

порта NTDLL.dll.

Вы тоже можете применять переадресацию вызовов функций в своих DLL. Самый простой способ — воспользоваться директивой pragma:

// переадресация к функции из DllWork

#pragma comment(linker, “/export:SomeFunc=DllWork.SomeOtherFunc”)

Эта директива сообщает компоновщику, что DLL должна экспортировать функцию SomeFunc, которая на самом деле реализована как функция SomeOtherFunc в модуле DllWork.dll. Такая запись нужна для каждой переадресуемой функции.

Известные DLL

Некоторые DLL, поставляемые с операционной системой, обрабатываются поособому. Они называются известными DLL (known DLLs) и ведут себя точно так же, как и любые другие DLL с тем исключением, что система всегда ищет их в одном и том же каталоге. В реестре есть раздел:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\

Session Manager\KnownDLLs

Содержимое этого раздела может выглядеть примерно так, как показано ниже (при просмотре реестра с помощью утилиты RegEdit.exe). Как видите, здесь содержится набор параметров, имена которых совпадают с именами известных DLL Значения этих параметров представляют собой строки, идентичные именам параметров, но дополненные расширением .dll. (Впрочем, это не всегда так, и вы сами убедитесь в этом на следующем примере.) Когда вы вызываете LoadLibrary или LoadLibraryEx, каждая из них сначала проверяет, указано ли имя DLL вместе с расширением .dll. Если нет, поиск DLL ведется по обычным правилам.

Соседние файлы в предмете Программирование на C++