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

возможностей, и в этой главе Вас ждет целый "букет" новых методов, относящихся к программированию DLL. Во многих приложениях эти методы скорее всего не понадобятся, тем не менее они очень полезны, и познакомиться с ними стоит Я бы посоветовал, как минимум, прочесть разделы "Модификация базовых адресов модулей" и "Связывание модулей", подходы, изложенные в них, помогут существенно повысить быстродействие всей системы.

Явная загрузка DLL и связывание идентификаторов

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

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

На рис 20-1 показано, как приложение явно загружает DLL и связывается с ней

Явная загрузка DLL

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

HINSTANCE LoadLibrary{PCTSTR pszDLLPathName);

HINSTANCE LoadLibraryEx( PCTSTR pszDLLPathName, HANDLE hFile, DWORD dwFlags);

Обе функции ищут образ DLL-файла (в каталогах, список которых приведен в предыдущей главе) и пытаются спроецировать его на адресное пространство вызывающего процесса. Значение типа HINSTANCE, возвращаемое этими функциями, со-

общает адрес виртуальной памяти, но которому спроецирован образ файла. Если спроецировать DLL на адресное пространство процесса не удалось, функции возвращают NULL Дополнительную информацию об ошибке можно получить вызовом GetLastError

Очевидно, Вы обратили внимание на два дополнительных параметра функции LoadLibraryEx, hFile и dwFlags Первый зарезервирован для использования в будущих версиях и должен быть NULL Bo втором можно передать либо 0, либо комбинацию флагов DONT_RESOLVE_DLL_REFERENCES, LOAD_LIBRARY_AS_DATAFILE и LOAD_WITH_ ALTERED_SEARCH_PATH, о которых мы сейчас и поговорим.

 

 

 

СОЗДАНИЕ DLL

 

СОЗДАНИЕ ЕХЕ

1 ) Заголовочный файл с экспортируемыми

 

6) Заголовочный файл с импортируемыми

прототипами структурами и идентификаторами

 

прототипами, структурами и идентификаторами 7)

 

 

 

 

 

 

(символьными именами) 2) Исходные файлы

 

Исходные файлы С/С++ в которых нет ссылок на

С/С++ в которых реализованы экспортируемые

 

импортируемые функции и переменные 8)

функции и определены переменные 3)

 

Компилятор создает OBJ файл из каждого

Компилятор создает OBJ-файл из каждого

 

исходного файла С/С++ 9) Компоновщик собирает

исходного файла С/С++ 4) Компоновщик

 

ЕХЕ-модуль из OBJ-модулей (LIB файл DLL не

собирает DLL из OBJ модулей 5) Если DLL

 

нужен, так как нет прямых ссылок на

экспортирует хотя бы одну переменную или

 

экспортируемые идентификаторы, раздел импорта в

функцию компоновщик создает и LIB-файл

 

ЕХЕ-модуле отсутствует)

{при явном связывании этот файл не

 

 

используется)

 

 

 

 

 

Рис. 20-1. Так DLL создается и явно связывается с приложением

DONT_RESOLVE__DLL_REFERENCES

Этот флаг укапывает системе спроецировать DLL на адресное пространство вызывающего процесса. Проецируя DLL, система обычно вызывает из нее специальную функцию DllMain (о ней — чуть позже) и с ее помощью инициализирует библиотеку. Так вот, данный флаг заставляет систему проецировать DLL, не обращаясь к DllMain.

Кроме того, DLL может импортировать функции из других DLL При загрузке библиотеки система проверяет, использует ли она другие DLL; если да, то загружает и их При установке флага DONT_RESOLVE_DLL_REFERENCES дополнительные DLL

автоматически не загружаются.

LOAD_LIBRARY_AS_DATAFILE

Этот флаг очень похож на предыдущий. DLL проецируется на адресное пространство процесса так, будто это файл данных. При этом система не тратит дополнительное время на подготовку к выполнению какого-либо кода из данного файла Например, когда DLL проецируется на адресное пространство, система считывает информацию из DLL-файла и на ее основе определяет, какие атрибуты защиты страниц следует присвоить разным частям файла. Если флаг LOAD_LIBRARY_AS_DATAFILE нс указан, атрибуты защиты устанавливаются такими, чтобы код из данного файла можно было выполнять.

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

LOAD_LIBRARY_AS_DATAFILE.

LOAD_WITH_ALTERED_SEARCH_PATH

Этот флаг изменяет алгоритм, используемый LoadLibraryEx при поиске DLL-файла. Обычно поиск осуществляется так, как я рассказывал в главе 19 Однако, если данный флаг установлен, функция ищет файл, просматривая каталоги в таком порядке

1.Каталог, заданный в napaмeтре pszDLLPathName.

2.Текущий каталог процесса.

3.Системный каталог Windows.

4.Основной каталог Windows.

5.Каталоги, перечисленные в переменной окружения PATH

Явная выгрузка DLL

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

BOOL FreeLibrary(HINSTANCE hinstDll);

Вы должны передать в FreeLibrary значение типа HINSTANCE, которое идентифицирует выгружаемую DLL. Это значение Вы получаете после вызова LoadLibrary(Ex).

DLL можно выгрузить и с помощью другой функции:

VOID FreeLibraryAndExitThread( HlNSTANCE hinstDll, DWORD dwExitCode);

Она реализована в Kernel32.dll так:

VOID FreeLibraryAndExitThread(HINSTANCE hinstDll, DWORD dwExitCode)

{

FreeLibrary(hinstDll);

ExitThread(dwExitCode);

}

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

вызывая сначала FreeLibrary, а потом ExttThread.

Если поток станет сам вызывать FreeLibrary и ExitThread, возникнет очень серьезная проблема: FreeI.ibrary тут же отключит DLL от адресного пространства процесса. После возврата из FreeLibrary код, содержащий вызов ExttThread, окажется недоступен, и поток попытается выполнить не известно что. Это приведет к нарушению доступа и завершению всего процесса!

С другой стороны, если поток обратится к FreeLibraryAndExitThread, она вызовет FreeLibrary, и та сразу же отключит DLL, Но следующая исполняемая инструкция находится в KerneI32.dlI, а нс в только что отключенной DLL. Значит, поток сможет продолжить выполнение и вызвать ExitThread, которая корректно завершит его, не возвращая управления.

Впрочем, FreeLibraryAndExitThread может и не понадобиться. Мне она пригодилась лишь раз, когда я занимался весьма нетипичной задачей. Да и код я писал под Windows NT 3-1, где этой функции не было. Наверное, поэтому я так обрадовался, обнаружив ее в более новых версиях Windows.

На самом деле LoadLibrary и LoadLibraryEx лишь увеличивают счетчик числа пользователей указанной библиотеки, a FreeLibrary и FreeLibraryAndExitThread его уменьшают Так, при первом вызове LoadLibrary дум загрузки DLL система проецирует образ DLL-файла иа адресное пространство вызывающего процесса и присваивает единицу счетчику числа пользователей этой DLL Если поток того же процесса вызывает LoadLibrary для той же DLL еще раз, DLL больше не проецируется; система просто увеличивает счетчик числа ее пользователей — вот и все.

Чтобы выгрузить DLL из адресного пространства процесса, FreeLibrary придется теперь вызывать дважды: первый вызов уменьшит счетчик до 1, второй — до 0. Обнаружив, что счетчик числа пользователей DLL обнулен, система отключит ее. После этого попытка вызова какой-либо функции из данной DLL приведет к нарушению доступа, так как код по указанному адресу уже не отображается на адресное пространство процесса.

Система поддерживает в каждом процессе свой счетчик DLL, т. e. если поток процесса А вызывает приведенную ниже функцию, а затем тот же вызов делает поток в процессе В, то