DESCRIPTION ‘description text for module’
Следующая директива, если и присутствует, то всегда определяет, что данный модуль предназначен для работы в среде Windows (аналогичные файлы описания модулей могут применяться для OS/2).
EXETYPEWINDOWS
Еще две директивы предназначены для задания размеров стека и кучи. Задание размера стека менее 5K приводит к тому, что Windows сам увеличивает его до 5K. Задание размера кучи вообще абстрактно — главное, что бы не 0, так как Windows будет увеличивать кучу при необходимости (вплоть до наибольшего размера сегмента данных приложения — 64K). Размер кучи 0 говорит о том, что она просто не используется..
HEAPSIZE size
STACKSIZE size
Очень любопытная директива — STUB. О ней надо рассказать чуть подробнее. Ранее было отмечено, что для Windows–приложений был разработан собственный формат выполняемого модуля. Очевидно, что попытка запустить такой модуль на старой версии DOS, который не рассчитан на такую возможность, приведет к грубой ошибке, вплоть до зависания компьютера или порчи данных. Чтобы этого избежать, сделали так — Windows–приложение состоит из двух частей:
· Первая часть представляет собой небольшое приложение MS–DOS, называемую заглушкой (stub). Обычно это приложение просто пишет на экране фразу типа “ThisprogrammustberununderMicrosoftWindows.”. Заголовок этой заглушки чуть изменен, чтобы Windows мог отличить DOS–приложение от Windows–приложения, но это изменение находится в неиспользуемой MS–DOS’ом части заголовка.
STUB‘stubexe.exe’
Здесь stubexe.exe — имя приложения–заглушки (возможно полное имя, вместе с путем)
· Вторая часть — собственно код и данные Windows–приложения с собственным заголовком.
Разрабатывая собственную заглушку можно делать интересные приложения, работающие в DOS и в Windows. Скажем, собрать в один выполняемый файл DOS–версию приложения и Windows–версию.
Еще три директивы используются для описания параметров сегментов кода и данных. Директива CODE задает характеристики сегментов кода, DATA — сегментов данных, а SEGMENTS позволяет описать характеристики для конкретного сегмента (в квадратные скобки [] заключены необязательные параметры, знак | разделяет возможные варианты; запись [FIXED|MOVEABLE] означает, что может быть указано либо FIXED, либо MOVEABLE, либо ничего). Более подробно о характеристиках сегментов приложения см. в описании диспетчера памяти Windows 3.x.
CODE [FIXED|MOVEABLE] [DISCARDABLE] [PRELOAD|LOADONCALL]
DATA [NONE|SINGLE|MULTIPLE] [FIXED|MOVEABLE]
SEGMENTS
segname [CLASS ‘clsname’] [minalloc] [FIXED|MOVEABLE] [DISCARDABLE] [SHARED|NONSHARED] [PRELOAD|LOADONCALL]
...
Могут быть указаны следующие параметры: FIXED или MOVEABLE — сегмент фиксирован в памяти или перемещаемый; если сегмент перемещаемый, то он может быть теряемым (DISCARDABLE). Параметры PRELOAD и LOADONCALL указывают, когда сегмент должен быть загружен в оперативную память — при загрузке приложения (PRELOAD) или при непосредственном обращении к нему (LOADONCALL). Параметры NONE, SINGLE или MULTIPLE применяются только для сегментов данных. NONE или SINGLE применяется для динамически загружаемых библиотек; NONE — библиотека не имеет сегмента данных вообще, SINGLE — сегмент данных присутствует в памяти в единственном экземпляре (динамические библиотеки загружаются только один раз, других копий не существует, все приложения ссылаются на одну библиотеку с единым сегментом данных). MULTIPLE — сегмент данных загружается для каждой копии приложения, применяется только для приложений.
Директива SEGMENTS описывает характеристики конкретных сегментов; segname — имя сегмента, clsname — имя класса сегмента, minalloc — минимальный размер сегмента. SHARED или NONSHARED сообщает, является ли сегмент разделяемым разными копиями приложения, либо нет. После одной директивы SEGMENTS может размещаться описание нескольких сегментов, по одному на каждой строке.
Еще две часто используемых директивы — EXPORTS и IMPORTS. Они задают списки экспортированных и импортированных имен функций, описание каждой функции находится на своей строке. В обоих случаях надо учитывать следующую особенность — в Windows различают внешние и внутренние имена. Внутренние имена — это имена, использованные при разработке исходного текста программы, те которые содержаться в тексте C–программы (или в объектном файле). Внешние имена — имена используемые при экспорте/импорте, доступные другим модулям. Внешнее и внутреннее имя могут не совпадать, поэтому предусмотрена возможность задавать оба имени.
EXPORTS
exportname [=internalname] [@id] [RESIDENTNAME] [NODATA] [argsize]
...
В разделе EXPORTS перечисляются имена функций, экспортируемых данным модулем. Параметр exportname задает внешнее имя функции, под которым она будет доступна другим модулям, параметр internalname используется, если внешнее и внутреннее имена различаются, @id задает идентификатор функции, argsize — если указан, сообщает сколько слов в стеке занимает список аргументов функции. Параметры RESIDENTNAME и NODATA используются крайне редко; RESIDENTNAME говорит о том, что имя функции должно размещаться в так называемом резидентном списке имен (который находиться в памяти постоянно после загрузки модуля), обычно имена размещаются в нерезидентном списке, который загружается при необходимости. NODATA говорит о том, что функция использует сегмент данных вызывающего модуля, а не экспортирующего (подробнее — при разговоре о диспетчере памяти).
IMPORTS
[internalname=] modulename.id
[internalname=] modulename.importname
Раздел IMPORTS задает соответствие внутренних имен импортируемых функций (internalname) функциям, экспортированным другими модулями. Возможно два метода связывания имен — по идентификатору (первый пример), modulename — имя экспортирующего модуля, id — идентификатор; либо по именам (второй пример), importname — внешнее имя функции, под которым она была экспортирована другим модулем.
Файл описания модуля в Win32 API используется для задания нестандартных характеристик динамически загружаемых библиотек. Полное описание файлов описания модуля для Win32 API надо смотреть в документации, сопровождающей применяемый компилятор.
Так как файл описания модуля используется для создания DLL, то первая директива — LIBRARY. Часто применяется также директива EXPORTS для задания идентификаторов экспортируемых функций (обе — см. в описании файла описания модуля для Windows API).
Для задания нестандартных параметров отдельных сегментов используется директива SECTIONS, заменяющая прежнюю директиву SEGMENTS. Синтаксис директивы SECTIONS несколько иной, хотя допускается замена слова SECTIONS на SEGMENTS:
SECTIONS
secname [CLASS ‘classname’] [EXECUTE] [READ] [WRITE] [SHARED]
После указания имени секции (сегмента) следует необязательное указание имени класса и атрибутов этой секции — разрешение на выполнение (EXECUTE), на чтение (READ), на запись (WRITE) и объявление секции разделяемой (SHARED) между разными копиями модуля (загруженными в разных адресных пространствах разных приложений!).
Дополнительные разделы
В этом разделе будет рассказано о малоизвестном заголовочном файле — windowsx.h. В некоторых случаях разработчики его, конечно, используют, но редко больше чем на 5% от его возможностей. Этот заголовочный файл был разработан специально для облегчения контроля исходного текста программы. К сожалению, в большей части документации, сопровождающей компиляторы, этот файл вообще не описан, хотя существует уже очень давно. Пожалуй впервые он описан в документации, сопровождающей Microsoft Visual C++ 4.0 (Microsoft Developer Studio, раздел “SDKs|Win32 SDK|Guides|Programming Techniques|Handling Messages with portable macros”). Однако даже там описаны только принципы его применения, но не дано подробное описание всех его макросов. Как результат — при применении windowsx.h приходится постоянно обращаться к его исходному тексту.
В Windows часто бывает так, что одна и та же функция может работать с объектами разных типов. В таких случаях бывает трудно проследить корректность ее применения. Для этого в windowsx.h включено много макросов, которые переопределяют вызовы таких функций, что бы текст получился более читаемым. Именно эту возможность windowsx.h иногда применяют на практике.
Например функция DeleteObject может применяться для удаления многих объектов GDI (Graphics Devices Interface) — карандашей, кистей, регионов и пр. По названию функции понять, какой–именно объект она удаляет нельзя, поэтому при чтении исходного кода приходится сосредотачиваться на параметрах этой функции. В windowsx.h определены макросы:
#define DeletePen(hpen) DeleteObject((HGDIOBJ)(HPEN)(hpen))
#define DeleteBrush(hbr) DeleteObject((HGDIOBJ)(HBRUSH)(hbr))
#define DeleteRgn(hrgn) DeleteObject((HGDIOBJ)(HRGN)(hrgn))
при использовании которых текст становится более читаемым и легче находятся ошибки.
При включении файла windowsx.h в ваше приложение это надо делать после включения основного файла windows.h:
#define STRICT
#include <windows.h>
#include <windowsx.h>
Для того, что бы получить представление о возможностях windowsx.h рекомендуется посмотреть его исходный текст. В нем присутствуют следующие разделы:
· макросы для работы с функциями ядра (несколько макросов для работы с глобальной памятью)
· макросы для работы с объектами GDI
· макросы для работы с окнами (вызовы стандартных функций)
· распаковщики сообщений (самая большая часть)
· макросы для работы с окнами стандартных классов (кнопки, списки и пр.)
· некоторые макросы для оптимизации стандартной библиотеки времени выполнения
В процессе рассмотрения соответствующих разделов мы часто будем обращаться к этому заголовочному файлу. Здесь же мы детально познакомимся с использованием распаковщиков сообщений.
Большая часть файла windowsx.h предназначена для описания распаковщиков сообщений (messagecrackers). Так как в книге преимущественно будут приводятся фрагменты кода с использованием распаковщиков, то в этом месте мы с ними и познакомимся. При их использовании придется постоянно заглядывать в исходный текст windowsx.h, так как в обычной документации распаковщики не описаны. По счастью этот файл хорошо структурирован и снабжен достаточными комментариями.
Для того, что бы понять его назначение, вернемся к оконной процедуре. Основная ее часть — конструкция switch, которая нужна для обработки конкретных сообщений. Обычно (кроме простейших примеров) этот switch разрастается до невообразимых размеров, так что его крайне трудно прочитать (и не дай боже искать там ошибку). Вторым побочным эффектом является то, что огромное количество переменных, используемых для обработки разных сообщений, сосредотачиваются в одной процедуре, бесполезно занимая место.