DLL-библиотеки в операционной системе Windows
Формат DLL-библиотеки почти идентичен формату загрузочного модуля приложения Windows, однако вы не можете "запустить" DLL-библиотеку на выполнение, как обычное приложение. И это понятно, так как назначение DLL-библиотек другое - они служат хранилищем функций, вызываемых приложениями во время работы. Функции, расположенные в DLL-библиотеках, могут вызывать функции, которые находятся в других библиотеках (рис. 3.3).
Рис. 3.3. Вызов функций из DLL-библиотек
Для того чтобы вам были понятны отличия между приложением и DLL-библиотекой, уточним такие понятия, как задача (task ), копия приложения (instance ) и модуль (module ).
Когда вы запускаете несколько копий одного приложения, Windows загружает в оперативную память только одну копию кода и ресурсов. Для каждой копии приложения создается отдельный сегмент данных, стек и очередь сообщений (рис. 3.4).
Рис. 3.4. Копии приложения и модуль приложения
Задачу (task) можно рассматривать как совокупность виртуального процессора и ресурсов, таких как стек, регистры процессора, глобальная память, очередь сообщений и т. д. В операционной системе Windows имеется база данных запущенных задач, в которой хранится вся относящаяся к задаче информация, например, идентификатор приложения, идентификатор модуля приложения и т. д.
Копия приложения (instance) является контекстом, в котором выполняется модуль приложения. Идентификатор копии приложения, который вы получаете через параметр hInstance функции WinMain, является идентификатором сегмента данных DGROUP, используемого при выполнении программного модуля.
Модуль приложения (module) состоит из исполнимого кода и ресурсов. Внутри Windows есть база данных модулей, в которой хранится такая информация, как ссылки на экспортируемые функции, расположение сегментов кода и сегментов, содержащих ресурсы. Так как модуль приложения не изменяется (сегменты кода и ресурсов не изменяются), все запущенные копии одного приложения могут им пользоваться.
DLL-библиотека также является модулем.
Она находится в памяти в единственном экземпляре, содержит сегменты кода и ресурсы, а так же один сегмент данных (рис. 3.5). Можно сказать, что для DLL-библиотеки создается одна копия (instance), состоящая только из сегмента данных, и один модуль, состоящий из кода и ресурсов.
Рис. 3.5. Структура DLL-библиотеки в памяти
DLL-библиотека, в отличие от приложения, не имеет стека и очереди сообщения.
Функции, расположенные в модуле DLL-библиотеки, выполняются в контексте вызвавшей их задачи. При этом они пользуются стеком копии приложения, так как собственного стека в DLL-библиотеке не предусмотрено.
Тем не менее, функции, расположенные в DLL-библиотеке, пользуются сегментом данных, принадлежащей этой библиотеке, а не копии приложения. При этом возникают трудности, связанные с тем, что сегментный регистр SS, с помощью которого адресуется стек, указывает на стек копии приложения, а регистр DS - на сегмент данных DLL-библиотеки. В обычных приложениях Windows регистры SS и DS адресуют один и тот же автоматический сегмент данных. Позже мы займемся этим вопросом вплотную.
Для чего используются DLL-библиотеки ?
Прежде всего для экономии памяти, так как все запущенные приложения могут использовать один модуль DLL-библиотеки, не включая стандартные функции в состав своих модулей.
С помощью DLL-библиотек можно организовать коллективное использование ресурсов или данных, расположенных в сегменте данных библиотеки. Более того, вы можете создать DLL-библиотеки, состоящие только из одних ресурсов, например, из пиктограмм или изображений bitmap. В состав Windows входит DLL-библиотека moricons.dll, состоящая из одних пиктограмм. Файлы с расширением fon представляют собой ни что иное, как DLL-библиотеки, содержащие шрифты в виде ресурса.
Функции, входящие в состав DLL-библиотеки, могут заказывать блоки памяти с атрибутом GMEM_SHARE. Такой блок памяти не принадлежит ни одному приложению и поэтому не освобождается автоматически при завершении работы приложения. Так как в Windows версии 3.1 все приложения используют общую глобальную память, блоки памяти с атрибутом GMEM_SHARE можно использовать для обмена данными между приложениями.
Управлять таким обменом могут, например, функции, расположенные в соответствующей DLL-библиотеке. Однако в следующих версиях Windows каждое приложение будет работать в собственном адресном пространстве, поэтому для организации обмена данных между приложениями следует использовать специальный механизм динамического обмена данными DDE, который мы рассмотрим позже в отдельной главе.
Использование DLL-библиотек повышает модульность приложений и самой операционной системы Windows. С точки зрения приложения DLL-библиотека является не более чем набором функций с тем или иным интерфейсом, а также, возможно, набором ресурсов. Внутреннее "устройство" и алгоритмы работы функций, а также используемые функциями структуры данных полностью скрыты от приложения. Поэтому при внесении изменений или усовершенствований в DLL-библиотеки нет необходимости выполнять повторную сборку приложений (если не изменился интерфейс или набор функций, входящих в библиотеку).
Область применения DLL-библиотек достаточно широка. Помимо предоставления приложениям функций организации пользовательского интерфейса и реализации различных расширений Windows типа мультимедиа или систем управления базами данных, DLL-библиотеки необходимы для обеспечения ряда системных операций.
Например, приложение может организовать "перехват" системных сообщений или функций, при этом соответствующие модули "перехватчика" необходимо располагать в фиксированном сегменте кода DLL-библиотеки. Для удаляемых (discardable) блоков памяти можно, вызвав функцию GlobalNotify, определить функцию, которой будет передаваться управление при попытке удалить блок из памяти. Такая функция должна находиться в фиксированном сегменте кода в DLL-библиотеке. Если ваше приложение обрабатывает аппаратные прерывания или само вызывает программные прерывания, ему также не обойтись без DLL-библиотек (единственный способ обработки прерываний в реальном времени - создание виртуального драйвера). Наконец, все обычные драйвера устройств в операционной системе Windows реализованы с помощью DLL-библиотек.
Даже если вы не собираетесь обрабатывать или вызывать прерывания и не разрабатываете собственный драйвер, отдельные подсистемы большого приложения имеет смысл оформлять в виде DLL-библиотек из соображений модульности и доступности библиотек для других приложений. Например, в приложении SMARTPAD мы создали орган управления Toolbar с использованием разработанного нами класса С++ Toolbar. Однако если бы мы сосредоточили все функции этого класса в DLL-библиотеке, нашим органом управления могли бы воспользоваться и другие созданные нами приложения.
Разумеется, у динамической компоновки есть и свои недостатки.
Во-первых, DLL-библиотеки сложнее в разработке по сравнению с обычными библиотеками статической компоновки. Приходится принимать во внимание неравенство содержимого регистров DS и SS во время выполнения функций, расположенных в DLL-библиотеке, а также то, что DLL-библиотека имеет единственный сегмент данных, общий для всех приложений, вызывающих функции из библиотеки.
Во-вторых, в дополнение к exe-файлу вместе с приложением необходимо устанавливать один или несколько dll-файлов, что в некоторой степени усложняет процесс установки и сопровождения приложения. Может также возникнуть ситуация, при которой приложение не находит свою DLL-библиотеку, несмотря на то, что нужная библиотека есть на диске.
В-третьих, без тщательного планирования состава функций, включаемых в DLL-библиотеку, экономии памяти может и не получиться. В частности, возможна такая ситуация, когда и приложение, и DLL-библиотека пользуются одними и теми же функциями стандартной библиотеки компилятора в варианте статической компоновки.