
Лаб по С и С++ / Для любознательных
.htmКак не утонуть в море типов данных Как не утонуть в море типов данных
Обратимся вновь к примеру. Первая строка вставляет в данное место программы файл заголовков windows.h. Заметьте, что в файле заголовков есть свои директивы #include, которые вставят другие файлы, поэтому компилируемый модуль окажется довольно внушительным по размеру. При рассмотрении текста программы бросается в глаза обилие новых типов данных (WINAPI, HWND, MSG, WNDCLASS, WPARAM, LPARAM и другие). Эти типы объявлены в подключаемых по цепочке файлах заголовков. Некоторые из типов надо знать обязательно. В системе Help просмотрите страницу Simple types, где приведены очень краткие описания около 70 типов, используемых при программировании в Win32. Однако эти описания вербальные (словесные). Если вы хотите узнать точное описание, следует смотреть исходные коды h-модулей в файлах из папки .. \VC\INCLUDE. Это не так просто, поскольку поиск занимает много времени, да и некоторые описания требуют усилий при иx осмыслении, например:
typedef int (FAR WINAPI *FARPROC)();
Здесь новый тип FARPROC определен как указатель на функции, использующие стандарт вызова WINAPI и возвращающие число типа int. Описатель FAR (дальний или 32-битный) не имеет смысла в Win32, так как уже нет «близких» (16-битных) указателей. Он имеет смысл, если рассматривать вопросы совместимости вашего приложения со старыми программами, однако мы этого делать не будем.
Рассмотрим тип WINAPI. В файле windef.h есть такая директива:
// Макроподстановка, задающая соглашение о способе вызова функций
#define WINAPI_stdcall
Вы, наверное, знаете, что макроподстановка перед компиляцией заменит все вхождения строки WINAPI на строку _stdcall. Это ключевое слово языка C++ описывает договоренность (convention) о передаче параметров при вызове Win32 API-функций. В данном контексте слово «convention» хочется перевести как стандарт. В качестве примера поясним суть стандарта__stdcall:
Порядок передачи аргументов: справа налево.
Аргументы передаются по значению (by value).
Вызываемая функция должна сама выбирать аргументы из стека.
Имя функции декорируется так, что вызов int func(int a. double b); становится _func@12. Число 12 описывает количество байтов, занимаемых списком аргументов. Декорации нужны компоновщику, но иногда и программисту, если он пишет ассемблерные вставки.
Трансляция регистра символов (верхнего или нижнего) не производится.
__stdcall -функции возвращают значение так же, как это принято для функций языка С (__cdecl).
Другой стандарт передачи параметров (__cdecl) приводит к большему размеру исполняемого модуля, чем __stdcall, так как имеется отличие в пункте 3. Теперь вызывающая, а не вызываемая функция должна выбирать аргументы из стека, и для каждого вызова функции компилятор вынужден включать код для очищения стека. Иногда вы будете встречать и устаревшие описатели соглашений, например PASCAL. Он теперь заменен на WINAPI.
Очень часто будут встречаться типы, используемые для описания различных объектов, — описатели из «семейства» HANDLE. Сам тип HANDLE определен оператором
typedef PVOID HANDLE; // Описатель объекта
Это означает, что тип HANDLE эквивалентен уже введенному ранее (действующему в данном месте) типу PVOID. Тип PVOID в свою очередь был определен (в файле winnt.h) как
typedef void *PVOID; // Указатель на любой тип
Отсюда следует, что тип HANDLE эквивалентен типу «указатель на любой тип».
Довольно часто вам будет встречаться описатель HWND — тип для описания окна. Декларация HWND hWnd; означает, что переменная hWnd должна идентифицировать окно. Что скрывается под типом HWND? Попробуем это выяснить. В файле windef.h находим строку
DECLARE_HANDLE (HWND); //Макрос, задающий тип HWND
Теперь ищем описание макроса. После некоторых усилий выясняем, что в файле winnt.h помещено описание
#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##_\
{int unused;};\
typedef struct name#__ *name #else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name)
typedef HANDLE name #endif
Сначала отметим, что в обеих ветвях условной директивы тип HANDLE объявлен как void* — указатель на произвольный тип, и это мы уже знаем. Затем признаем, что для осмысления следующих объявлений надо знать, как работает Token-Pasting Operator (##). Он сливает лексемы. Становится ясно, что если выбрана опция компилятора STRICT, то усилиями препроцессора макрос
DECLARE_HANDLE (HWND); превратится в
struct HWND__ {int unused;};
typedef struct HWND_ *HWND;
Если не объявлен идентификатор STRICT, то тот же макрос даст строку
typedef HANDLE HWND;
Таким образом, при STRICT -компиляции HWND — это указатель на структуру типа HWND__, а при non-STRICT-компиляции HWND — это то же, что и HANDLE, то есть указатель на произвольный тип. Позднее мы сами проверим, действует ли по умолчанию режим STRICT в наших проектах типа Win32 Application, а также в проектах типа MFC AppWizard(exe). Идентификатор STRICT (переводится как строгий, жесткий) означает наличие жесткой проверки типов, введенной для повышения безопасности Windows-программ. В результате декорации имен (выполняемой макросом DECLARE_HANDLE) все различные описатели (HANDLES) в Windows будут различаться по типу. Без опции STRICT можно легко присвоить значение типа HPEN описателю типа HBITMAP, так как и тот и другой являются указателями на произвольный тип. При STRICT-компиляции HPEN будет иметь тип указателя на структуру HPEN__, a HBITMAP будет иметь другой тип — указатель на структуру HBITMAP__, и компилятор, заметив несоответствие типов, локализует ошибку. Возвращаясь к тексту WinMain, отметьте, что описатели и идентификаторы, начинающиеся с букв Н или h, относятся к какому-то из описателей (handles).