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

} Region;

};

} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY, *PPROCESS_HEAP_ENTRY;

Прежде чем перечислять блоки в куче, присвойте NULL элементу lpData, и это заставит функцию HeapWalk инициализировать все элементы структуры Чтобы пе рейти к следующему блоку, вызовите HeapWalk еще раз, переддв сй тот же описатель кучи и адрес той же структуры PROCESS_HFAPENTRY Если HeapWalk вернет FALSE, значит, блоков в куче больше нет Подробное описание элементов структуры PRO CESS_HEAP_ENTRY см. в документации PlatformSDK

Обычно вызовы функции HeapWalk "обрамляют" вызовами HeapLock и HeapUnlock, чтобы посторонние потоки не портили картину, создавая или удаляя блоки в просматриваемой куче

ЧАСТЬ IV ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫЕ БИБЛИОТЕКИ

ГЛАВА 19 DLL: основы

Динамически подключаемые библиотеки (dynamic-link libraries, DLL) — краеугольный камень операционной системы Windows, начиная с самой первой ec версии. В DLL содержатся все функции Windows API. Три самые важные DLL: Kernel32.dll (управление памятью, процессами и потоками), User32.dll (поддержка пользовательского интерфейса, в том числе функции, связанные с созданием окон и передачей сообщений) и GDI32.dll (графика и вывод текста).

ВWindows есть и другие DLL, функции которых предназначены для более специализированных задач. Например, в AdvAPI32.dll содержатся функции для защиты объектов, работы с реестром и регистрации событий, в ComDlg32.dll ~ стандартные диалоговые окна (вроде File Open и File Save), a ComCrl32 dll поддерживает стандартные элементы управления.

Вэтой главе я расскажу, как создавать DLL-модули в Ваших приложениях. Вот лишь некоторые из причин, по которым нужно применять DLL:

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

Возможность использования разных языков программирования. У Вас есть выбор, на каком языке писать ту или иную часть приложения Так, пользовательский интерфейс приложения Вы скорее всего будете создавать на

Microsoft Visual Basic, но прикладную логику лучше всего реализовать на С++. Программа на Visual Basic может загружать DLL, написанные на С++, Коболе, Фортране и др.

Более простое управление проектом. Если в процессе разработки программного продукта отдельные его модули создаются разными группами, то при использовании DLL таким проектом управлять гораздо проще Однако конечная версия приложения должна включать как можно меньше файлов (Знал я одну компанию, которая поставляла свой продукт с сотней DLL. Их приложение запускалось ужасающе долго — перед началом работы ему приходилось открывать сотню файлов на диске.)

Экономия памяти. Если одну и ту же DLL использует несколько приложений, в оперативной памяти может храниться только один ее экземпляр, доступный этим приложениям. Пример — DLL-версия библиотеки С/С++ Ею пользуются многие приложения. Если всех их скомпоновать со статически подключаемой версией этой библиотеки, то код таких функций, как sprintf, strcpy, malloc и др., будет многократно дублироваться в памяти Но ссли они компонуются с DLL-версией библиотеки С/С++, в памяти будет присутствовать лишь одна копия кода этих функций, что позволит гораздо эффективнее использовать оперативную память.

Разделение ресурсов. DLL могут содержать такие ресурсы, как шаблоны диалоговых окон, строки, значки и битовые карты (растровые изображения). Эти ресурсы доступны любым программам

Упрощение локализации. DI,L нередко применяются для локализации приложений. Например, приложение, содержащее только код без всяких компонентов пользовательского интерфейса, может загружать DLL с компонентами локализованного интерфейса

Решение проблем, связанных с особенностями различных платформ. В разных версиях Windows содержатся разные наборы функций. Зачастую разработчикам нужны новые функции, существующие в той версии системы, которой они пользуются. Если Ваша версия Windows не поддерживает эти функции, Вам не удастся запустить такое приложение: загрузчик попросту откажется его запускать. Но если эти функции будут находиться в отдельной DLL, Вы загрузите программу даже в более ранних версиях Windows, хотя воспользоваться ими Вы все равно не сможете.

• Реализация специфических возможностей. Определенная функциональность в

Windows доступна только при использовании DLL Например, отдельные виды ловушек (устанавливаемых вызовом SetWindowsHookEx и SetWinEventHook можно задействовать при том условии, что функция уведомления ловушки размещена в DLL. Кроме того, расширение функциональности оболочки Windows возможно лишь за счет создания СОМ-объектов, существование которых допустимо только в DLL. Это же относится и к загружаемым Web-браузером ActiveX-элементам, позволяющим создавать Web-страницы с болсс богатой функциональностью.

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

Зачастую создать DLL проще, чем приложение, потому что она является лишь набором автономных функций, пригодных для использования любой программой, причем в DLL обычно нет кода, предназначенного для обработки циклов выборки сообщений или создания окон DLL представляет собой набор модулей исходного кода, в каждом из которых содержится определенное число функций, вызываемых приложением (исполняемым файлом) или другими DLL. Файлы с исходным кодом компилируются и компонуются так же, как и при создании ЕХЕ-файла Но, создавая DLL, Вы должны указывать компоновщику ключ /DLL. Тогда компоновщик записывает в конечный файл

информацию, по которой загрузчик операционной системы определяет, что данный файл

— DLL, а не приложение

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

Как только DLL спроецирована на адресное пространство вызывающего процесса, ее функции доступны всем потокам этого процесса Фактически библиотеки при этом теряют почти всю индивидуальность: для потоков код и данные DLL — просто дополнительные код и данные, оказавшиеся в адресном пространстве процесса. Когда поток вызывает из DLL какую-то функцию, та считывает свои параметры из стека

потока и размещает в этом стеке собственные локальные переменные Кроме того, любые созданные кодом DLL объекты принадлежат вызывающему потоку или процессу — DLL ничем пе владеет,

Например, если DLL-функция вызывает VirtualAlloc, резервируется регион в адресном пространстве того процесса, которому принадлежит поток, обратившийся к DLLфункции Если DLL будет выгружена из адресного пространства процесса, зарезервированный регион не освободится, так как система не фиксирует того, что регион зарезервирован DLL-функцисй Считается, что он принадлежит процессу и поэтому освободится, только если поток этого процесса вызовет VirtualFree или завершится ам процесс.

Вы уже знаете, что глобальные и статические переменные ЕХЕ-файла пе разделяются его параллельно выполняемыми экземплярами. В Windows 98 это достигается за счет выделения специальной области памяти для таких переменных при проецировании ЕХЕфайла на адресное пространство процесса, а в Windows 2000 — с помощью механизма копирования при записи, рассмотренного в главе 13 Глобальные и статические переменные DLL обрабатываются точно так же. Когда какой-то процесс проецирует образ DLL-файла на свое адресное пространство, система создает также экземпляры глобальных и статических переменных.

NOTE:

Важно понимать, что единое адресное пространство состоит из одного исполняемого модуля и нескольких DLL-модулей. Одни из них могут быть скомпонованы со статически подключаемой библиотекой С/С++, другие — с DLLверсией той же библиотеки, а третьи (написанные нс на С/С++) вообще ею не пользуются Многие разработчики допускают ошибку, забывая, что в одном адресном пространстве может одновременно находиться несколько библиотек С/С++. Взгляните на этот код:

VOID EXEFunc()

{

PVOID pv = DLLFunc();

//обращаемся к памяти, на которую указывает pv;

//предполагаем, что pv находится в С/С++-куче ЕХЕ-файла

free(pv);

}

PVOID DLLFunc()

{

// выделяем блок в С/С++-куче DLL return(malloo(100));

}

Ну и что Вы думаете? Будет ли этот код правильно работать? Освободит ли ЕХЕфункция блок, выделенный DLL-функцией? Ответы на все вопросы одинаковыможет быть Для точных ответов информации слишком мало. Если оба модуля (EXE и DLL) скомпонованы с DLL-версией библиотеки С/С++, код будет работать совершенно нормально. По ссли хотя бы один из модулей связан со статической библиотекой С/С++, вызов free окажется неудачным. Я нс раз видел, как разработчики обжигались на подобном коде.

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

VOID EXEFunc()

{

PVOID pv = DLLFunc();

// обращаемся к памяти, на которую указывает pv, // не делаем никаких предположений по поводу С/С++-кучи

DLLFreeFunc(pv);

}

PVOID DllLFunc()

{

// выделяем блок в С/С++-кую DLL PVOID pv = malloc(100); return(pv);

}

BOOL DLLFreeFunc(PVOID pv)

{

// освобождаем блок, выделенный в С/С++- куче OLL

return(free(pv));

}

Этот код будет работать при любых обстоятельствах Создавая свой модуль, не забывайте, что функции других модулей могут быть написаны па других языках, а значит, и ничего нс знать о malloc и free. Не стройте свой код на подобных допущениях. Кстати, то же относится и к С++-опсраторам new и delete, реализованным с использованием malloc frее

Общая картина

Попробуем разобраться в том, как работают DLL и как опи используются Вами и системой Начнем с общей картины (рис 19-1).

Для пачала рассмотрим неявное связывание EXE- и DLL-модулей. Неявное связывание (implicit linking) — самый распространенный на сегодняшний день метод (Windows поддерживает и явное связывание, но об этом — в главе 20.)

Как видно на рис 19-1, когда некий модуль (например, EXE) обращается к функциям и переменным, находящимся в DLL, в этом процессе участвует несколько файлов и компонентов. Для упрощения будем считать, что исполняемый модуль (EXE) импортирует функции и переменные из DLL, а DLL-модули, наоборот, экспортируют их в исполняемый модуль. Но учтите, что DLL может (и это не редкость) импортировать функции и переменные из других DLL

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

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

2.Вы пишете на С/С++ модуль (или модули) исходного кода с телами функций и определениями переменных, которые должны находиться в DLL. Так как эти модули исходного кода не нужны для сборки исполняемого модуля, они могут остаться коммерческой тайной компании-разработчика.

3.Компилятор преобразует исходный код модулей DLL в OBJ-файлы (по одному на каждый модуль).

4.Компоновщик собирает все OBJ-модули в единый загрузочный DLL-модуль, в который в конечном итоге помещаются двоичный код и переменные (глобальные и статические), относящиеся к данной DLL Этот файл потребуется при компиляции исполняемого модуля

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

Создав DLL, можно перейти к сборке исполняемого модуля.

6.Во все модули исходного кода, где есть ссылки на внешние функции, переменные, структуры данных или идентификаторы, надо включить заголовочный файл, предоставленный разработчиком DLL.

СОЗДАНИЕ DLL

СОЗДАНИЕ ЕХЕ

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

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

прототипами, структурами и

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

идентификаторами (символьными

Исходные файлы С/С++, из которых вызываются

именами) 2) Исходные файлы С/С++ в

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

которых реализованы функции и

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

определены переменные 3) Компилятор

исходного файла С/С++ 9) Используя OBJ

создаэт OBJ-файл из каждого исходного

модули и LIB-файл и учитывая ссылки на

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

импортируемые идентификаторы компоновщик

DLL из OBJ-модулей 5) Если DLL экспортирует хотя бы одну переменную или функцию, компоновщик создает и

LIB-файл

собирает ЕХЕ-модуль (в котором также размещается таблица импорта — список необходимых DLL и импортируемых идентификаторов)

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

1.Вы пишете на С/С++ модуль (или модули) исходного кода с телами функций и определениями переменных, которые должны находиться в ЕХЕ-файле. Естественно, ничто не мешает Вам ссылаться на функции и псрсмснные, определенные в заголовочном файле DLL-модуля

2.Компилятор преобразует исходный код модулей EXE в OBJ-файлы (по одному на каждый модуль).

3.Компоновщик собирает все OBJ-модули в единый загрузочный ЕХЕ-модуль, в который в конечном итоге помещаются двоичный код и переменные (глобальные и статические), относящиеся к данному EXE. В нем также создается раздел импорта, где перечисляются имена всех необходимых DLL-модулей (информацию о разделах см в главе 17) Кроме того, для каждой DLL в этом разделе указывается, па какие символьные имена функций и переменных ссылается двоичный код