C++ — Обработка событий,C++, Win32 Api


Содержание

Структура оконного приложения

Оконные приложения строятся по принципам событийно-управляемого программирования (event-driven programming) — стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна.

С каждым окном в Windows связана определенная функция обработки событий – оконная функция . События для окон называются сообщениями . Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров ( WPARAM и LPARAM ), интерпретация которых зависит от типа сообщения.

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

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

Классическое оконное приложение, как правило, состоит по крайней мере из двух функций:

Стартовая функция WinMain

В консольной программе на С точкой входа является функция main() . С этого места программа начинает выполняться.
Точкой входа программы для Windows является функция WinMain() .

Эта функция использует последовательность вызовов API и при завершении возвращает операционной системе целое число.
Аргументы функции:

  • hInstance – дескриптор процесса (instance handle) – число, идентифицирующее программу, когда она работает под Windows. Если одновременно работают несколько копий одной программы, каждая копия имеет свое значение hInstance .
  • hPrevInstance — предыдущий дескриптор процесса (previous instance) — в настоящее время устарел, всегда равен NULL.
  • szCmdLine — указатель на оканчивающуюся нулем строку, в которой содержатся параметры, переданные в программу из командной строки. Можно запустить программу с параметром командной строки, вставив этот параметр после имени программы в командной строке.
  • iCmdShow — целое константное значение, показывающее, каким должно быть выведено на экран окно в начальный момент. Задается при запуске программы другой программой. В большинстве случаев число равно 1 ( SW_SHOWNRMAL ).
Имя Значение Описание
SW_HIDE Скрывает окно и делает активным другое окно
SW_SHOWNORMAL 1 Отображает и делает активным окно в его первоначальном размере и положении.
SW_SHOWMINIMIZED 2 Активизирует окно и отображает его в свернутом виде
SW_SHOWMAXIMIZED 3 Активизирует окно и отображает его в полноэкранном виде
SW_SHOWNOACTIVATE 4 Отображает окно аналогично SW_SHOWNORMAL , но не активизирует его
SW_SHOW 5 Отображает и делает активным окно с текущим размером и положением.
SW_MINIMIZE 6 Сворачивает текущее окно и делает активным следующее окно в порядке очереди.
SW_SHOWMINNOACTIVE 7 Сворачивает окно аналогично SW_SHOWMINIMIZED , но не активизирует его.
SW_SHOWNA 8 Отображает окно в текущей позиции аналогично SW_SHOW , но не активизирует его.
SW_RESTORE 9 Отображает и активизирует окно. Если окно было свернуто или развернуто во весь экран, оно отображается в своем первоначальном положении и размере.
SW_SHOWDEFAULT 10 Отображает окно способом, заданным по умолчанию.
SW_FORCEMINIMIZE 11 Применяется для минимизации окон, связанных с различными потоками.

В структуре стартовой функции Windows можно выделить следующие операции, образующие «скелет» программы:

Регистрация класса окна

Регистрация класса окна осуществляется функцией

Члены структуры
style — устанавливает стиль(и) класса. Этот член структуры может быть любой комбинацией стилей класса.

Имя Значение Описание
CS_VREDRAW 0x01 Вертикальная перерисовка: осуществлять перерисовку окна при перемещении или изменении высоты окна.
CS_HREDRAW 0x02 Горизонтальная перерисовка: осуществлять перерисовку окна при перемещении или изменении ширины окна.
CS_KEYCVTWINDOW 0x04 В окне будет выполняться преобразование виртуальных клавиш.
CS_DBLCLKS 0x08 Окну будут посылаться сообщения о двойном щелчке кнопки мыши.
CS_OWNDC 0x20 Каждому экземпляру окна присваивается собственный контекст изображения.
CS_CLASSDC 0x40 Классу окна присваивается собственный контекст изображения,который можно разделить между копиями.
CS_PARENTDC 0x80 Классу окна передается контекст изображения родительского окна.
CS_NOKEYCVT 0x100 Отключается преобразование виртуальных клавиш.
CS_NOCLOSE 0x200 Незакрываемое окно: в системном меню блокируется выбор пункта закрытия окна.
CS_SAVEBITS 0x800 Часть изображения на экране, закрытая окном, сохраняется.
CS_BYTEALIGNCLIENT 0x1000 Выравнивание клиентской области окна: использование границы по байту по оси x.
CS_BYTEALIGNWINDOW 0x2000 Выравнивание окна: bспользование границы по байту по оси x.
CS_PUBLICCLASS CS_GLOBALCLASS 0x4000 Определяется глобальный класс окон.

lpfnWndProc — указатель на оконную процедуру.

cbClsExtra — устанавливает число дополнительных байт, которые размещаются вслед за структурой класса окна. Система инициализирует эти байты нулями, в большинстве случаев равен 0.

cbWndExtra — устанавливает число дополнительных байтов, которые размещаются вслед за экземпляром окна. Система инициализирует байты нулями.

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

hIcon — дескриптор значка класса, дескриптор ресурса значка. Если этот член структуры — NULL, система предоставляет заданный по умолчанию значок.

hCursor — дескриптор курсора класса, дескриптор ресурса курсора. Если этот член структуры — NULL, приложение устанавливает форму курсора всякий раз, когда мышь перемещается в окно прикладной программы.

hbrBackground — дескриптор кисти фона класса, дескриптор физической кисти, которая используется, чтобы красить цветом фона, или код цвета, преобразованный к типу HBRUSH .

lpszMenuName — указатель на символьную строку с символом конца строки ( ‘\0’ ), которая устанавливает имя ресурса меню класса. Можно использовать целое число, чтобы идентифицировать меню с помощью макроса MAKEINTRESOURCE( int ) . Если этот член структуры — NULL , окна, принадлежащие этому классу, не имеют заданного по умолчанию меню.

lpszClassName — указатель на символьную строку с именем класса, оканчивающуюся ‘\0’ .

Создание окна

Создание окна осуществляется функцией

Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение – дескриптор создаваемого окна. В случае невозможности создать окно возвращается NULL.

Аргументы функции :
lpClassName – указывает на строку с ‘\0’ в конце, которая определяет имя класса окна. Имя класса может быть зарегистрированным функцией RegisterClass или любым из предопределенных имен класса элементов управления.

lpWindowName — указывает на строку с ‘\0’ в конце, которая определяет имя окна.

dwStyle — определяет стиль создаваемого окна.

Имя Значение Описание
WS_BORDER 0x00800000 Окно имеет тонкую границу в виде линии.
WS_CAPTION 0x00C00000 Окно имеет строку заголовка.
WS_CHILD 0x40000000 Окно является дочерним.
WS_DISABLED 0x08000000 Окно является изначально неактивным.
WS_GROUP 0x00020000 Окно группирует другие управляющие элементы.
WS_HSCROLL 0x00100000 Окно содержит горизонтальную полосу прокрутки.
WS_MAXIMIZE 0x01000000 Исходный размер окна – во весь экран.
WS_MINIMIZE 0x20000000 Исходно окно свернуто.
WS_OVERLAPPED 0x00000000 Окно может быть перекрыто другими окнами.
WS_POPUP 0x80000000 Всплывающее окно.
WS_SYSMENU 0x00080000 Окно имеет системное меню в строке заголовка.
WS_VISIBLE 0x10000000 Окно изначально видимое.
WS_VSCROLL 0x00200000 Окно имеет вертикальную полосу прокрутки.

x — определяет координату левой стороны окна относительно левой стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату левой стороны относительно начальной координаты родительского окна. Если установлен как CW_USEDEFAULT , Windows выбирает заданную по умолчанию позицию окна.

у – определяет координату верхней стороны окна относительно верхней стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату верхней стороны относительно начальной координаты родительского окна.

nWidth – определяет ширину окна в единицах измерения устройства. Если параметр соответствует CW_USEDEFAULT , Windows выбирает заданную по умолчанию ширину и высоту для окна.

nHeight – определяет высоту окна в единицах измерения устройства.

hWndParent – дескриптор родительского окна.

hMenu – идентифицирует меню, которое будет использоваться окном. Этот параметр может быть NULL , если меню класса будет использовано.

hInstance — идентифицирует экземпляр модуля, который будет связан с окном.

lpParam — указывает на значение, переданное окну при создании.

Отображение и перерисовка окна

Отображение окна осуществляется функцией

Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешное отображение окна, 0 – ошибка.

Аргументы функции :
hWnd – дескриптор отображаемого окна.

nCmdShow – константа, определяющая, как будет отображаться окно согласно таблице.

Перерисовка окна осуществляется функцией


Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешная перерисовка окна, 0 – ошибка.
Аргумент функции hWnd – дескриптор окна.

Цикл обработки сообщений

После вызова функции UpdateWindow , окно окончательно выведено на экран. Теперь программа должна подготовить себя для получения информации от пользователя через клавиатуру и мышь. Windows поддерживает «очередь сообщений» (message queue) для каждой программы, работающей в данный момент в системе Windows. Когда происходит ввод информации, Windows преобразует ее в «сообщение», которое помещается в очередь сообщений программы. Программа извлекает сообщения из очереди сообщений, выполняя блок команд, известный как «цикл обработки сообщений» (message loop):

Для получения сообщения из очереди используется функция:

Прототип функции находится в файле библиотеки user32.dll.
В случае получения из очереди сообщения, отличного от WM_QUIT , возвращает ненулевое значение.

Аргументы функции :
lpMsg — указатель на структуру сообщения.

Структура POINT имеет вид

hWnd — дескриптор окна, очередь для которого просматривается.

wMsgFilterMin — нижняя граница фильтра идентификаторов сообщений.

wMsgFilterMax — верхняя граница фильтра идентификаторов сообщений.

передает аргумент — структуру msg обратно в Windows для преобразования какого-либо сообщения с клавиатуры. Возвращает ненулевое значение в случае успешной расшифровки сообщения, 0 – ошибка.

передает аргумент — структуру msg обратно в Windows. Windows отправляет сообщение для его обработки соответствующей оконной процедуре — таким образом, Windows вызывает соответствующую оконную функцию, указанную при регистрации класса окна.

После того, как оконная функция обработает сообщение, оно возвращается в Windows, которая все еще обслуживает вызов функции DispatchMessage . Когда Windows возвращает управление в стартовую функцию WinMain() к следующему за вызовом DispatchMessage коду, цикл обработки сообщений в очередной раз возобновляет работу, вызывая GetMessage .
Возвращает значение, определяемое оконной функцией, которое чаще всего игнорируется.
Прототипы функций находятся в файле библиотеки user32.dll.

Пример стартовой функции, создающей и выводящей окно размером 500х300 точек:

Примечание : Для корректной сборки приложения используется многобайтовая кодировка.

Оконная функция — обработка сообщений окна

Оконная функция предназначена для обработки сообщений окна. Функция обработки сообщений окна организована по принципу ветвления, состоящего из последовательной проверки типа сообщения. При совпадении типа сообщения, переданного в структуре Message с соответствующей веткой, осуществляется его обработка. Минимальный вид оконной функции представлен ниже.

4 аргумента оконной функции идентичны первым четырем полям структуры сообщения MSG .

В примере обрабатывается только один тип сообщения WM_DESTROY , которое передается оконной функции при закрытии окна.

Вызов функции DefWindowProc() обрабатывает по умолчанию все сообщения, которые не обрабатывает оконная процедура.
Функция PostQuitMessage() сообщает Windows, что данный поток запрашивает завершение. Аргументом является целочисленное значение, которое функция вернет операционной системе.

Результат выполнения программы, выводящей окно:

Комментариев к записи: 13

/*WinAPI приложение. Минимальный
набор функций для отображения окна.
Эта программа станет базовой заготовкой для всех последующих программ*/
#include
//Создаём прототип функции окна
LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM );
//объявляем имя программы
char szProgName[]=&qout;Имя программы&qout;;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
<
HWND hWnd; //идентификатор окна
MSG lpMsg;
WNDCLASS w; //создаём экземпляр структуры WNDCLASS и начинаем её заполнять
w.lpsz >//имя программы
w.hInstance=hInstance; //идентификатор текущего приложения
w.lpfnWndProc=WndProc; //указатель на функцию окна
w.hCursor=LoadCursor( NULL , >//загружаем курсор в виде стрелки
w.hIcon=0; //иконки у нас не будет пока
w.lpszMenuName=0; //и меню пока не будет
w.hbrBackground=( HBRUSH )GetStockObject(WHITE_BRUSH); //цвет фона окна — белый
w.style= CS_HREDRAW | CS_VREDRAW ; //стиль окна — перерисовываемое по х и по у
w.cbClsExtra=0;
w.cbWndExtra=0;
//Если не удалось зарегистрировать класс окна — выходим
if (! RegisterClass (&w))
return 0;
//Создадим окно в памяти, заполнив аргументы CreateWindow
hWnd= CreateWindow (szProgName, //Имя программы
«Моя первая программа!» , //Заголовок окна
WS_OVERLAPPEDWINDOW , //Стиль окна — перекрывающееся
100, //положение окна на экране по х
100, //по у
500, //размеры по х
400, //по у
( HWND ) NULL , //идентификатор родительского окна
( HMENU ) NULL , //идентификатор меню
( HINSTANCE )hInstance, //идентификатор экземпляра программы
( HINSTANCE ) NULL ); //отсутствие дополнительных параметров
//Выводим окно из памяти на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);
//Цикл обработки сообщений
while ( GetMessage (&lpMsg, NULL , 0, 0)) < //Получаем сообщение из очереди
TranslateMessage(&lpMsg); //Преобразуем сообщения клавиш в символы
DispatchMessage (&lpMsg); //Передаём сообщение соответствующей функции окна
>
return (lpMsg.wParam);
>

//Функция окна
LRESULT CALLBACK WndProc( HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
<
HDC hdc; //создаём контекст устройства
PAINTSTRUCT ps; //создаём экземпляр структуры графического вывода
//Цикл обработки сообщений
switch (messg)
<
//сообщение рисования
case WM_PAINT :
//начинаем рисовать
hdc=BeginPaint(hWnd, &ps);
//здесь вы обычно вставляете свой текст:
TextOut(hdc, 150,150, «Здравствуй, WIN 32 API. » , 26);
//закругляемся
//обновляем окно
Val >NULL );
//заканчиваем рисовать
EndPaint(hWnd, &ps);
break ;
//сообщение выхода — разрушение окна
case WM_DESTROY :
PostQuitMessage(0); //Посылаем сообщение выхода с кодом 0 — нормальное завершение
break ;
default:
return ( DefWindowProc (hWnd, messg, wParam, lParam));
//освобождаем очередь приложения от нераспознаных
>
return 0;
>

Простейшая программа WinAPI на C++

Posted by bullvinkle under Журнал, Статьи

Многие, кто переходит с «учебного» ДОСовского компилятора вроде Borland C++ на визуальное программирование быстро запутываются в сложных библиотеках типа MFC или VCL, особенно из-за того, что новые создаваемые проекты уже содержат с десяток файлов и сложную структуру классов. Рано или поздно встает вопрос: «…а почему нельзя написать оконную программу с простой линейной структурой, состоящую из одного файла .cpp?» На самом деле можно. Для этого нужно работать с низкоуровневыми функциями операционной системы – API.

Простейшая программа WinAPI на C++

Дмитрий Федорков

Windows API (application programming interfaces) – общее наименование целого набора базовых функций интерфейсов, программирования приложений операционных систем семейств Windows и Windows NT корпорации «Майкрософт». Является самым прямым способом взаимодействия приложений с Windows.

Зачем нам вообще API

Все что делает любая программа – делает либо непосредственно с помощью инструкций процессора, либо обращаясь к функциям биоса (хотя их прямые вызовы используются всё реже), либо через системные функции (API). К последним относится: прорисовка окон, получение координат мыши, чтение файлов и т. д.

WinAPI – это основа, в который должен разбираться любой программист, пишущий под Windows, независимо от того, использует ли он библиотеки вроде MFC (Microsoft Visual C++) или VCL (Borland Delphi / C++ Builder). Часто бывает проще написать простенькую программу, состоящую из одного файла, чем настраивать относительно сложный проект, созданный визардами. Я не говорю уже, что программа получается гораздо оптимизированнее (всё-таки низкоуровневое программирование) и в несколько раз меньше. К тому же у них не возникает проблем совместимости, если у конечного пользователя не хватает каких-т
о библиотек, чем иногда грешит MFC.

Наша программа

Напишем простую программу: окно, в нем – синусоида, которая движется влево, как график функции

y = sin (x + t). Если кликнуть мышкой по окну, анимация приостановится, или наоборот продолжится. Чтобы было проще разобраться, я сразу приведу весь исходный код, а потом прокомментирую ключевые места. Попробуйте самостоятельно модифицировать разные части программы, пробуйте оптимизировать мою программу, может вам даже удастся найти ошибки в коде (см. листинг):

тестовая программа

LRESULT CALLBACK WindowProc (HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;

wc.hCursor = LoadCursor (NULL, IDC_ARROW);

HWND hWnd = CreateWindow (L”CMyWnd”, L”WinMain sample”, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 320, 240, NULL, NULL, hInstance, NULL);

ShowWindow (hWnd, nCmdShow);

// Message loop (timer, etc)

SetTimer (hWnd, 1, USER_TIMER_MINIMUM, NULL);

while (GetMessage(&msg,NULL,0,0) > 0)// while not WM_QUIT (0) nor some error (-1)


// Message processing function

LRESULT CALLBACK WindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

static bool Move = true;

static int Phase=0, Width, Height;

Rectangle (dc, -1, -1, Width+1, Height+1);

MoveToEx (dc, 0, Height * (0.5 + 0.3*sin(0.1*Phase)), NULL);

if (wParam != VK_ESCAPE)

return DefWindowProc (hWnd, message, wParam, lParam);

Обращаю ваше внимание на то, что эта программа писалась под Visual C++. У Билдера может быть проблема из-за заголовка , вместо него нужен . Для этой программы понадобится пустой проект с единственным файлом .cpp. В Visual Studio в свойствах создаваемого проекта нужно отметить галочку «Empty project». Итак, приступим…

Пройдемся по коду

В программе мы добавляем заголовочный файл , который нужен для расчета синусоиды, и , который содержит все функции WinAPI. Строчка #define WIN32_LEAN_AND_MEAN отключает некоторые редко используемые функции и ускоряет компиляцию.

Функцию WindowProc() пока пропустим, оставив на сладкое.

HDC – контекст устройства рисования. Не будем подробно останавливаться на графике – это не основная тема статьи, да и используется не очень часто. Скажу лишь, что эта переменная глобальная, потому что используется в обеих функциях. Надо добавить, что буква ”H” в типе данных WinAPI (в “HDC”) обычно означает ”Handle” (хэндл), т.е. переменную, дающую доступ к самым разным устройствам, объектам и прочим сущностям WinAPI. Хэндл – представляет собой обычный указатель, работа с которым зависит от контекста (от типа переменной). Вообще, хэндлы – сложная тема, без к
оторой тоже поначалу вполне можно обойтись.

Теперь главное (main) – точка входа. В консольных программах функция main может возвращать либо void, либо int, а также может иметь или не иметь аргументы (int argc, char **argv). Итого 4 варианта. В нашем случае используется функция WinMain(), которая может иметь только такой вид, как в примере. Слово WINAPI (которое подменяется препроцессором на __stdcall) означает, что аргументы функции передаются через стек*. Аргумент HINSTANCE hInstance — хэндл текущего процесса, который бывает нужен в некоторых ситуациях. Назначение следующего аргумента HINSTANCE hPrevInstance весьма смутное, известно только, что э
та переменная всегда равна NULL. В исходниках квейка можно даже найти такую строчку: if (hPrevInstance != NULL) return 0.

* подробнее – в учебниках по ассемблеру

Аргумент LPSTR lpCmdLine – командная строка. В отличие от консольного main (int argc, char **argv), эта строка не разделена на отдельные аргументы и включает имя самой программы (что-нибудь типа “C:\WINDOWS\system32\format.com C: \u”). Далее int nCmdShow определяет параметры окна, указанные например, в свойствах ярлыка (это будет нужно при создании окна).

Перейдем, наконец, к выполняемому коду. В первую очередь нам нужно создать окно. Структура WNDCLASS хранит свойства окна, например текст заголовка и значок. 4-ре из 9-ти полей структуры должны быть нулевые, поэтому сразу инициализируем ее нулями. Далее CS_HREDRAW | CS_VREDRAW означает, что окно будет перерисовываться при изменении размера окна. wc.hInstance задаёт текущий процесс (тут-то и понадобился этот аргумент из WinMain). Еще также нужно явно указать мышиный курсор, иначе, если это поле оставить нулевым, курсор не будет меняться, скажем, при переходе с границы окна на са
мо окно (попробуйте сами). wc.lpfnWndProc – это адрес функции, которая будет обрабатывать все события. Такие как нажатие клавиши, движение мыши, перетаскивание окна и т. д. После знака ”=” просто указываем имя нашей функции. Позже мы напишем эту функцию, которая и будет определять реакцию программы на интересующие нас события.

WNDCLASS – это не само окно, а класс (форма), экземпляр которого и будет нашим окном. Но перед созданием окна нужно зарегистрировать в системе этот класс. Задаем его имя в системе CMyWnd и регистрируем класс.

Функция создания окна CreateWindow() достаточно простая и вместо того, чтобы перечислять все ее аргументы, опять сошлюсь на интернет. Кому мало одиннадцати аргументов, могут попробовать функцию CreateWindowEx(). Обратите внимание – все строковые аргументы предваряются буквой L, что означает, что они – юникодовые. Для многих функций WinAPI существует по два варианта: обычный ANSI и юникодовый. Соответственно они имеют суффикс A или W, например CreateWindowA и CreateWindowW. Если вы посмотрите определение функции в , то увидите что-то типа #define CreateWindow
CreateWindowW
. Вместо CreateWindow() мы можем явно вызывать CreateWindowA() с обычными строками (без приставки L).

Описание GetDC() и ShowWindow() снова пропущу (кому интересно – тот легко найдет).

Дальше начинается самое интересное – работа с событиями. Для начала создадим таймер, который будет генерировать соответствующее событие 65 раз в секунду (фактически максимальная частота, по крайней мере для Windows XP). Если вместо последнего аргумента SetTimer() написать имя подходящей функции, она будет вызываться вместо генерации события.

Далее идет то, что называется message loop – цикл обработки событий. Мы принимаем событие и обрабатываем его. В нашем случае можно убрать TranslateMessage(&msg), но эта функция понадобится, если на основе этого примера кто-нибудь будет создавать более сложную программу (с обработкой скан-кодов клавиатуры). Если мы получаем событие выхода программы, то GetMessage() возвращает ноль. В случае ошибки возвращается отрицательное значение. В обоих случаях выходим из цикла и возвращаем код выхода программы.

Теперь займемся функцией обработки событий WindowProc(), которую мы оставили на сладкое. Эта функция вызывается при любом событии. Какое именно сейчас у нас событие – определяется аргументом message. Дополнительные параметры (например, координаты мыши в событии “мышь двинулась”) находятся в аргументах wParam и lParam. В зависимости от того, чему равно message, мы совершаем те или иные (или вообще никакие) действия, а потом в любом случае вызываем DefWindowProc, чтобы не блокировать естественные реакции окна на ра
зные события.

Вообще то, что я сделал с оператором switch больше похоже на стиль ассемблера и порицается большинством серьезных разработчиков. Я имею в виду сквозные переходы между case- ми (там, где нет break). Но пример простой, к тому же у меня было настроение “похакерить”, так что не буду лишать вас удовольствия разобраться в этом коде.

Имена констант message говорят сами за себя и уже знакомы тем, кто работал с MFC. При событии WM_PAINT рисуем белый прямоугольник, а на нём — чёрную синусоиду. На каждый WM_TIMER смещаем фазу синусоиды и перерисовываем ее. На клик мыши запускаем или останавливаем движение, при этом, если нажать обе кнопки одновременно, то фаза увеличится ровно на 1, для чего здесь и убран break (см. рисунок). При изменении размера окна мы обновляем переменные Width и Height за счёт того, что в lParam хранятся новые размеры. Всегда нужно вручную обрабатывать собы

Обработчик событий WinAPI в Rad Studio XE5

Использую WinAPI для передачи сообщений между окнами путем посылки WM_COPYDATA. В старых C++ Builder проектах все работает как часы. В RAD нет. Имеется следующий код:

А вот объявленное событие OnMyCopyData(TWMCopyData &dat) при посылке сообщения не вызывается. Помогите разобраться в ситуации.

Знаете кого-то, кто может ответить? Поделитесь ссылкой на этот вопрос по почте, через Твиттер или Facebook.

Посмотрите другие вопросы с метками c++builder winapi radstudio c++ или задайте свой вопрос.

Похожие

Подписаться на ленту

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

дизайн сайта / логотип © 2020 Stack Exchange Inc; пользовательское содержимое попадает под действие лицензии cc by-sa 4.0 с указанием ссылки на источник. rev 2020.11.13.35428

Пошаговое руководство. создание традиционного классического приложения Windows (C++) Walkthrough: Create a traditional Windows Desktop application (C++)

В этом пошаговом руководстве показано, как создать традиционное классическое приложение Windows в Visual Studio. This walkthrough shows how to create a traditional Windows desktop application in Visual Studio. В примере приложения, которое вы создадите, будет использоваться API Windows для вывода «Hello, Windows Desktop!» The example application you’ll create uses the Windows API to display «Hello, Windows desktop!» «Hello, World!». in a window. Код, созданный в этом пошаговом руководстве, можно использовать в качестве шаблона для создания других классических приложений Windows. You can use the code that you develop in this walkthrough as a pattern to create other Windows desktop applications.

API Windows (также известный как API Win32, Windows Desktop API и Windows Classic API) — это платформа на основе языка C для создания приложений Windows. The Windows API (also known as the Win32 API, Windows Desktop API, and Windows Classic API) is a C-language-based framework for creating Windows applications. Он уже существует, так как 1980-х и использовался для создания приложений Windows в течение десятилетий. It has been in existence since the 1980s and has been used to create Windows applications for decades. Более сложные и удобные платформы были построены поверх Windows API. More advanced and easier-to-program frameworks have been built on top of the Windows API. Например, MFC, ATL, .NET Frameworks. For example, MFC, ATL, the .NET frameworks. Даже самый современный код среда выполнения Windows для приложений UWP и Store, написанных C++в/WinRT, использует API Windows под. Even the most modern Windows Runtime code for UWP and Store apps written in C++/WinRT uses the Windows API underneath. Дополнительные сведения об API Windows см. в разделе индекс Windows API. For more information about the Windows API, see Windows API Index. Существует множество способов создания приложений Windows, но описанный выше процесс был первым. There are many ways to create Windows applications, but the process above was the first.

Для краткости в тексте пропущены некоторые операторы кода. For the sake of brevity, some code statements are omitted in the text. В разделе Построение кода в конце документа показан полный код. The Build the code section at the end of this document shows the complete code.

Необходимые компоненты Prerequisites

Компьютер под управлением Microsoft Windows 7 или более поздних версий. A computer that runs Microsoft Windows 7 or later versions. Для обеспечения оптимальной среды разработки рекомендуется использовать Windows 10. We recommend Windows 10 for the best development experience.

Копия Visual Studio. A copy of Visual Studio. Сведения о скачивании и установке Visual Studio см. в этой статье. For information on how to download and install Visual Studio, see Install Visual Studio. Когда вы запускаете установщик, убедитесь, что установлена рабочая нагрузка Разработка классических приложений на C++ . When you run the installer, make sure that the Desktop development with C++ workload is checked. Не беспокойтесь, если вы не установили эту рабочую нагрузку при установке Visual Studio. Don’t worry if you didn’t install this workload when you installed Visual Studio. Вы можете снова запустить установщик и установить ее сейчас. You can run the installer again and install it now.

Базовые значения об использовании интегрированной среды разработки Visual Studio. An understanding of the basics of using the Visual Studio IDE. Если вы уже использовали классические приложения для Windows, вы, вероятно, справитесь. If you’ve used Windows desktop apps before, you can probably keep up. Общие сведения см. в обзоре возможностей интегрированной среды разработки Visual Studio. For an introduction, see Visual Studio IDE feature tour.

Основные навыки владения языком C++. An understanding of enough of the fundamentals of the C++ language to follow along. Не волнуйтесь, мы не будем делать ничего сложного. Don’t worry, we don’t do anything too complicated.

Создание проекта для настольных систем Windows Create a Windows desktop project

Выполните следующие действия, чтобы создать свой первый проект для настольных систем Windows. Follow these steps to create your first Windows desktop project. В процессе работы вы вводите код для рабочего приложения Windows. As you go, you’ll enter the code for a working Windows desktop application. В левом верхнем углу этой страницы находится селектор версий. There’s a version selector in the upper left of this page. Убедитесь, что для него задана версия Visual Studio, которую вы используете. Make sure it’s set to the version of Visual Studio that you’re using.

Создание проекта для классических приложений Windows в Visual Studio 2020 To create a Windows desktop project in Visual Studio 2020

В главном меню выберите Файл > Создать > Проект, чтобы открыть диалоговое окно Создание проекта. From the main menu, choose File > New > Project to open the Create a New Project dialog box.


В верхней части диалогового окна задайте параметру Language значение C++ , задайте для параметра платформа значение Windowsи задайте для параметра тип проекта значение Рабочий стол. At the top of the dialog, set Language to C++, set Platform to Windows, and set Project type to Desktop.

В отфильтрованном списке типов проектов выберите Мастер рабочего стола Windows , а затем нажмите кнопку Далее. From the filtered list of project types, choose Windows Desktop Wizard then choose Next. На следующей странице введите имя проекта, например десктопапп. In the next page, enter a name for the project, for example, DesktopApp.

Нажмите кнопку Создать, чтобы создать проект. Choose the Create button to create the project.

Откроется диалоговое окно проекта Windows Desktop . The Windows Desktop Project dialog now appears. В разделе Тип приложениявыберите классическое приложение (. exe) . Under Application type, select Desktop application (.exe). В поле Дополнительные параметрывыберите Пустой проект. Under Additional options, select Empty project. Нажмите кнопку ОК , чтобы создать проект. Choose OK to create the project.

В Обозреватель решенийщелкните правой кнопкой мыши проект Десктопапп , выберите Добавить, а затем выберите новый элемент. In Solution Explorer, right-click the DesktopApp project, choose Add, and then choose New Item.

В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp) . In the Add New Item dialog box, select C++ File (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. In the Name box, type a name for the file, for example, HelloWindowsDesktop.cpp. Выберите Добавить. Choose Add.

Теперь проект создан и исходный файл открыт в редакторе. Your project is now created and your source file is opened in the editor. Чтобы продолжить, перейдите к созданию кода. To continue, skip ahead to Create the code.

Создание проекта для классических приложений Windows в Visual Studio 2020 To create a Windows desktop project in Visual Studio 2020

В меню Файл выберите команду Создать, а затем пункт Проект. On the File menu, choose New and then choose Project.

В левой области диалогового окна Новый проект разверните узел установленные > C++визуальныйэлемент, а затем выберите пункт Windows Desktop. In the New Project dialog box, in the left pane, expand Installed > Visual C++, then select Windows Desktop. В средней области выберите Мастер рабочего стола Windows. In the middle pane, select Windows Desktop Wizard.

В поле имя введите имя проекта, например десктопапп. In the Name box, type a name for the project, for example, DesktopApp. Нажмите кнопку ОК. Choose OK.

В диалоговом окне проект Windows Desktop в разделе Тип приложениявыберите приложение Windows (. exe) . In the Windows Desktop Project dialog, under Application type, select Windows application (.exe). В поле Дополнительные параметрывыберите Пустой проект. Under Additional options, select Empty project. Убедитесь, что предварительно скомпилированный заголовок не выбран. Make sure Precompiled Header isn’t selected. Нажмите кнопку ОК , чтобы создать проект. Choose OK to create the project.

В Обозреватель решенийщелкните правой кнопкой мыши проект Десктопапп , выберите Добавить, а затем выберите новый элемент. In Solution Explorer, right-click the DesktopApp project, choose Add, and then choose New Item.

В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp) . In the Add New Item dialog box, select C++ File (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. In the Name box, type a name for the file, for example, HelloWindowsDesktop.cpp. Выберите Добавить. Choose Add.

Теперь проект создан и исходный файл открыт в редакторе. Your project is now created and your source file is opened in the editor. Чтобы продолжить, перейдите к созданию кода. To continue, skip ahead to Create the code.

Создание проекта для классических приложений Windows в Visual Studio 2015 To create a Windows desktop project in Visual Studio 2015

В меню Файл выберите команду Создать, а затем пункт Проект. On the File menu, choose New and then choose Project.

В левой области диалогового окна Новый проект разверните узел установленные > шаблоны > визуальный C++ элемент, а затем выберите пункт Win32. In the New Project dialog box, in the left pane, expand Installed > Templates > Visual C++, and then select Win32. В средней области выберите шаблон Проект Win32. In the middle pane, select Win32 Project.

В поле имя введите имя проекта, например десктопапп. In the Name box, type a name for the project, for example, DesktopApp. Нажмите кнопку ОК. Choose OK.

На странице Обзор мастера приложений Win32нажмите кнопку Далее. On the Overview page of the Win32 Application Wizard, choose Next.

На странице Параметры приложения в разделе Тип приложениявыберите пункт приложение Windows. On the Application Settings page, under Application type, select Windows application. В разделе Дополнительные параметрыснимите флажок предкомпилированный заголовок, а затем выберите пустой проект. Under Additional options, uncheck Precompiled header, then select Empty project. Нажмите кнопку Готово , чтобы создать проект. Choose Finish to create the project.

В Обозреватель решенийщелкните правой кнопкой мыши проект десктопапп, выберите Добавить, а затем выберите новый элемент. In Solution Explorer, right-click the DesktopApp project, choose Add, and then choose New Item.

В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp) . In the Add New Item dialog box, select C++ File (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. In the Name box, type a name for the file, for example, HelloWindowsDesktop.cpp. Выберите Добавить. Choose Add.

Теперь проект создан и исходный файл открыт в редакторе. Your project is now created and your source file is opened in the editor.

Создание кода Create the code

Далее вы узнаете, как создать код для классического приложения Windows в Visual Studio. Next, you’ll learn how to create the code for a Windows desktop application in Visual Studio.

Запуск классического приложения Windows To start a Windows desktop application

Так же, как у каждого C++ приложения C и приложения должна быть функция main в качестве начальной точки, каждое классическое приложение Windows должно иметь функцию WinMain . Just as every C application and C++ application must have a main function as its starting point, every Windows desktop application must have a WinMain function. WinMain имеет следующий синтаксис: WinMain has the following syntax.

Сведения о параметрах и возвращаемом значении этой функции см. в разделе WinMain Entry Point. For information about the parameters and return value of this function, see WinMain entry point.

Что такое дополнительные слова, такие как CALLBACK или HINSTANCE , или _In_ ? What are all those extra words, such as CALLBACK , or HINSTANCE , or _In_ ? Традиционные API Windows часто используют определения типов и макросов препроцессора для абстракции некоторых сведений о типах и кода для конкретной платформы, таких как соглашения о вызовах, объявления __declspec и директивы pragma компилятора. The traditional Windows API uses typedefs and preprocessor macros extensively to abstract away some of the details of types and platform-specific code, such as calling conventions, __declspec declarations, and compiler pragmas. В Visual Studio можно использовать функцию » быстрые сведения » IntelliSense, чтобы увидеть, что определяются этими определениями и макросами. In Visual Studio, you can use the IntelliSense Quick Info feature to see what these typedefs and macros define. Наведите указатель мыши на интересующую слово или выберите его и нажмите клавиши ctrl +K, CTRL +I для небольшого всплывающего окна, содержащего определение. Hover your mouse over the word of interest, or select it and press Ctrl+K, Ctrl+I for a small pop-up window that contains the definition. Дополнительные сведения см. в статье Using IntelliSense (Использование IntelliSense). For more information, see Using IntelliSense. Параметры и возвращаемые типы часто используют аннотации SAL , чтобы помочь в перехвате ошибок программирования. Parameters and return types often use SAL Annotations to help you catch programming errors. Дополнительные сведения см. в разделе Использование аннотаций SAL для сокращенияC++ числа дефектов C/Code. For more information, see Using SAL Annotations to Reduce C/C++ Code Defects.

Для настольных программ Windows требуется > Windows desktop programs require . определяет макрос TCHAR , который, в конечном итоге, разрешается в wchar_t , если символ Юникода определен в проекте, в противном случае он разрешается в char. defines the TCHAR macro, which resolves ultimately to wchar_t if the UNICODE symbol is defined in your project, otherwise it resolves to char. Если вы всегда создаете Юникод с включенным поддержкой Юникода, то не требуется TCHAR и можете просто использовать wchar_t напрямую. If you always build with UNICODE enabled, you don’t need TCHAR and can just use wchar_t directly.

Наряду с функцией WinMain в каждом классическом приложении Windows также должна быть функция окна. Along with the WinMain function, every Windows desktop application must also have a window-procedure function. Эта функция обычно называется WndProc , но вы можете назвать ее по своему усмотрению. This function is typically named WndProc , but you can name it whatever you like. WndProc имеет следующий синтаксис: WndProc has the following syntax.

В этой функции вы пишете код для управления сообщениями , получаемыми приложением из Windows при возникновении событий . In this function, you write code to handle messages that the application receives from Windows when events occur. Например, если пользователь нажмет кнопку ОК в приложении, Windows отправит вам сообщение, и вы сможете написать код внутри функции WndProc , которая будет использовать любую работу. For example, if a user chooses an OK button in your application, Windows will send a message to you and you can write code inside your WndProc function that does whatever work is appropriate. Он называется обработкой события. It’s called handling an event. Вы обрабатываете только те события, которые относятся к вашему приложению. You only handle the events that are relevant for your application.

Дополнительные сведения см. в разделе Процедуры окна. For more information, see Window Procedures.

Добавление функциональных возможностей в функцию WinMain To add functionality to the WinMain function

В функции WinMain заполните структуру типа вндклассекс. In the WinMain function, you populate a structure of type WNDCLASSEX. Структура содержит сведения о окне: значок приложения, цвет фона окна, имя, отображаемое в строке заголовка, помимо прочего. The structure contains information about the window: the application icon, the background color of the window, the name to display in the title bar, among other things. Важно, что он содержит указатель на функцию окна. Importantly, it contains a function pointer to your window procedure. В приведенном ниже примере показана типичная структура WNDCLASSEX . The following example shows a typical WNDCLASSEX structure.

Дополнительные сведения о полях приведенной выше структуры см. в разделе вндклассекс. For information about the fields of the structure above, see WNDCLASSEX.

Зарегистрируйте WNDCLASSEX в Windows, чтобы он знал о вашем окне и способах отправки в него сообщений. Register the WNDCLASSEX with Windows so that it knows about your window and how to send messages to it. Воспользуйтесь функцией RegisterClassEx и передайте структуру класса окна в качестве аргумента. Use the RegisterClassEx function and pass the window class structure as an argument. Используется макрос _T , так как используется тип TCHAR . The _T macro is used because we use the TCHAR type.

Теперь можно создать окно. Now you can create a window. Воспользуйтесь функцией CreateWindow . Use the CreateWindow function.

Эта функция возвращает HWND , который является обработчиком окна. This function returns an HWND , which is a handle to a window. Маркер похож на указатель, используемый Windows для наблюдения за открытыми окнами. A handle is somewhat like a pointer that Windows uses to keep track of open windows. Дополнительные сведения см. в разделе Типы данных Windows. For more information, see Windows Data Types.

На этом этапе окно было создано, но нам по-прежнему нужно сообщить Windows, что он стал видимым. At this point, the window has been created, but we still need to tell Windows to make it visible. Вот что делает этот код: That’s what this code does:

Отображаемое окно не содержит много содержимого, так как вы еще не реализовали функцию WndProc . The displayed window doesn’t have much content because you haven’t yet implemented the WndProc function. Иными словами, приложение еще не обрабатывает сообщения, отправляемые Windows в него. In other words, the application isn’t yet handling the messages that Windows is now sending to it.

Для обработки сообщений сначала нужно добавить цикл обработки сообщений для прослушивания сообщений, отправляемых Windows. To handle the messages, we first add a message loop to listen for the messages that Windows sends. Когда приложение получает сообщение, этот цикл отправляет его в функцию WndProc для обработки. When the application receives a message, this loop dispatches it to your WndProc function to be handled. Цикл обработки сообщений напоминает приведенный ниже код. The message loop resembles the following code.

Дополнительные сведения о структурах и функциях, используемых в цикле обработки сообщений, см. в разделах, посвященных MSG, GetMessage, TranslateMessageи DispatchMessage. For more information about the structures and functions in the message loop, see MSG, GetMessage, TranslateMessage, and DispatchMessage.

На этом этапе функция WinMain должна напоминать приведенный ниже код. At this point, the WinMain function should resemble the following code.

Добавление функциональных возможностей в функцию WndProc To add functionality to the WndProc function


Чтобы включить обработку получаемых приложением сообщений функцией WndProc , реализуйте оператор switch. To enable the WndProc function to handle the messages that the application receives, implement a switch statement.

Одно важное сообщение об обработке — это сообщение WM_PAINT . One important message to handle is the WM_PAINT message. Приложение получает WM_PAINT сообщение, когда часть его отображаемого окна необходимо обновить. The application receives the WM_PAINT message when part of its displayed window must be updated. Это событие может возникать, когда пользователь перемещает окно перед окном, а затем снова перемещает его. The event can occur when a user moves a window in front of your window, then moves it away again. Приложение не знает, когда происходят эти события. Your application doesn’t know when these events occur. Только Windows знает, поэтому она уведомляет ваше приложение с WM_PAINT сообщением. Only Windows knows, so it notifies your app with a WM_PAINT message. При первом отображении окна его все должно быть обновлено. When the window is first displayed, all of it must be updated.

Для обработки сообщения WM_PAINT сначала вызовите метод BeginPaint, далее обработайте логику расположения текста, кнопок и других элементов управления в окне, а затем вызовите метод EndPaint. To handle a WM_PAINT message, first call BeginPaint, then handle all the logic to lay out the text, buttons, and other controls in the window, and then call EndPaint. Для приложения логика между начальным вызовом и завершающим вызовом — отображение строки «Hello, Windows Desktop!» For the application, the logic between the beginning call and the ending call is to display the string «Hello, Windows desktop!» «Hello, World!». in the window. В приведенном ниже коде обратите внимание, что функция TextOut используется для отображения строки. In the following code, notice that the TextOut function is used to display the string.

HDC в коде является обработчиком контекста устройства, который представляет собой структуру данных, которую Windows использует для обеспечения взаимодействия приложения с графической подсистемой. HDC in the code is a handle to a device context, which is a data structure that Windows uses to enable your application to communicate with the graphics subsystem. Функции BeginPaint и EndPaint позволяют приложению работать как хороший член и не использовать контекст устройства дольше, чем требуется. The BeginPaint and EndPaint functions make your application behave like a good citizen and doesn’t use the device context for longer than it needs to. Функции помогают сделать графическую подсистему доступной для использования другими приложениями. The functions help make the graphics subsystem is available for use by other applications.

Приложение обычно обрабатывает много других сообщений. An application typically handles many other messages. Например, WM_CREATE при первом создании окна и WM_DESTROY при закрытии окна. For example, WM_CREATE when a window is first created, and WM_DESTROY when the window is closed. В приведенном ниже коде содержится базовое представление полной функции WndProc . The following code shows a basic but complete WndProc function.

Создание кода Build the code

Как обещано, вот полный код для рабочего приложения. As promised, here’s the complete code for the working application.

Сборка примера To build this example

Удалите код, введенный в хелловиндовсдесктоп. cpp в редакторе. Delete any code you’ve entered in HelloWindowsDesktop.cpp in the editor. Скопируйте этот пример кода и вставьте его в хелловиндовсдесктоп. cpp: Copy this example code and then paste it into HelloWindowsDesktop.cpp:

В меню Построение выберите Построить решение. On the Build menu, choose Build Solution. Результаты компиляции должны отобразиться в окне вывод в Visual Studio. The results of the compilation should appear in the Output window in Visual Studio.

Чтобы запустить приложение, нажмите клавишу F5. To run the application, press F5. Окно, содержащее текст «Hello, Windows Desktop!» A window that contains the text «Hello, Windows desktop!» должно отображаться в левом верхнем углу экрана. should appear in the upper-left corner of the display.

Поздравляем! Congratulations! Вы выполнили это пошаговое руководство и создали традиционное классическое приложение для Windows. You’ve completed this walkthrough and built a traditional Windows desktop application.

2D графика на основе WinApi C++ (2D graphics based on WinApi C++)

Для начала нам потребуется шаблон , на основе которого можно будет создавать дальнейшие приложения. Создайте в Visual Studio пустой проект (File → New → Project… → Visual C++ →Win32→Win32 Project → Application Settings → Empty Project→ Finish), создайте там main.cpp файл (Project → Add New Item…) и вставьте туда программный код (см. ниже).

Для запуска приложения используйте Visual Studio не старше 17 версии. В 19 версии появляются ошибки, которые не нашел способ исправить.

Если возникают ошибки из-за преобразования символов, то в окне свойств проекта установите «Использовать набор символов Юникода»:

При запуске программы должно появиться окно, залитое синим цветом.

Проект содержит файл main.cpp, ознакомьтесь с программным кодом.

Директива препроцессора #include открывает доступ к тысячам описаний констант, структур, типов данных и функций Windows.

WinAPI приложение является в своей основе процедурным приложением и содержит два основных модуля – функции WinMain и WndProc.

Функция WinMain составляет основу любого WinAPI приложения. Она служит как бы точкой входа в приложение и отвечает за следующее:

  • начальную инициализацию приложения;
  • создание и регистрацию объекта класса окна приложения;
  • создание и инициализацию цикла обработки событий.

Обработанное в бесконечном цикле событие переправляется (опосредовано через Windows) оконной функции WndProc. События в ней идентифицируются именем константы (WM_PAINT, WM_DESTROY и др.). Рисование осуществляется при помощи объектов типа HDC (дескриптор контекста устройства).

Рисуем треугольник в оконной СК

Нарисуем треугольник, задав координаты его вершин в оконной (физической) системе координат. Начало этой системы координат располагается в левом верхнем углу экрана. Ось X направлена слева направо, ось Y – сверху вниз. В качестве единицы длины в этой системе координат используется пиксель.

Для этого создадим заголовочный файл draw.h:

Еще создадим файл draw.cpp:

В файле main.cpp подключим draw.h:

А между заполнением области фоновым цветом и выводом изображения на основной контекст, появляется вызов процедуры рисования:

Рисуем мы на контексте-двойнике, а уже потом перекидываем изображение на основной контекст.

Логическая система координат

Оконная СК неудобна для пользователя из-за непривычного расположения осей (ось y направлена вниз), задания координат в пикселях и др. Устраним эти недостатки, используя логическую СК, в которой:

  • центр координат перенесен из левого верхнего угла в центр экрана;
  • направление оси Y меняется на противоположное;
  • координаты (от -1 до +1) соотнесены с шириной и высотой окна;
  • введен отступ от края окна (margin).

Преобразования из логической системы координат в оконную осуществляются при помощи зависимостей:

X_Window = MARGIN + (1.0/2)*(X_Log + 1)*(Width — 2 * MARGIN);

Y_Window = MARGIN + (-1.0/2)*(Y_Log — 1)*(Height — 2 * MARGIN);

Ниже приводится новый код модуля draw.cpp:

В draw.h добавляется объявление новой функции:

В модуль main.cpp добавляем обработчик события WM_SIZE и WM_ERASEBKGND:

Теперь, при изменении размеров окна наш треугольник подстраивается под новые размеры. При изменении размеров окна происходит перерисовка изображения, поскольку событие WM_SIZE автоматически вызывает событие WM_PAINT.

Контрольные вопросы

  1. Чем обусловлен переход от оконной СК к логической?
  2. Какие задачи решают функции Tx(0.0), Ty(0.5)?
  3. Какие задачи решает функция SetWindowSize?

Поворот плоскости

Алгоритмическая часть


Мы нарисовали треугольник в логической системе координат. Теперь обеспечим возможность поворота треугольника при каждом нажатии клавиш ← или →.

Для пересчета положения каждой вершины треугольника используем преобразование координат при повороте. Рассмотрим произвольный вектор r, задающий некоторую точку в системе координат ху:

При повороте на угол φ координаты точки запишутся в виде:

Выделим из этих уравнений 2 пары зависимостей – для пересчета координат точек в новой СК (1.1) и для обновления значений косинуса и синуса угла (1.2):

Для решения задачи пошагового вращения треугольника необходимо сохранять параметры предыдущего положения треугольника. В качестве таких параметров могут использоваться предыдущие координаты, либо суммарный угол поворота. Будем сохранять угловые характеристики – синусы и косинусы, поскольку именно они используются в выражениях (1.1 и 1.2).

В матричном представлении выражения (1) могут быть записаны в одном из следующих видов:

Программная реализация

В программе модифицированы все модули (main.cpp, draw.h, draw.cpp) и добавлены новые (geometry.h, matrix.h, matrix.cpp). В файле main.cpp добавляются обработчики WM_CREATE и WM_KEYPRESSED, клавиши ← и → идентифицируются в программе константами VK_LEFT и VK_RIGHT, начальный поворот плоскости инициализируется вызовом функции InitRotation, текущий поворот – функцией AddRotation. Перерисовка изображения (вызов события WM_PAINT) обеспечивается функцией InvalidateRect.

В файлы draw.h, draw.cpp и main.cpp вносим изменения, которые обеспечивают рисование треугольника в зависимости от текущего значения угла. В определение функций InitRotation и AddRotation входит вызов функции SetRotationMatrix, которая осуществляет инициализацию массива из 4-х тригонометрических характеристик угла. Переменная массива объявляется типом Matrix. Обновление массива реализует функция MultiplyMatrices. В функции Draw каждая вершина треугольника объявляется типом _Point. При каждом запуске функции Draw вершины инициализируются начальными координатами точек, затем функция ApplyMatrixtoPoint обеспечивает пересчет координат в соответствии с текущими значениями массива тригонометрических характеристик угла. Поворот точек треугольника реализуется в логической системе координат, затем осуществляется преобразование в оконные координаты и рисование сторон треугольника.

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

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

Контрольные задания:

  1. Согласно системы уравнений (1.2) sin и cos суммы 2-х углов, определяются по двум аргументам – sin и cos этих углов. В функции MultiplyMatrices(Matrix &dest, Matrix &left, Matrix &right), которая реализует в программе систему уравнений (1.2), используются 3 параметра (один параметр лишний). Модифицируйте функцию MultiplyMatrices, чтобы она принимала 2 параметра. Упрощенный аналог решения этого задания: Fun (&A, B)
  2. Внесите изменения в программу, которые обеспечат поворот треугольника на угол (PI/10) при нажатии на левую кнопку мышки (событие WM_LBUTTONDOWN) и поворот на угол (-PI/10) при нажатии на правую кнопку (событие WM_RBUTTONDOWN).

Анимация изображения

Мы уже обеспечили возможность поворота треугольника при каждом нажатии клавиш ← или →. Теперь поставим задачу обеспечения непрерывного вращения треугольника. Для реализации задачи необходимо обеспечить изменение угла поворота треугольника с последующим обновлением изображения. Причем, это должно выполняться в цикле.

Рассмотрим и оценим два способа циклического обновления изображения:

  1. Бесконечный цикл обработки сообщений (в функции WinMain);
  2. Цикл, осуществляемый через обработку события WM_TIMER.

В первом случае в бесконечный цикл помещаются 2 функции:

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

Если для анимации использовать бесконечный цикл обработки сообщений, то добиться точной скорости обновления изображения сложно. В WinAPI для работы со временем обычно используют сообщение WM_TIMER:

Это сообщение будет посылаться вашей программе через интервал времени, который вы зададите при создании таймера:

Создание таймера необходимо предусматривать до его использования, например – в событии WM_CREATE. Кроме этого необходимо в заголовке файла определить идентификатор таймера:

Уничтожение таймера предусматривается, когда необходимо остановить анимацию:

Контрольные вопросы и задания

  1. Почему нецелесообразно использовать бесконечный цикл обработки сообщений для анимации изображения?
  2. Обеспечьте при запуске программы вращение треугольника как стрелки секундомера.
  3. Обеспечьте возможность запуска вращения треугольника при нажатии на клавишу P (пуск) и остановку – при нажатии на клавишу S (stop). Пример оформления события нажатия на клавишу приводится ниже:

Аффинные преобразования треугольника

Рассматриваемое ниже приложение базируется на приложении, рассмотренном выше. Отличие лишь в том, что вместо матрицы поворота используется матрица аффинных преобразований в однородных координатах. При нажатии на клавишу «T» или перемещении мыши (при нажатой клавише CTRL) матрица инициализируется конкретными значениями, задавая соответствующие преобразования треугольника.

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

Header Files

action.h
engine.h
geometry.h
matrix.h
vec.h
viewport.h

Source Files

main.cpp
action.cpp
engine.cpp
matrix.cpp
viewport.cpp

В файлах описаны классы, в которых сгруппированы функции и переменные для комплексного решения задачи аффинных преобразований на плоскости.

Класс Engine объединяет функцию формирования изображения (Draw) и объекты классов Action и Viewport, обеспечивающие действия по перестройке изображения, вызванной событиями нажатия на кнопки, перемещения мышки и изменения размеров окна.

Класс Action объединяет действия и данные, связанные с аффинными преобразованиями. Функция Rotate (Translate) устанавливает преобразования вращения (перемещения), опираясь на текущее и предыдущее положения мыши. Функция InitAction запоминает координаты курсора в объекте класса vec. Эти функции вызываются при событиях нажатия кнопки мышки и перемещения курсора. Функция Transform устанавливает преобразования через инициализацию коэффициентов матрицы

Класс Viewport объединяет действия и данные, связанные с изменением размеров окна, Кроме этого, в класс включена функция T, которая выполняет преобразование точек фигуры из экранных координат экрана, в логические координаты, а также, функция T_inv , которая преобразует координаты курсора из экранных координат в логические. Функция T_inv отсутствовала в приложении, рассмотренном ранее, поскольку в нем реализовывалось лишь преобразование вращения, а значение угла поворота не зависело от СК. В этом приложении с помощью курсора задается и преобразование перемещения, а оно зависит от используемой СК.

В класс Matrix была добавлена функция SetTranslationMatrix, в которой непосредственно инициализируются коэффициенты матрицы преобразований.
В классе Vec переопределяются операции с векторами. Эти операции используются в программе для определения преобразования перемещения, опираясь на текущее и предыдущее положения курсора при перемещении мыши.

Контрольные задания

Задание 1. Определите коэффициенты матрицы элементарных аффинных преобразований в соответствии с вариантом задания

В каждый вариант задания включены по 3 преобразования

Пример оформления задания показан ниже на рисунке

Выполнить тестовые запуски приложения для проверки соответствия матриц заданным преобразованиям

Коэффициенты матрицы преобразования инициализируются в функции Matrix::SetTranslationMatrix1, которая определена в файле matrix.cpp. Преобразование осуществляется при нажатии клавиши T.


Задание 2. Определить произведение матриц AB, (AB)C и C(AB) и протестировать композицию элементарных преобразований запуском приложения. Преобразование осуществлять нажатием клавиши T.

Задание 3. Определить последовательность элементарных аффинных преобразований (преобразований не более 3-х), которые от начального положения треугольника приведут к заданному результату. Пример:

Для тестирования добавьте в программу дополнительные реакции на нажатие клавиш 1, 2 и 3, которые обеспечивают соответствующее элементарное преобразование:

Объявления и определения функций action->Transform вместе с входящими функциями описываются по аналогии с описанием действий при нажатии на клавишу “T”.

Задание 4. Добавьте в приложение программный код, обеспечивающий возможность переключения преобразований из глобальной СК в локальную СК. Для тестирования приложения переместите треугольник и поверните его при помощи мышки на угол 135 градусов. Если включена глобальная СК, вращение будет происходить относительно центра окна, если локальная СК – относительно вершины прямого угла.

В соответствии с алгоритмом для выполнения этого задания необходимо поменять местами текущую матрицу и матрицу, переданную в качестве параметра для функции MultiplyMatrices(Matrix &right).

Разбираемся в WinAPI

Для кого эта статья

Эта статья адресована таким же, как и я новичкам в программировании на С++ которые по воле случая или по желанию решили изучать WinAPI.
Хочу сразу предупредить:
Я не претендую на звание гуру по C++ или WinAPI.
Я только учусь и хочу привести здесь несколько примеров и советов которые облегчают мне изучение функций и механизмов WinAPI.

В данной статье я предполагаю что вы уже достаточно ознакомились с С++, что бы уметь создавать классы и перегружать для них различные операторы и что вы уже «прятали» какие-то свои механизмы в класс.

Создание и использование консоли

Для отладки Win32 приложения или просто для того что посмотреть как оно там всё внутри происходит я всегда пользуюсь консолью.
Так как вы создаете GUI приложение, а не консольное, то консоль не подключается. Для того что бы её вызвать в недрах интернета был найден вот этот код

if (AllocConsole())
<
int hCrt = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE), 4);
*stdout = *(::_fdopen(hCrt, «w»));
::setvbuf(stdout, NULL, _IONBF, 0);
*stderr = *(::_fdopen(hCrt, «w»));
::setvbuf(stderr, NULL, _IONBF, 0);
std::ios::sync_with_stdio();
>
Для удобства советую обернуть его в функцию. Например:
void CreateConsole()
<
if (AllocConsole())
<
int hCrt = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE), 4);
*stdout = *(::_fdopen(hCrt, «w»));
::setvbuf(stdout, NULL, _IONBF, 0);
*stderr = *(::_fdopen(hCrt, «w»));
::setvbuf(stderr, NULL, _IONBF, 0);
std::ios::sync_with_stdio();
>

Вызванная консоль работает только в режиме вывода и работает он также как и в консольных приложениях. Выводите информацию как и обычно — cout/wcout.
Для работоспособности данного кода необходимо включить в прект следующие файлы:
#include
#include #include
и включить пространство имен std в глобальное пространство имён:
using namespace std;
Конечно же, если вы не хотите этого делать, то просто допишите std:: ко всем сущностям которые в ней находятся.

Наследование объектов для вывода и арифм. операций

При создании и изучении самих «окошек» мне всегда требовалось выводить в консоль какое-нибудь значение.
Например:
Вы получаете размер клиентской области окна с помощью функции GetClientRect куда как параметр передается адрес объекта структуры RECT, что бы заполнить этот объект данными. Если вам нужно узнать размер полученной клиентский области вы просто можете вывести его в уже подключённую консоль

Но делать так каждый раз (особенно если вам часто приходиться делать что-то подобное) очень неудобно.
Здесь нам на помощь приходит наследование.
Создайте класс который открыто наследуется от структуры RECT и перегрузите оператор вывода class newrect:public RECT
<
public:
friend ostream& operator

Теперь просто выводите обьект с помощью cout/wcout:

И вам в удобном виде будет выводиться всё так, как вам требуется.
Так же вы можете сделать с любыми нужными вам операторами.
Например, если надо сравнивать или присваивать структуры (допустим тот же RECT или POINT) — перегрузите operator==() и operator=() соответственно.
Если хотите реализовать оператор меньше class BaseWindow
<
WNDCLASSEX _wcex;
TCHAR _className[30];
TCHAR _windowName[40];
HWND _hwnd;
bool _WindowCreation();
public:
BaseWindow(LPCTSTR windowName,HINSTANCE hInstance,DWORD style,UINT x,UINT y,UINT height,UINT width);
BaseWIndow(LPCTSTR windowName,HINSTANCE hInstance);
const HWND GetHWND()const
LPCTSTR GetWndName()const
>;

Один раз хорошенько продумав и написав такой класс вы облегчите себе жизнь и будете больше времени уделять обучению и оттачиванию навыков чем написанию одного и того же каждый раз. Тем более, я считаю это очень полезно — самому сделать такой класс и дополнять его по необходимости.

C++ — Использование STL-строк на границах Win32 API

Продукты и технологии:

C++, Standard Template Library, Visual Studio, Win32 API

В статье рассматриваются:

• Win32 API;

• передача стандартных STL-строк в качестве входных и выходных параметров;

• обработка потенциально возможного состояния гонок (race condition).

Win32 API предоставляет несколько средств, используя чистый C-интерфейс. Это означает, что при обмене текстом на границах Win32 API никаких строковых классов C++ нет. Вместо этого используются указатели на символы в стиле C. Например, Win32-функция SetWindowText имеет следующий прототип (из соответствующей документации MSDN на bit.ly/1Fkb5lw):

Строковый параметр выражается в форме LPCTSTR, что эквивалентно const TCHAR*. В Unicode-сборках (которые используются по умолчанию со времен Visual Studio 2005 и должны применяться в современных Windows-приложениях на C++) TCHAR typedef соответствует wchar_t, поэтому прототип SetWindowText читается как:

А значит, входная строка фактически передается как указатель-константа (т. е. только для чтения) на символ wchar_t с тем допущением, что указываемая строка заканчивается нулевым символом в классическом стиле чистого C. Это типичный шаблон для входных строковых параметров, передаваемых на границах Win32 API.

Конечно, применение C++ вместо чистого C — чрезвычайно продуктивный вариант разработки Windows-кода пользовательского режима и, в частности, Windows-приложений.

С другой стороны, выходные строки на границах Win32 API обычно представляются с использованием двух частей информации: указателя на буфер-получатель, выделенный вызвавшим кодом, и параметра размера, выражающего общую длину этого буфера. Примером является функция GetWindowText (bit.ly/1bAMkpA):

В этом случае информация, относящаяся к строковому буферу-получателю (выходной строковый параметр), хранится в последних двух параметрах: lpString и nMaxCount. Первый из них является указателем на буфер-приемник (представленный с использованием LPTSTR Win32 typedef, который транслируется в TCHAR* или wchar_t* в Unicode-сборках). Последний параметр, nMaxCount, представляет размер строкового буфера-получателя в wchar_ts; заметьте, что это значение включается завершающий нулевой символ (не забудьте, что строки в стиле C являются массивами символов, завершаемых нулевым символом).

В целом использование C++ поднимает семантический уровень кода и увеличивает эффективность труда программиста без негативного влияния на производительность приложения.

Конечно, применение C++ вместо чистого C — чрезвычайно продуктивный вариант разработки Windows-кода пользовательского режима и, в частности, Windows-приложений. В целом использование C++ поднимает семантический уровень кода и увеличивает эффективность труда программиста без негативного влияния на производительность приложения. В частности, работать со строковыми классами C++ гораздо лучше (легче, продуктивнее, менее чревато ошибками), чем иметь дело с массивами символов в стиле C, завершаемыми нулевыми символами.

Теперь возникает вопрос: какой вид строковых классов C++ можно использовать для взаимодействия с уровнем Win32 API, который изначально предоставляет интерфейсы для чистого C?

Класс CString из Active Template Library (ATL)/Microsoft Foundation Class Library (MFC) Что ж, этот класс — неплохой вариант. CString очень хорошо интегрируется с Windows-инфраструктурами C++ вроде ATL, MFC и Windows Template Library (WTL), которые упрощают Win32-программирование на C++. Так что, если вы используете эти инфраструктуры, имеет смысл применять CString для представления строк на специфичном для платформы уровне Win32 API приложений Windows на C++. Более того, CString предлагает удобные и специфичные для платформы Windows средства вроде возможности загружать строки из ресурсов и т. д.; это средства, зависимые от платформы, которые кросс-платформенная стандартная библиотека наподобие Standard Template Library (STL) просто по определению не в состоянии предложить. Поэтому, например, если вам нужно спроектировать и реализовать новый C++-класс, производный от какого-либо существующего ATL- или MFC-класса, подумайте о применении CString для представления строк.

Стандартные STL-строки Однако бывают случаи, где лучше использовать стандартный класс string в интерфейсе пользовательских C++-классов, образующих Windows-приложения. Например, вам может понадобиться абстрагировать уровень Win32 API в своем коде на C++ и предпочтительнее применять STL-класс строки вместо специфичных для Windows классов вроде CString в открытом интерфейсе пользовательских C++-классов. Давайте рассмотрим ситуацию, где текст хранится в строковых классах STL. В этом случае вам нужно передавать STL-строки через границы Win32 API (который предоставляет чистый C-интерфейс, как говорилось в начале статьи). При использовании ATL, WTL и MFC инфраструктура реализует «склеивающий» код между Win32 C-интерфейсом и CString, скрывая его «под капотом», но такое удобство недоступно в случае STL-строк.

Для целей этой статьи предположим, что строки хранятся в Unicode-формате UTF-16, который является кодировкой Unicode по умолчанию для Windows API. По сути, если бы эти строки использовали другой формат (скажем, Unicode UTF-8), их можно было бы преобразовывать в UTF-16 на границе Win32 API, чтобы удовлетворить ранее упомянутое требование этой статьи. Для таких преобразований применимы Win32-функции MultiByteToWideChar и WideCharToMultiByte: первую можно вызывать для преобразования из Unicode UTF-8 («мультибайтовой») строки в Unicode UTF-16 («широкосимвольную») строку, а последнюю — использовать для обратного преобразования.

В Visual C++ тип std::wstring хорошо подходит для представления строки Unicode UTF-16, поскольку его нижележащий тип символов — wchar_t, который имеет размер 16 битов в Visual C++, что совпадает с размерностью символа UTF-16. Заметьте, что на других платформах, таких как GCC Linux, wchar_t состоит из 32 битов, поэтому std::wstring на этих платформах хорошо подошел бы для представления текста, кодированного в формате Unicode UTF-32. Чтобы убрать эту неоднозначность, в C++11 ввели новый стандартный тип строк: std::u16string. Это специализация класса std::basic_string с элементами типа char16_t, т. е. с 16-битными символами.

Случай с входными строками

Если какая-то Win32-функция ожидает PCWSTR (или LPCWSTR в более старой терминологии), т. е. входной строковый параметр const wchar_t* с завершающим нулевым символом в стиле C, может сработать простой вызов метода std::wstring::c_str. По сути, этот метод возвращает указатель на строку в стиле C только для чтения.

CString очень хорошо интегрируется с Windows-инфраструктурами C++ вроде ATL, MFC и Windows Template Library (WTL), которые упрощают Win32-программирование на C++.

Например, чтобы задать текст заголовка окна или текст элемента управления, используя содержимое, хранящееся в std::wstring, Win32-функцию SetWindowText можно вызвать так:

Заметьте: хотя ATL/MFC CString обеспечивает неявное преобразование в «голый» const-указатель на символ (const TCHAR*, эквивалентный const wchar_t* в современных Unicode-сборках), STL-строки не поддерживают такое неявное преобразование. Вместо этого вы должны явно вызвать метод c_str STL-строки. В современном C++ сложилось общее понимание, что неявные преобразования — штука нехорошая, поэтому разработчики строковых STL-классов перешли на явно вызываемый метод c_str. (Соответствующее обсуждение отсутствия неявного преобразования в современных смарт-указателях STL вы найдете в публикации в блоге по ссылке bit.ly/1d9AGT4.)

Случай с выходными строками


В этом случае все немного усложняется. Обычный шаблон заключается в вызове какой-то Win32-функции, чтобы получить размер буфера-получателя для выходной строки. Размер может включать (а может и не включать) завершающий нулевой символ — вы должны прочитать документацию на эту Win32-функцию.

Затем вызывающий код динамически создает буфер должного размера. Размер этого буфера был определен на предыдущем этапе.

И наконец, вызовом другой Win32-функции содержимое строки считывается в буфер, созданный вызвавшим кодом.

Например, чтобы извлечь текст из элемента управления, можно вызвать API-функцию GetWindowTextLength и получить длину текстовой строки в wchar_ts. (Заметьте, что в этом случае возвращаемая длина не включает завершающий нулевой символ.)

Затем, используя эту длину, можно создать строковый буфер. Здесь одним из вариантов могло бы быть использование std::vector для управления строковым буфером:

Это проще, чем чистый вызов new wchar_t[bufferLength], потому что тогда потребовалось бы освобождать буфер вызовом delete[] (а если бы вы забыли об этом, появилась бы утечка памяти). Применение std::vector проще, даже если использование vector влечет за собой небольшие издержки по сравнению с чистым вызовом new[]. По сути, в этом случае деструктор std::vector автоматически удаляет выделенный буфер.

Это также помогает в написании C++-кода, безопасного при исключениях: если где-то в коде возникает исключение, автоматически вызывается деструктор std::vector. Тогда как буфер, динамически создаваемый new[], чей указатель хранится в исходном указателе-владельце (raw owning pointer), вызвал бы утечку памяти в такой ситуации.

Другой вариант, считающийся альтернативой std::vector, — использование std::unique_ptr, в частности std::unique_ptr . В этом варианте обеспечиваются автоматическая деструкция std::vector (и безопасность при исключениях) благодаря деструктору std::unique_ptr, а также меньшие издержки, чем в случае std::vector, поскольку std::unique_ptr — совсем крошечная C++-оболочка исходного указателя-владельца. В основном unique_ptr является указателем-владельцем, защищенным в безопасных границах RAII. RAII (bit.ly/1AbSa6k) — это очень распространенная идиома программирования на C++. Если вы не знакомы с ней, просто рассматривайте RAII как метод реализации, при котором автоматически вызывается delete[] обернутого указателя (например, в деструкторе unique_ptr), освобождая связанные ресурсы и предотвращая утечку памяти (и вообще утечку ресурсов).

С unique_ptr код мог бы выглядеть так:

Или следующим образом — при использовании std::make_unique (доступна в C++14 и реализована в Visual Studio 2013):

После создания буфера должного размера можно вызвать GetWindowText, передав указатель на этот буфер. Чтобы получить указатель на начало исходного буфера, управляемого std::vector, можно использовать метод std::vector::data (bit.ly/1I3ytEA), примерно так:

В случае unique_ptr вызывался бы его метод get:

И наконец, текст из элемента управления можно полностью скопировать (deep copied) из временного буфера в экземпляр std::wstring:

В предыдущем фрагменте кода я использовал перегрузку конструктора wstring, которая принимает константный исходный указатель wchar_t на входную строку с завершающим нулевым символом. Это работает нормально, поскольку вызываемый Win32 API вставит завершающий нулевой символ в строковый буфер-получатель, предоставленный вызвавшим кодом.

Для небольшой оптимизации, если длина строки (в wchar_ts) известна, вместо этого можно было бы использовать перегрузку конструктора wstring, которая принимает указатель и счетчик символов в строке. В этом случае длина строки предоставляется по месту вызова, и конструктору wstring не нужно находить ее (обычно с помощью операции O(N) подобно вызову wcslen в реализации Visual C++).

Сокращение для случая выходной строки: работа с std::wstring по месту

В отношении метода создания временного строкового буфера с использованием std::vector (или std::unique_ptr) и последующего его полного копирования and в std::wstring, вы могли бы пойти по кратчайшему пути. Экземпляр std::wstring можно было бы применять непосредственно как буфер-получатель, передаваемый в Win32-функции.

По сути, std::wstring имеет метод resize, который применим для формирования строки должного размера. Заметьте, что в этом случае вас не волнует начальное содержимое строки измененного размера, поскольку оно будет перезаписано вызванной Win32-функцией. На рис. 1 приведен фрагмент примера кода, показывающий, как считывать строки по месту, используя std::wstring..

Рис. 1. Чтение строк по месту, используя std::wstring

Я хотел бы прояснить несколько моментов, относящихся к коду на рис. 1.

Получаем доступ для записи к внутреннему строковому буферу Сначала рассмотрим вызов GetWindowText:

У программиста на C++ может возникнуть соблазн использовать метод std::wstring::data для доступа к внутреннему содержимому строки через указатель, передаваемый в вызове GetWindowText. Но wstring::data возвращает константный указатель, который не позволит модифицировать содержимое внутреннего строкового буфера. А поскольку GetWindowText ожидает доступа для записи к содержимому wstring, этот вызов не пройдет компиляцию. Поэтому альтернативой является применение синтаксиса &text[0] для получения адреса начала внутреннего строкового буфера, который должен быть передан как выходная строка (т. е. изменяемая) нужной Win32-функции.

По сравнению с предыдущим подходом этот метод более эффективен, так как нет временного std::vector с созданием буфера, последующим полным копированием в std::wstring и, наконец, его удалением. По сути, в этом случае код работает просто по месту — в экземпляре std::wstring.

Избегаем строк с двойными завершающими нулевыми символами Обратите внимание на последнюю строку кода на рис. 1:

При начальном вызове wstring::resize [text.resize(bufferLength); без коррекции «–1»] во внутреннем буфере wstring выделяется достаточно места, чтобы позволить Win32-функции GetWindowText влепить свой завершающий нулевой символ. Однако в дополнение к этому std::wstring неявно предоставляет другой завершающий нулевой символ. Таким образом, полученная строка завершается двумя нулевыми символами: один записывается GetWindowText, а другой автоматически добавляется wstring.

Чтобы исправить эту неправильную строку с двумя завершающими нулевыми символами, можно уменьшить размер экземпляра wstring, чтобы отбросить нулевой символ, записанный Win32-функцией, и оставить только нулевой символ, добавленный wstring. В этом заключается цель вызова text.resize(bufferLength–1).

Обработка состояния гонок

Прежде чем завершить эту статью, стоит обсудить, как обрабатывать потенциально возможное состояние гонок, которые могут возникнуть при работе с некоторыми API-функциями. Например, у вас есть код, который читает некое строковое значение из реестра Windows. Следуя шаблону, показанному в предыдущем разделе, программист на C++ сначала вызвал бы функцию RegQueryValueEx, чтобы получить длину строкового значения. Затем был бы создан буфер для этой строки, и, наконец, RegQueryValueEx была бы вызвана во второй раз, чтобы считать строковое значение в буфер, созданный на предыдущем шаге.

Состояние гонок, которое могло бы возникнуть в этом случае, — другой процесс, модифицирующий это строковое значение между двумя вызовами RegQueryValueEx. Длина строки, возвращенная первый вызовом, могла бы оказаться бессмысленным значением, не относящимся к новому строковому значению, записанному в реестр другим процессом. Таким образом, второй вызов RegQueryValueEx считал бы новую строку в буфер с неправильным размером.

Чтобы устранить эту ошибку, вы можете использовать шаблон кодирования наподобие представленного на рис. 2.

Рис. 2. Пример шаблона кодирования для обработки потенциально возможного состояния гонок в получении строк

Использование цикла while на рис. 2 гарантирует, что строка читается в буфер должного размера, так как каждый раз, когда возвращается ERROR_MORE_DATA, создается новый буфер с правильным значением bufferLength, и так до тех пор, пока API-вызов не закончится успехом (вернув ERROR_SUCCESS) или провалом по причине, отличной от неправильного размера буфера.

По сути, std::wstring имеет метод resize, который применим для формирования строки должного размера.

Заметьте, что фрагмент кода на рис. 2 — это не более чем пример скелетного кода; другие Win32-функции могут использовать другие коды ошибок, относящиеся к недостаточному размеру буфера, который предоставлен вызвавшим кодом, например код ERROR_INSUFFICIENT_BUFFER.

Заключение

Хотя использование CString на границе Win32 API (с помощью таких инфраструктур, как ATL/WTL и MFC) скрывает механику взаимодействия с уровнем чистых C-интерфейсов Win32, при работе с STL-строками программист на C++ должен обращать внимание на определенные детали. В этой статье я обсудил некоторые шаблоны кодирования для взаимодействия STL-класса wstring и Win32-функций с чистым C-интерфейсом. В случае ввода вызов метода c_str из wstring прекрасно подходит для передачи входной строки на границу Win32 C-интерфейса в форме простого константного (только для чтения) указателя на символ строки, завершаемой нулевым символом. В случае выходных строк вызывающий код должен создавать временный строковый буфер. Этого можно добиться, используя либо STL-класс std::vector, либо (с чуть меньшими издержками) STL-класс шаблона смарт-указателя std::unique_ptr. Другой вариант — задействовать метод wstring::resize, чтобы выделять некое пространство внутри экземпляра строки под буфер для Win32-функций. В этом случае важно указывать достаточное пространство, чтобы вызванная Win32-функция могла вписать свой завершающий нулевой символ, а затем уменьшать его размер, чтобы отбросить этот символ и оставить только завершающий нулевой символ от wstring. Наконец, я кратко рассмотрел потенциально возможное состояние гонок и представил пример шаблона кодирования для решения этой проблемы.

Джованни Диканио (Giovanni Dicanio) — программист, специализирующийся на C++ и операционной системе Windows, автор на Pluralsight. Является обладателем звания Visual C++ MVP. Помимо программирования и создания учебных курсов, любит помогать другим людям на форумах и в сообществах, преданных C++. С ним можно связаться по адресу giovanni.dicanio@gmail.com.

Выражаю благодарность за рецензирование статьи экспертам Дэвиду Крейви (David Cravey) (из GlobalSCAPE) и Стивену Т. Лававею (Stephan T. Lavavej) (из Microsoft).

xydan

Пережеванные выкладки программиста

или же трудные будни ленивца-скурпулезы

Данная статья достаточно важна, так как открывает тонкий аспект при создании объекта ядра windows , такое как – “событие ( Event )”.
Данный аспект коснулся меня достаточно недавно, из-за чего я потратил много времени в поисках глюка своей программы. Причина оказалась достаточно банальна, но в том месте, где я никак не ожидал ее обнаружить. Все дело было формировании объекта – события.

Скажу коротко, что мой проект, которым я занимаюсь в данное время, состоит из множества событий, типа Events , то есть это не те события, что принято считать событиями в RAD оболочках типа Builder или Delphi или того же Visual Studio (то есть которые передаются с параметрами через систему сообщений windows ). Это события ядра, которые работают через ф-ции WinApi – WaitForSingleObject или же WaitForMultipleObjects .
Смысл данного события, освободить какой-либо поток от ожидания, по средством данной ф-ции, приведенной выше.

Начну с того, что приведу основные ф-ции и их последовательность для написания события. Буду приводить ф-ции WinApi , так как в том же Builder С++ есть VCL методы, которые являются просто оберткой для тех же WinApi ф-ций.

Для начала надо событие создать само событие, ф-цией CreateEvent .
Синтаксис:
HANDLE CreateEvent (
LPSECURITY _ ATTRIBUTES lpEventAttributes , // атрибут доступа
BOOL bManualReset , // флаг указывающий тип сброса (автомат, ручной)
BOOL bInitialState , // флаг указывающий на начальное состояние
LPCTSTR lpName // имя объекта ядра
);

Тут главными параметрами является флаг, указывающий тип сброса (основное отличие типов сбросов является то, что если мы укажем true – у нас будет событие с ручным сбросом, и таким событием мы сможем возобновить работу сразу нескольких потоков, если же это будет событие с авто-сбросом ( false ), то таким событием возможно будет возобновить работу только одного ждущего потока).
Следующий флаг, начальное состояние – тут все более понятно – если true то событие свободно, если false то занято.
Далее очень важный параметр – имя объекта ядра. Вот тут надо быть очень аккуратным, собственно тут я и накололся. Именую какой либо объект ядра в системе мы можем наткнутся на одинаково названные объекты, скажем в другом процессе. Таким способом мы можем нечаянно перехватить управление объектом на себя, и, думая, что управляем своим объектом, в своем процессе на самом деле влиять на объект чужого процесса. Но тут не так все просто, при таком перехвате, прежде всего, проверяется уровень доступа процесса к данному объекту ядра и если права доступа имеются, то в таблице дескрипторов нашего процесса создается новая запись с дескриптором hEvent . Проще говоря, мы получаем доступ к уже созданному в другом процессе объекту ядра (скажем событию), и можем на него влиять. Естественно, что счетчик пользователей созданного объекта ядра увеличивается на единицу.
Если же объекты разного типа, но называются в системе одинаково (скажем, событие и семафор), то тогда наша ф-ция не сможет создать событие, и выдаст нулевой handle .
Тут можно посоветовать одно, либо создавать уникальные имена событий, либо, читаем далее, проверять на наличие созданного объекта в других процессах следующей ф-цией.

Итак:
HANDLE OpenEvent (
DWORD dwDesiredAccess // Требуемый доступ
BOOL bInheritHandle , // флаг опции наследования
LPCTSTR lpName // текстовое имя объекта, к которому хотим получить доступ
);


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

Опция dwDesiredAccess – может иметь следующие константы :
EVENT_ALL_ACCESS – Полный доступ к объекту ;
EVENT_MODIFY_STATE — Разрешено
использовать дескриптор объекта в ф — циях SetEvent и ResetEvent;
SYNCHRONIZE (Windows NT only) – Разрешено
использовать дескриптор объекта в любых wait- функциях .

Отмечу, что тип доступа в CreateEvent и OpenEvent должны совпадать, иначе будет ошибка доступа к объекту. Собственно это главное .

Далее bInheritHandle – определяет разрешение наследования данного объекта при создании дочерних процессов. По английский говоря (Specifies whether the returned handle is inheritable. If TRUE, a process created by the CreateProcess function can inherit the handle; otherwise, the handle cannot be inherited.)

Ну и самое главное, как освободить и занять событие.

BOOL SetEvent (
HANDLE hEvent // Хэндл объекта, который возвращает ф-ция CreateEvent или OpenEvent .
);
Данной ф-цией мы установим объект события в свободное состояние. Это значит, что если программа наткнулась на функцию, скажем, WaitForSingleObject с параметром INFINITE , то программа остановится на этой ф-ции и будет ждать до посинения, пока мы в каком либо потоке не запустим ф-ция SetEvent , после которой, код, написанный после WaitForSingleObject продолжит выполнятся.

Ф-ция ResetEvent имеет одинаковый синтаксис с SetEvent . Данная ф-ция работает с событиями РУЧНОГО типа. Если к примеру, ф-ция WaitForSingleObject встретится нам еще раз где то в коде, причем того же объекта, пример которого я привел в описании SetEvent , то если где то не установить событие в занятое состояние, при встрече WaitForSingleObject код не остановится, а продолжит свое выполнение как ни в чем не бывало. Следовательно, после данной ф-ции ( WaitForSingleObject ) нужно ставить ResetEvent , если вы конечно хотите, чтобы WaitForSingleObject остановил поток еще раз.
В случае если вы создали событие с АВТОСБРОСОМ, тогда никакого ResetEvent вам не понадобится, после освобождения ф-ции WaitForSingleObject , она автоматический придет в занятое состояние и когда в коде встретится еще раз ф-ция WaitForSingleObject данного объекта, то код в очередной раз остановит свое выполнение.

А теперь внимательно – тонкость!
К примеру, Вы не хотите именовать объекты строковым параметром, а хотите лишь управлять своим объектом только в своем процессе и вместо имени задаете параметр NULL во всех своих событиях. Получаете два разных адреса ( handle ), то есть два совершенно разных объекта одного типа и казалось бы, что еще нужно? Управляем разными объектами только обращаясь к ним по уникальному адресу, записанному в handle , но что тут происходит? Почему события работают неправильно, все глючит и самое главное, освобождая одно событие мы освобождаем все события процесса и наоборот, занимая одно событие, занимаем сразу все! Что же такое? Почему?
Все дело в том, что создавая неименованные события, система каким то образом отожествляет их как одно событие процесса, поэтому обязательно нужно именовать уникальным именем все свои события, тогда все события будут работать так, как полагается.

В заключение скажу, будьте очень внимательны работая с объектами ядра, потому как незначительные вещи могут глобально влиять на весь ваш процесс.

P . s . Тысячу слов никогда не заменят хорошего примера, так вот пример с исходным кодом вы можете скачать на моей странице www . xydan . narod . ru
Пример выполнен в виде проекта на Borland C++ Builder 6. Чтобы ознакомится с указанным глюком, поменяйте названия, при создании объекта ядра на нулевые (NULL), и увидите как после этого будут работать события.
Если вы хотите собрать проект в любой другой оболочке, то просто замените классовые методы работы с событиями на ф-ции WinApi. Посмотрев код, можно легко определить где что заменять, поскольку синтаксис WinApi ф-ции и VCL методов в работе с событиями — одинаковый.

Введение в API-программирование / Использование API / Visual C++ .NET

Введение в API-программирование

API это программный интерфейс приложения. Другими словами, это те возможности, которые предоставляет операционная система Windows для использования прикладными программыми. Системные функции, которые предоставляет Windows программисту, называются ещё функциями API. Программирование с использованием только этих функций называется API-программированием. В данной статье мы дадим достаточно широкое введение в API-программы.

Структура API-программ

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

Классическая структура API-программы определяется четырьмя компонентами: инициализация; цикл ожидания, или цикл обработки сообщений; функция главного окна; другие функции. В простейшем случае последний компонент может отсутствовать. Два первых компонента распологаятся в функции WinMain. Итак, все по порядку.

Функция WinMain

Функция WinMain вызывается системой, в которую передаются четыре параметра:

  • hInstance — дискриптор текушего экземпляра приложения.
  • hPrevInctance — всегда равен NULL.
  • lpCmdLine — указатель на командную строку запускаемой программы.
  • nCmdShow — способ визуализации окна.

Инициализация

Если кратко, то здесь производится регистрация класса окон, его создание и вывод на икран. Регистрация классв окон осуществляется функцией:

Пусть вас не смущает тип ATOM, для нас это просто int. Единственный параметр функции указатель на структуру WNDCLASS. После того как класс будет зарегестрировани, окно из данного класса может быть создано функцией CreateWindow. Разберём теперь структуру WNDCLASS:

Как видите, в структуре нет ничего сложного. Если регистрация прошла успешно, о чём мы узнаем по нулевому возвращенному значению, значит можно создавать окно.

Перечислим некоторые типичные значения членов структуры:

  • Стили класса окон. Стиль окна определяется комбинацией нескольких предопределённых констант. Довольно часто он пологается нулю, что означает «стиль по умолчанию».
  • Дискриптор иконки окна. Определяется с помощью функции LoadIcon. Первым параметром данной функции является дискриптор приложения, вторы — строка, определяющая имя иконки в ресурсах. Для того чтобы чтобы задать одну из стандартных иконок, первый параметр должен иметь значение NULL, а второй значение одной из следующих констант: IDI_APLICATION — стандартнаю иконка приложения; IDI_ASTERISK — иконка «информация»; IDI_EXCLAMATION — «восклицательный знак»; IDI_HAND — «знак Стоп»; IDI_QUESTION — «вопросительный знак».
  • Дискриптор курсора. Для определения курсора используется API-функция LoadCursor. Функция похожа на функцию LoadIcon.
  • Имя класса. Название класса — это просто строка, которая потом используется при создании окна.

Со значениями других членов структуры мы познакомимся позднее.
Окно создаётся функцией CreteWindow. Вот прототип этой функции:

Использование данной функции мы разберём позднее. Главное здесь то, что функция возвращает дискриптор созданного окна, при ошибке — 0.
Для того чтобы корректно отобразить окно на экране, следует выполнить ещё две функции.

BOOL ShowWindow(HWND hWnd, int nCmdShow) — эта функция отображает окно на экране. Первый параметр — дискриптор окна, второй — режим отображения. В качестве этого параметра обычно используют параметр nWinMode функции WinMain. Можно также использовать предопределённые константы:

  • SW_HIDE — скрыть окно
  • SW_MAXIMIZE — максимизировать окно
  • SW_MINIMIZE — минимизировать окно и активировать самое верхнее окно
  • SW_RESTORE — отобразить окно в нормальном состоянии
  • SW_SHOW — активизировать окнос текущими разменами
  • SW_SHOWMAXIMIZED — максимизировать окно и сделать его активным
  • SW_SHOWMINIMIZED — минимизировать окно
  • SW_SHOWNA — отобразить окно в его текущем состоянии. При этом активированое окно оставить активным.
  • SW_SHOWNOACTIVATE — востанавливает окно в его предыдушем состоянии. При этом активное окно остаётся активным.
  • SW_SHOWNORMAL — активизировать и востановить окно в прежних размерах.

BOOL UpdateWindow(HWND hWnd) — вызов данной функции приводит к немедленной перерисовке окна и посылке функции окна сообщения WM_PAINT.

Цикл обработки сообщений

Цикл обработки сообщений присутствует во всех проложеният Windows. Правда, не всегда этот цикл представлен явно в программе.

В цикле сообщения присутствует три функции. Эти функции есть там всегда, но кроме них в цикле могут быть и другие. Функция GetMessage выбирает из очереди сообщений приложения очередное приложение. вместо этой функции используют так же функции PostMessage и PeekMessage.
Во всех трех функциях присутствует указатель на строку MSG. Разберём её:

  • hwnd — дискриптор окна.
  • message — код сообщения.
  • wParam — дополнительный параметр.
  • lParam — дополнительный параметр.
  • time — время посылки сообщения.
  • pt — положение курсора мыши.

Прототип функции MessageBox.

Первый параметр функции — указатель на строку MSG, куда и будет помещена получаемая информация. Вторым параметром является дискриптор окна, которому предназначено сообщение. Если параметр равен NULL, то «отталкиваются» все сообщения, получаемые приложением. Два последних параметра определяют диапазон сообщений. Для того чтобы получать сообщения из всего диапазона, эти параметры должны быть равны 0.

Функция TransleteMessage преабразует сообщения WM_KEYDOWN и WM_KEYUP в WM_CHAR. Функция DispatchMessage просто переправляет сообщение оконной процедуре.

Оконная функция

Это еще один компонент отвечающий за обработку сообщений окна. Эта функция вызывается системой и имеет четыре параметра, совпадающих с первыми четырбмя членами структуры MSG. Искуство API-программирования заключается в основном в написании оконных функций. Вот пример оконной функции:

Примеры

Среда Visual C++ оказывает некоторую помощь желающим написать API-программы. Вы можете воспользоваться одной из двух возможностей. В списке проектов выбираем Win32 Project и далее следуем одним из двух путей: отменить флажок Empty project или нет. Если не отмечать флажок, то система создаст простейшее оконное приложение с минимальной функциональностью, позволяющей, однако, развивать ваш проект и создавать приложения любой степени сложности.

И так, начнём. Выбираем путь, простой проект. Откроем окно Solution Explorer. Как и следовало ожидать, проект пуст. Щелкним правой кнопкой мыщи по строке проекта и выбирем пункт Add new Item, тип файла — CPP и введём имя api1.cpp. Теперь мы свами можем воспользоваться тем что с вами иже узнали. Как сидите мы обощлись минимальным количеством файлов. Пожключаемый файл windows.h содержит определения всех API-функций, а также необходимых для их использования констант и типов данных. Вот код этой программы:


Простейшая программа WinAPI на C++

Posted by bullvinkle under Журнал, Статьи

Многие, кто переходит с «учебного» ДОСовского компилятора вроде Borland C++ на визуальное программирование быстро запутываются в сложных библиотеках типа MFC или VCL, особенно из-за того, что новые создаваемые проекты уже содержат с десяток файлов и сложную структуру классов. Рано или поздно встает вопрос: «…а почему нельзя написать оконную программу с простой линейной структурой, состоящую из одного файла .cpp?» На самом деле можно. Для этого нужно работать с низкоуровневыми функциями операционной системы – API.

Простейшая программа WinAPI на C++

Дмитрий Федорков

Windows API (application programming interfaces) – общее наименование целого набора базовых функций интерфейсов, программирования приложений операционных систем семейств Windows и Windows NT корпорации «Майкрософт». Является самым прямым способом взаимодействия приложений с Windows.

Зачем нам вообще API

Все что делает любая программа – делает либо непосредственно с помощью инструкций процессора, либо обращаясь к функциям биоса (хотя их прямые вызовы используются всё реже), либо через системные функции (API). К последним относится: прорисовка окон, получение координат мыши, чтение файлов и т. д.

WinAPI – это основа, в который должен разбираться любой программист, пишущий под Windows, независимо от того, использует ли он библиотеки вроде MFC (Microsoft Visual C++) или VCL (Borland Delphi / C++ Builder). Часто бывает проще написать простенькую программу, состоящую из одного файла, чем настраивать относительно сложный проект, созданный визардами. Я не говорю уже, что программа получается гораздо оптимизированнее (всё-таки низкоуровневое программирование) и в несколько раз меньше. К тому же у них не возникает проблем совместимости, если у конечного пользователя не хватает каких-т
о библиотек, чем иногда грешит MFC.

Наша программа

Напишем простую программу: окно, в нем – синусоида, которая движется влево, как график функции

y = sin (x + t). Если кликнуть мышкой по окну, анимация приостановится, или наоборот продолжится. Чтобы было проще разобраться, я сразу приведу весь исходный код, а потом прокомментирую ключевые места. Попробуйте самостоятельно модифицировать разные части программы, пробуйте оптимизировать мою программу, может вам даже удастся найти ошибки в коде (см. листинг):

тестовая программа

LRESULT CALLBACK WindowProc (HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;

wc.hCursor = LoadCursor (NULL, IDC_ARROW);

HWND hWnd = CreateWindow (L”CMyWnd”, L”WinMain sample”, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 320, 240, NULL, NULL, hInstance, NULL);

ShowWindow (hWnd, nCmdShow);

// Message loop (timer, etc)

SetTimer (hWnd, 1, USER_TIMER_MINIMUM, NULL);

while (GetMessage(&msg,NULL,0,0) > 0)// while not WM_QUIT (0) nor some error (-1)

// Message processing function

LRESULT CALLBACK WindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

static bool Move = true;

static int Phase=0, Width, Height;

Rectangle (dc, -1, -1, Width+1, Height+1);

MoveToEx (dc, 0, Height * (0.5 + 0.3*sin(0.1*Phase)), NULL);

if (wParam != VK_ESCAPE)

return DefWindowProc (hWnd, message, wParam, lParam);

Обращаю ваше внимание на то, что эта программа писалась под Visual C++. У Билдера может быть проблема из-за заголовка , вместо него нужен . Для этой программы понадобится пустой проект с единственным файлом .cpp. В Visual Studio в свойствах создаваемого проекта нужно отметить галочку «Empty project». Итак, приступим…

Пройдемся по коду

В программе мы добавляем заголовочный файл , который нужен для расчета синусоиды, и , который содержит все функции WinAPI. Строчка #define WIN32_LEAN_AND_MEAN отключает некоторые редко используемые функции и ускоряет компиляцию.

Функцию WindowProc() пока пропустим, оставив на сладкое.

HDC – контекст устройства рисования. Не будем подробно останавливаться на графике – это не основная тема статьи, да и используется не очень часто. Скажу лишь, что эта переменная глобальная, потому что используется в обеих функциях. Надо добавить, что буква ”H” в типе данных WinAPI (в “HDC”) обычно означает ”Handle” (хэндл), т.е. переменную, дающую доступ к самым разным устройствам, объектам и прочим сущностям WinAPI. Хэндл – представляет собой обычный указатель, работа с которым зависит от контекста (от типа переменной). Вообще, хэндлы – сложная тема, без к
оторой тоже поначалу вполне можно обойтись.

Теперь главное (main) – точка входа. В консольных программах функция main может возвращать либо void, либо int, а также может иметь или не иметь аргументы (int argc, char **argv). Итого 4 варианта. В нашем случае используется функция WinMain(), которая может иметь только такой вид, как в примере. Слово WINAPI (которое подменяется препроцессором на __stdcall) означает, что аргументы функции передаются через стек*. Аргумент HINSTANCE hInstance — хэндл текущего процесса, который бывает нужен в некоторых ситуациях. Назначение следующего аргумента HINSTANCE hPrevInstance весьма смутное, известно только, что э
та переменная всегда равна NULL. В исходниках квейка можно даже найти такую строчку: if (hPrevInstance != NULL) return 0.

* подробнее – в учебниках по ассемблеру

Аргумент LPSTR lpCmdLine – командная строка. В отличие от консольного main (int argc, char **argv), эта строка не разделена на отдельные аргументы и включает имя самой программы (что-нибудь типа “C:\WINDOWS\system32\format.com C: \u”). Далее int nCmdShow определяет параметры окна, указанные например, в свойствах ярлыка (это будет нужно при создании окна).

Перейдем, наконец, к выполняемому коду. В первую очередь нам нужно создать окно. Структура WNDCLASS хранит свойства окна, например текст заголовка и значок. 4-ре из 9-ти полей структуры должны быть нулевые, поэтому сразу инициализируем ее нулями. Далее CS_HREDRAW | CS_VREDRAW означает, что окно будет перерисовываться при изменении размера окна. wc.hInstance задаёт текущий процесс (тут-то и понадобился этот аргумент из WinMain). Еще также нужно явно указать мышиный курсор, иначе, если это поле оставить нулевым, курсор не будет меняться, скажем, при переходе с границы окна на са
мо окно (попробуйте сами). wc.lpfnWndProc – это адрес функции, которая будет обрабатывать все события. Такие как нажатие клавиши, движение мыши, перетаскивание окна и т. д. После знака ”=” просто указываем имя нашей функции. Позже мы напишем эту функцию, которая и будет определять реакцию программы на интересующие нас события.

WNDCLASS – это не само окно, а класс (форма), экземпляр которого и будет нашим окном. Но перед созданием окна нужно зарегистрировать в системе этот класс. Задаем его имя в системе CMyWnd и регистрируем класс.

Функция создания окна CreateWindow() достаточно простая и вместо того, чтобы перечислять все ее аргументы, опять сошлюсь на интернет. Кому мало одиннадцати аргументов, могут попробовать функцию CreateWindowEx(). Обратите внимание – все строковые аргументы предваряются буквой L, что означает, что они – юникодовые. Для многих функций WinAPI существует по два варианта: обычный ANSI и юникодовый. Соответственно они имеют суффикс A или W, например CreateWindowA и CreateWindowW. Если вы посмотрите определение функции в , то увидите что-то типа #define CreateWindow
CreateWindowW
. Вместо CreateWindow() мы можем явно вызывать CreateWindowA() с обычными строками (без приставки L).

Описание GetDC() и ShowWindow() снова пропущу (кому интересно – тот легко найдет).

Дальше начинается самое интересное – работа с событиями. Для начала создадим таймер, который будет генерировать соответствующее событие 65 раз в секунду (фактически максимальная частота, по крайней мере для Windows XP). Если вместо последнего аргумента SetTimer() написать имя подходящей функции, она будет вызываться вместо генерации события.

Далее идет то, что называется message loop – цикл обработки событий. Мы принимаем событие и обрабатываем его. В нашем случае можно убрать TranslateMessage(&msg), но эта функция понадобится, если на основе этого примера кто-нибудь будет создавать более сложную программу (с обработкой скан-кодов клавиатуры). Если мы получаем событие выхода программы, то GetMessage() возвращает ноль. В случае ошибки возвращается отрицательное значение. В обоих случаях выходим из цикла и возвращаем код выхода программы.

Теперь займемся функцией обработки событий WindowProc(), которую мы оставили на сладкое. Эта функция вызывается при любом событии. Какое именно сейчас у нас событие – определяется аргументом message. Дополнительные параметры (например, координаты мыши в событии “мышь двинулась”) находятся в аргументах wParam и lParam. В зависимости от того, чему равно message, мы совершаем те или иные (или вообще никакие) действия, а потом в любом случае вызываем DefWindowProc, чтобы не блокировать естественные реакции окна на ра
зные события.

Вообще то, что я сделал с оператором switch больше похоже на стиль ассемблера и порицается большинством серьезных разработчиков. Я имею в виду сквозные переходы между case- ми (там, где нет break). Но пример простой, к тому же у меня было настроение “похакерить”, так что не буду лишать вас удовольствия разобраться в этом коде.

Имена констант message говорят сами за себя и уже знакомы тем, кто работал с MFC. При событии WM_PAINT рисуем белый прямоугольник, а на нём — чёрную синусоиду. На каждый WM_TIMER смещаем фазу синусоиды и перерисовываем ее. На клик мыши запускаем или останавливаем движение, при этом, если нажать обе кнопки одновременно, то фаза увеличится ровно на 1, для чего здесь и убран break (см. рисунок). При изменении размера окна мы обновляем переменные Width и Height за счёт того, что в lParam хранятся новые размеры. Всегда нужно вручную обрабатывать собы

Цукерберг рекомендует:  Вакансии Atlas Delivery
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих