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

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

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

Глава 18. Динамически распределяемая память.docx 613

Имейте в виду, что описатель стандартной кучи процесса тоже включается в этот массив описателей, возвращаемый функцией GetProcessHeaps. Целостность кучи позволяет проверить функция HeapValidate:

BOOL HeapValidate(

HANDLE hHeap,

DWORD fdwFlags,

LPCVOID pvMem);

Обычно ее вызывают, передавая в hHeap описатель кучи, в fdwFlags — 0 (этот параметр допускает еще флаг HEAP_NO_SERIALIZE), а в pvMem — NULL. Функция просматривает все блоки в куче, чтобы убедиться в отсутствии поврежденных блоков. Чтобы она работала быстрее, в параметре pvMem можно передать адрес конкретного блока. Тогда функция проверит только этот блок.

Для объединения свободных блоков в куче, а также для возврата системе любых страниц памяти, на которых нет выделенных блоков, предназначена функция

HeapCompact:

UINT HeapCompact( HANDLE hHeap, DWORD fdwFlags);

Обычно в параметре fdwFhgs передают 0, но можно передать и

HEAP_NO_SERIALIZE.

Следующие две функции — HeapLock и HeapUnbck — используются парно:

B00L HeapLock(HANDLE hHeap);

B00L HeapUnlock(HANDLE hHeap);

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

Функции HeapAlloc, HeapSize, HeapFree и другие — все обращаются к HeapLock и HeapUnlock, чтобы обеспечить последовательный доступ к куче. Самостоятельно вызывать эти функции вам вряд ли понадобится.

Последняя функция, предназначенная для работы с кучами, — HeapWalk:

B00L HeapWalk(

HANDLE hHeap,

PPROCESS_HEAP_ENTRY pHeapEntry);

Она предназначена только для отладки и позволяет просматривать содержимое кучи. Обычно ее вызывают по несколько раз, передавая адрес структуры PROCESS_HEAP__ENTRY (Вы должны сами создать ее экземпляр и инициализировать):

614 Часть III. Управление памятью

typedef struct _PROCESS_HEAP_ENTRY { PVOID lpData;

DWORD cbData; BYTE cbOverhead; BYTE iRegionIndex; WORD wFlags; union {

struct {

HANDLE hMem;

DWORD dwReserved[ 3 ];

}Block; struct {

DWORD dwCommittedSize; DWORD dwUnCommittedSize; LPVOID lpFirstBlock; LPVOID lpLastBlock;

}Region;

};

} PROCESS_HEAP_ENTRY, * LPPROCESS_HEAP_ENTRY, *PPROCESS_HEAP_ENTRY;

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

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

Ч А С Т Ь I V

ДИНАМИЧЕСКИ

ПОДКЛЮЧАЕМЫЕ

БИБЛИОТЕКИ

Оглавление

 

Г Л А В А 1 9 DLL: основы .....................................................................................................

616

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

617

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

619

Создание DLL-модуля ......................................................................................................

622

Создание ЕХЕ-модуля......................................................................................................

628

Выполнение ЕХЕ-модуля................................................................................................

632

Г Л А В А 1 9

DLL: основы

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

ипередачей сообщений) и GDI32.dll (графика и вывод текста).

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

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

Расширение функциональности приложения. DLL можно загружать в ад-

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

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

Экономия памяти. Если одну и ту же DLL использует несколько приложений, в оперативной памяти может храниться только один ее экземп-

Глава 19. DLL - основы.docx 617

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

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

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

Решение проблем, связанных с особенностями различных платформ. В

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

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

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

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

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

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

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

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

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

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

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

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

VOID EXEFunc() {

PVOID pv = DLLFunc();

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

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

}

Глава 19. DLL - основы.docx 619

PVOID DLLFunc() {

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

}

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

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

VOID EXEFunc() {

PVOID pv = DLLFunc();

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

//не делаем никаких предположений по поводу С/С++-кучи

DLLFreeFunc(pv);

}

PVOID DLLFunc() {

// выделяем блок в С/С++-куче DLL

PVOID pv = malloc(100); return(pv);

}

BOOL DLLFreeFunc(PVOID pv) {

// освобождаем блок, выделенный в C/C++-куче DLL return(free(pv));

}

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

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

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

Для начала рассмотрим неявное связывание ЕХЕ- и DLL-модулей. Неявное связывание (implicit linking) — самый распространенный на сего-

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