18 января 2010 в 22:06
Введение в WinAPI. Часть первая. Создание окна.
Наконец-то! Наконец-то! Сегодня мы начнём создавать полноценное окно Windows. Прощай убогая консоль!!!
К этому моменту вы уже должны неплохо знать синтаксис C++, уметь работать с ветвлениями и циклами, хорошо понимать работу функций. Если вы справились с морским боем, можете считать, что всё это вы усвоили.
Венгерская форма записи
Весь код, который мы встретим в WinAPI написан в венгерской форме. Это такое соглашение по написанию кода.
При этом перед именем переменной ставится начальная буква типа. Все слова в именах переменных и функций начинаются с заглавной буквы.
Вот несколько префиксов:
b - переменная типа bool.
l - переменная типа long integer.
w - от word (слово) - 16 бит. Переменная типа unsigned short.
dw - от double word (двойное слово) - 32 бита. Переменная типа unsigned long.
sz - строка заканчивающаяся нулём (string terminated zero). Просто обычная строка, которую мы постоянно использовали.
p или lp - указатель (от pointer). lp (от long pointer) - данные указатели перешли из прошлого. Сейчас lp и p означают одно и то же.
h - описатель (от handle).
Например, указатель будет называться вот так:
void* pData;
Данная форма записи используется Microsoft. Многие критикуют этот способ именования переменных. Но подобные вещи (соглашения о кодировании) в больших компаниях жизненно необходимы.
Напомню, что идентификаторы констант обычно состоят только из заглавных букв: WM_DESTROY. WM_DESTOY - это 2, константа определена через define.
Кроме того, в winAPI используется очень много переопределённых типов. Вот на этой страничке -
И ещё одна вещь, которую мы не разбирали. Указателям часто присваивается значение NULL. Считайте, что это просто 0 и указатели которым присвоено значение NULL (ноль), не указывают ни на какой участок памяти.
Windows API (WinAPI)
Все программы под Windows используют специальный интерфейс программирования WinAPI. Это набор функций и структур на языке C, благодаря которым ваша программа становится совместимой с Windows.
Windows API обладает огромными возможностями по работе с операционной системой. Можно даже сказать - безграничными.
Мы не рассмотрим даже один процент всех возможностей WinAPI. Первоначально я хотел взять больше материала, но это заняло бы слишком много времени, и увязнув в болоте WinAPI, до DirectX'а мы добрались бы через пару лет. Описание WinAPI займёт два урока (включая этот). В них мы рассмотрим только каркас приложения под Windows.
Программа под Windows точно так же как и программа под DOS, имеет главную функцию. Здесь эта функция называется WinMain.
Функция WinMain
Программа под Windows состоит из следующих частей (всё это происходит внутри WinMain):
Создание и регистрация класса окна. Не путайте с классами C++. WinAPI написана на C, здесь нет классов в привычном для нас понимании этого слова.
Создание окна программы.
Основной цикл, в котором обрабатываются сообщения.
Обработка сообщений программы в оконной процедуре. Оконная процедура представляет собой обычную функцию.
Вот эти четыре пункта - основа программы Windows. В течение этого и следующего урока мы разберём всё это подробно. Если вы запутаетесь в описании программы, то вернитесь к этим пунктам.
Теперь разберём всё это подробно:
WinAPI: Структура WNDCLASS
Прежде всего нужно создать и заполнить структурную переменную WNDCLASS, а затем на её основе зарегистрировать оконный класс.
Вот как выглядит эта структура:
typedef struct { UINT style; // стиль окна WNDPROC lpfnWndProc; // указатель на оконную процедуру int cbClsExtra; // дополнительные байты после класса. Всегда ставьте 0 int cbWndExtra; // дополнительные байты после экземпляра окна. Всегда ставьте 0 HINSTANCE hInstance; // экземпляр приложения. Передаётся в виде параметра в WinMain HICON hIcon; // иконка приложения HCURSOR hCursor; // курсор приложения HBRUSH hbrBackground; // цвет фона LPCTSTR lpszMenuName; // имя меню LPCTSTR lpszClassName; // имя класса } WNDCLASS, *PWNDCLASS;
Структура WNDCLASS в составе WinAPI определяет базовые свойства создаваемого окна: иконки, вид курсора мыши, есть ли меню у окна, какому приложению будет принадлежать окно...
После того как вы заполните эту структуру, на её основе можно зарегистрировать оконный класс. Речь идёт не о таких классах как в C++. Скорее можно считать, что оконный класс это такой шаблон, вы его зарегистрировали в системе, и теперь на основе этого шаблона можно создать несколько окон. И все эти окна будут обладать свойствами, которые вы определили в структурной переменной WNDCLASS.
WinAPI: Функция CreateWindow
После регистрации оконного класса, на его основе создаётся главное окно приложения (мы сейчас приступили ко второму пункту). Делается это с помощью функции CreateWindow. Она имеет следующий прототип:
HWND CreateWindow(
LPCTSTR lpClassName, // имя класса
LPCTSTR lpWindowName, // имя окна (отображается в заголовке)
DWORD dwStyle, // стиль окна
int x, // координата по горизонтали от левого края экрана
int y, // координата по вертикали от верхнего края экрана
int nWidth, // ширина окна
int nHeight, // высота окна
HWND hWndParent, // родительское окно
HMENU hMenu, // описатель меню
HINSTANCE hInstance, // экземпляр приложения
LPVOID lpParam // параметр; всегда ставьте NULL
);
Если в оконном классе (структуре WNDCLASS) задаются базовые свойства окна, то здесь - более специфические для каждого окна: размер окна, координаты...
Данная функция возвращает описатель окна. С помощью описателя можно обращаться к окну, это примерно как идентификатор.
Обратите внимание, что здесь очень много новых типов. На самом деле они все старые, просто переопределены. Например: HWND - это переопределение типа HANDLE, который в свою очередь является переопределением PVOID, который в свою очередь является переопределением void*. Как глубоко закопана правда! Но всё же тип HWND - это указатель на void.
Окно состоит из нескольких частей. Практически в каждой программе вы увидите: заголовок окна, системное меню (если нажать на иконку приложения в левой верхней части окна), три системные кнопки для работы с окном: свернуть, развернуть на весь экран и закрыть. Также, практически всегда в приложении присутствует меню. Вот как раз последнего у нас точно не будет. И, конечно же, большую часть окна занимает т.н. клиентская область, в которой обычно и работает пользователь.
Это что касается оконного режима. Довольно долго мы будем практиковаться с DiectX именно в окне - не будем пользоваться полноэкранным режимом.
Обработка сообщений (Message handling)
Основным отличием всех наших предыдущих программ от программ под Windows является обработка сообщений.
Например, когда пользователь нажимает какую-нибудь клавишу на клавиатуре, генерируется сообщение, что была нажата клавиша. Затем это сообщение поступает в приложение, которое было активным, когда пользователь нажал клавишу.
Здесь у нас произошло событие (event) - была нажата клавиша.
Событием может быть: перемещение курсора мыши, смена фокуса приложения, нажатие клавиши клавиатуры, закрытие окна. Событий очень много. Очень! За секунду в операционной системе может происходить десятки событий.
Так вот, когда происходит какое-либо событие, операционная система создаёт сообщение: была нажата такая-то клавиша, координаты курсора мыши изменились, открылось новое окно.
Сообщения может создавать как операционная система, так и различные приложения.
Сообщение представляет собой структуру, и выглядят следующим образом:
typedef struct tagMSG { HWND hwnd; // окно, которое получит это сообщение UINT message; // код сообщения WPARAM wParam; // параметр LPARAM lParam; // параметр DWORD time; // время, когда произошло сообщение POINT pt; // координаты курсора мыши } MSG;
Обратите внимание, как с помощью typedef переопределяются структуры.
Чтобы создать данную структуру можно воспользоваться следующим кодом:
MSG msg;
Всё. Дальше можете использовать эту структуру как обычно.
msg.messgae == 2; // эти две строки эквивалентны так как msg.message == WM_DESTROY; // константа WM_DESTROY равна двум
Здесь, поле, в котором содержится код сообщения (имя сообщения, сравнивается с константой WM_DESTROY. WM - от Windows Message (сообщение Windows). WM_DESTROY - это сообщение, которое генерируется при закрытии окна (destroy - уничтожить).
Коды сообщений определены с помощью констант и имеют префикс WM_: WM_CLOSE, WM_CREATE и др.
В структуре MSG встречается тип HWND - от Window Handle (дескриптор окна или описатель окна). Это такая штука, которая "описывает" окно. Это что-то вроде идентификатора (имени окна).
Запомните это слово - handle (описатель, дескриптор). В Windows это понятие используется очень часто. Почти все типы Windows, которые начинаются с H - описатели: описатель иконки, описатель шрифта, описатель экземпляра приложения. Их штук тридцать насколько я помню.
Все взаимодействия между приложениями в Windows осуществляются с помощью этих самых описателей окон (HWND).
Существует ещё один важный описатель - описатель приложения (HINSTANCE - первый параметр WinMain) - это уникальный идентификатор приложения, благодаря которому операционная система не сможет перепутать две разных программы. Это примерно как штрих-код. Мы рассмотрим его позже.
Каждый раз, когда пользователь совершает какое-то действие, создаётся и заполняется сообщение: задаётся описатель окна, которое должно получить данное сообщение, задаётся идентификатор сообщения, заполняются параметры, заполняется время (текущее) и указываются координаты курсора мыши (смотрите структуру).
После этого данное сообщение помещается в очередь сообщений операционной системы. Когда доходит очередь до нашего сообщения, оно отправляется нужному окну (windows знает какому окну отправлять каждое сообщение благодаря описателям). Когда сообщение приходит в приложение, оно помещается в очередь сообщений приложения. Как только до него доходит очередь, оно обрабатывается.
Смотрите, между тем моментом, когда пользователь совершил какое-либо действие (произошло событие и сгенерировалось сообщение) и тем моментом, когда программа среагировала на это действие (сообщение было обработано программой) происходит много событий. Ведь как в очереди сообщений Windows так и в очереди сообщений приложения может быть много сообщений. В первом случае речь может идти о сотнях, во втором случае как минимум о нескольких.
Оконная процедура (Window procedure - WndProc)
Продолжаем с того момента, как сообщение попало в очередь сообщений приложения. Как только до него дошла очередь, оно обрабатывается. Для обработки сообщений в каждой программе должна существовать специальная функция - оконная процедура. Обычно она называется WndProc (от Window Procedure). Вызов оконной процедуры расположен в основном цикле программы и выполняется при каждой итерации цикла.
Сообщения (в виде структурных переменных MSG) попадают в данную функцию в виде параметров: описатель окна, идентификатор сообщения и два параметра. Обратите внимание, что в оконную процедуру не передаются поля time и pt. То есть сообщение уже "разобрано".
Внутри оконной процедуры расположено ветвление switch, в котором идёт проверка идентификатора сообщения. Вот пример простой оконной процедуры (она полностью рабочая):
// не обращайте пока внимания на HRESULT и __stdcall. Мы рассмотрим их позже HRESULT __stdcall WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_PAINT: // код обработки сообщения WM_PAINT return 0; case WM_DESTROY: // код обработки сообщения WM_DESTROY return 0; } // обработчик всех остальных сообщений }
И последнее - основной цикл. Он очень прост. Каждую итерацию цикла проверяется очередь сообщений приложения. Если в очереди сообщений есть сообщение, оно вытаскивается из очереди. Затем в теле цикла происходит вызов оконной процедуры, чтобы обработать взятое из очереди сообщение.
Вот, в общем-то, и всё на сегодня. Уже видно, что программа под WinAPI намного сложнее программы под DOS. Как я уже писал выше, в следующем уроке мы разберём код работающей программы.
В качестве упражнения создайте новый проект. В окне New Project (Новый проект) выберите шаблон (template) - Win32Project (до сих пор мы выбирали Win32 Console Application). В одном из следующих окон не ставьте флажок Empty Project (пустой проект) и IDE сгенерирует заготовку программы.
Если вы внимательно посмотрите на код файла имя_проекта.cpp, то вы обнаружите все вещи, которые мы обсуждали: структурную переменную MSG, заполнение структуры WNDCLASS, создание окна функцией CreateWindow, основной цикл программы. Кроме того, в файле определена функция WndProc. В ней происходит обработка нескольких сообщений в ветвях switch: WM_COMMAND, WM_PAINT, WM_DESTROY. Найдите всё это в файле.
Кроме того что мы рассмотрели, в программе содержится много дополнительного кода. В следующем выпуске мы рассмотрим код программы, в котором будет вырезано всё лишнее. Он будет намного проще и понятнее того, что генерирует IDE.
На сегодня всё.
7 ноября 2014 в 20:39
спасибо
18 августа 2015 в 12:56
Зачёт!
12 января 2017 в 01:55
Отлично изложенный материал, спасибо !
28 февраля 2017 в 09:43
Side effects ofhttp://cia1sideffects.com/ , What should I watch for while taking this drug?
3 марта 2017 в 02:18
немного поправлю тк автор путает понятия ID и описатель.
отсебятина:
хэндл|id --- это число (| слово) условно означающее что-то.
это как адрес|индекс, но не сами данные.
пр.: токен, команда в zx бейсике, код ошибки..
описатель, дескриптор, заголовок (header) --- это данные описывающие что-то.
пр.: структурка WNDCLASS и WAVEHDR, то что в начале bmp и au файлов, инфо о вас в микрософте и гугле..
тут упамянуто о "венгерской записи" - стандарте префиксов для придумывания названий.
поэтому, усеры - сверяйтесь с названиями (от автор-бреда, чаще помогает) !!
mv:
дб так:
h - id, хэндл (от handle).
HICON hIcon; // хэндл иконки
HCURSOR hCursor; // хэндл курсора (если норушка в окне)
HBRUSH hbrBackground; // хэндл бруша
^
последний пример поясню:
не пытайтесь вставить RGBA (как советует автор: "цвет фона")!!
для этого (как и остальных хэндлов) надо
создать обьект (CreateSolidBrush), и дать туда хэндл.
надеюсь теперь не будет путаницы между условным номером (хэндлом) и данными (описателем)?
авторизуйтесь
или войдите через