Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3 семестр, WinAPI, MFC.pdf
Скачиваний:
379
Добавлен:
15.06.2014
Размер:
6.17 Mб
Скачать

Чтобы определить, спроецирована ли DLL на адресное пространство процесса, поток может вызвать функцию GetModuleHandle.

HINSTANCE GetModuleHandle(LPCTSTR IpszModuleName);

Например, следующий код загружает MYLIB.DLL, только если она еще не спроецирована на адресное пространство процесса:

HINSTANCE hinstDll;

hinstDll = GetModuleHandle("MyLib"); // подразумевается расширение DLL if (hinstDll == NULL) {

hinstDll = LoadLibrary("MyLib"); // подразумевается расширение DLL

}

Если уже есть значение HINSTANCE для DLL, можно определить и полное имя

DLL (или ЕХЕ) с помощью GetModuleFileName:

DWORD GetModuleFileName(HINSTANCE hinstModule, LPTSTR IpszPath, DWORD cchPath);

Первый параметр функции — это значение HINSTANCE для ЕХЕ или DLL. Второй задает адрес буфера, в который она запишет полное имя образа файла. Третий, и последний, параметр (cchPath) определяет размер буфера в символах.

Уменьшить счетчик числа пользователей DLL можно и с помощью другой Win32функции:

VOID FreeLibraryAndExitThread(HINSTANCE hinstDll, DWORD dwExitCode);

Она реализована в KERNEL32.DLL так:

VOID FreeLibraryAndExitThread(HINSTANCE hinstDll, DWORD dwExitCode) { FreeLibrary(hinstDll);

ExitTh read (dwExitCode);

}

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

У Win32 DLL может быть одна функция входа/выхода, но она необязательна.

Она используется DLL для инициализации и очистки в конкретных процессах или потоках. Эта функция в DLL должна выглядеть так:

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason,

LPVOID flmpLoad) { 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

}

Операционная система вызывает функцию входа/выхода в различных ситуациях.

При этом параметр hinstDll должен содержать описатель экземпляра DLL. Как и hinstExe функции WinMain, это значение — виртуальный адрес, по которому файл DLL проецируется на адресное пространство процесса.

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

DLL_PROCESS_DETACH, DLL_THREAD_ATTACH или DLL_THREAD_DETACH.

DLL_PROCESS_ATTACH

Система вызывает DllMain с этим значением параметра fdwReason сразу после того, как DLL спроецирована на адресное пространство процесса. А это происходит, только когда образ DLL-файла проецируется в первый раз.

Обрабатывая DLL_PROCESS_ATTACH, библиотека должна выполнить в процессе инициализацию, необходимую ее функциям.

При обработке уведомления DLL_PROCESS_ATTACH значение, возвращаемое функцией DllMain, указывает, корректно ли прошла инициализация DLL.

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

DLL_PROCESS_DETACH

При отключении DLL от адресного пространства процесса вызывается ее функция

DllMain с передачей в параметре fdwReason значения DLL_PROCESS_DETACH.

Обрабатывая это значение, DLL должна провести очистку в данном процессе.

Например, вызвать HeapDestroy чтобы разрушить кучу, созданную ею при обработке уведомления DLL_PROCESS_ATTACH

Если DllMain вызвана со значением DLL_PROCESS_ATTACH и возвращает в результате FALSE, сообщая о неудачной инициализации, система впоследствии все равно вызовет эту функцию со значением DLL_PROCESS_DETACH. Взгляните для примера на показанную ниже функцию DllMain и попробуйте определить, где может возникнуть нарушение доступа (защиты памяти):

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason,

LPVOID

flmpLoad)

 

{

static PVOID pvData = NULL;

 

 

BOOL fOk = TRUE; // пока предполагаем, что все идет хорошо

 

switch (fdwReason) {

 

 

case DLL_PROCESS_ATTACH:

 

 

pvData = HeapAlloc(GetProcessHeap(), 0, 1000);

 

if (pvData == NULL)

 

 

fOk = FALSE;

 

 

break;

 

 

case DLL_PROCESS_DETACH:

 

 

HeapFree(GetProcessHeap(),

0, pvData);

 

break;

 

 

}

 

 

return(fOk); // используется только для DLL_PROCESS_ATTACH

}

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

В приведенном фрагменте, если DllMain при обработке DLL_PROCESS_-АТТАСН возвращает FALSE, система вызывает ее потом со значением

DLL_PROCESS_DETACH.

Чтобы исправить ошибку, перепишем обработку DLL_PROCESS_DETACH

case DLL_PROCESS_DETACH: if (pvData != NULL)

HeapFree(GetProcessHeap(), 0, pvData); break;

Если DLL отключается по причине завершения процесса, то за исполнение кода DHMain отвечает поток, вызвавший ExitProcess, — обычно первичный поток приложения. Когда Ваша функция WinMain возвращает управление стартовому коду из стандартной библиотеки С, тот явно вызывает ExitProcess и завершает процесс.

Если DLL отключается в результате вызова FreeLibrary, код DHMain исполняется потоком, вызвавшим FreeLibrary. Управление от FreeLibrary не передается до завершения DHMain.

Если процесс завершается в результате вызова TerminateProcess, система не вы-

зывает DllMain со значением DLL_PROCESS_DETACH. Значит, ни одна из спроецированных на адресное пространство процесса DLL-библиотек не получит шанса на очистку до завершения процесса.

DLL_THREAD_ATTACH

Когда в процессе создается новый поток, система просматривает все DLL, спроецированные в данный момент на адресное пространство этого процесса, и в каждой из таких DLL вызывает DllMain со значением DLL_THREAD_ATTACH.

Если в момент проецирования DLL на адресное пространство процесса в нем исполняется несколько потоков, система не вызывает DllMain со значением DLL_THREAD_ATТАСН ни для одного из существующих потоков.

Вызов DllMain с этим значением осуществляется, только если DLL проецируется на адресное пространство процесса в момент создания потока.

Обратите также внимание, что система не вызывает функции DllMain со значением DLL_THREAD_ATTACH и для первичного потока процесса. Любая DLL, проецируемая на адресное пространство процесса в момент его создания, получает уведомление

DLL_PROCESS_ATTACH, но не DLL_THREAD_ATTACH.

DLL_THREAD_DETACH

Завершая поток вызовом ExitThread, система просматривает все образы DLLфайлов, спроецированные в данный момент на адресное пространство процесса, и в каждой из этих DLL вызывает DllMain со значением DLL_THREAD_DETACH.

Тем самым она уведомляет DLL-модули о необходимости очистки, связанной с данным потоком. Например, DLL-версия стандартной библиотеки С освобождает блок данных, используемый для управления многопоточными приложениями.

Если поток завершается из-за того, что другой поток вызвал для него TerminateThread, система не вызывает DllMain со значением DLL_THREAD_DETACH. Значит, ни одна из спроецированных на адресное пространство процесса DLL не получит шанса на выполнение очистки до завершения потока, что может привести к потере данных. Поэтому TerminateThread следует использовать лишь в самом крайнем случае!

Если при отключении DLL еще исполняются какие-то потоки, то для них DllMain не вызывается со значением DLL_THREAD_DETACH.