05. Механизм сообщений в операционных системах

Лекция №5. Механизм сообщений в операционных системах

Курс: Системное программирование (2026–2027)
Учебная программа: 2025, регистрационный № УП-46/2025Пп/уч
Специальность переподготовки: 9-09-0612-02 «Программное обеспечение информационных систем»
Квалификация: Инженер-программист
Тема по программе: Тема 5. Механизм сообщений в операционных системах (4 академических часа)
Формируемые компетенции: СП-23, СП-24


Введение и цели занятия

📌 Слайд 1: Тема 5. Механизм сообщений в операционных системах

В традиционном консольном программировании поток выполнения строго линеен и монологичен: программа сама последовательно определяет, когда запрашивать данные от пользователя (например, через блокирующий вызов scanf или cin), когда производить вычисления и когда выводить текстовый результат. Однако современный графический интерфейс пользователя (GUI) построен на фундаментально иной архитектурной парадигме — событийно-ориентированном программировании (Event-Driven Programming) и инверсии управления (Inversion of Control).

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

Цель настоящей лекции — всесторонне изучить архитектуру оконной подсистемы операционной системы, регистрацию и жизненный цикл оконных классов (WNDCLASSEXW), устройство очередей сообщений потока, механизм взаимодействия Raw Input Thread с пользовательскими окнами, внутреннее устройство цикла выборки (GetMessage / PeekMessage), различие синхронного (SendMessage) и асинхронного (PostMessage) обмена данными, а также системные интерфейсы низкоуровневой обработки событий мыши и клавиатуры.

📌 Слайд 2: План лекции

Учебные вопросы лекции:

  1. Парадигмы консольного и графического пользовательского интерфейса. Инверсия управления.
  2. Архитектура класса окна в операционной системе Windows (WNDCLASSEXW) и создание дескриптора HWND.
  3. Предопределенные оконные классы операционной системы и динамическое управление данными окна (GetWindowLongPtrW / SetWindowLongPtrW).
  4. Понятие оконного сообщения. Структура MSG, параметры WPARAM и LPARAM, классификация и диапазоны идентификаторов.
  5. Архитектура очередей сообщений: системный поток необработанного ввода (Raw Input Thread, RIT), потоковая очередь и синтетические флаги.
  6. Синхронное (SendMessage) и асинхронное (PostMessage) взаимодействие. Главный цикл выборки сообщений (GetMessage, TranslateMessage, DispatchMessage).
  7. Оконная процедура (Window Procedure) и функция обработки по умолчанию (DefWindowProcW).
  8. Системные интерфейсы обработки мышиного ввода: клиентские и неклиентские сообщения, хит-тестинг (WM_NCHITTEST), захват мыши (SetCapture).
  9. Системные интерфейсы обработки клавиатурного ввода: скан-коды, виртуальные коды клавиш (Virtual-Key), фокус ввода и генерация текстовых символов WM_CHAR.

1. Парадигмы консольного и оконного интерфейса

📌 Слайд 3: Консольная парадигма против событийного оконного интерфейса

Исторически первой моделью взаимодействия человека с ЭВМ являлась консольная (командно-строчная) парадигма:

  • Линейный детерминированный поток управления: программа имеет единую точку входа main(), последовательно инициализирует ресурсы, выполняет алгоритм и завершает работу.
  • Блокирующий синхронный ввод: когда программе требуются входные данные, она вызывает блокирующую функцию (scanf, getchar, ReadFile(hStdIn)). Поток переводится ядром ОС в состояние ожидания (Blocked) до тех пор, пока пользователь не нажмет клавишу Enter. Во время этого ожидания приложение не может реагировать ни на какие другие действия.

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

Для решения этой задачи графические ОС используют событийно-ориентированную парадигму:

  1. Инверсия управления (Inversion of Control, IoC): программист больше не управляет главным потоком инструкций. Главным потоком управляет операционная система, которая опрашивает устройства ввода и вызывает зарегистрированные программистом функции обратного вызова (Callbacks). В программировании этот принцип известен как «Голливудский принцип» (Hollywood Principle): «Не звоните нам, мы сами вам перезвоним».
  2. Цикл обработки сообщений (Message Loop): поток создает одно или несколько окон и входит в бесконечный цикл выборки сообщений из своей системной очереди.
  3. Неблокирующая диспетчеризация: обработка каждого отдельного сообщения должна занимать минимальное время (миллисекунды), чтобы окно не теряло интерактивности и не переходило в состояние «Не отвечает» (Not Responding).

📌 Слайд 4: Парадигмы программирования: Консоль vs Событийно-ориентированный GUI

center


2. Архитектура класса окна и создание экземпляра HWND

📌 Слайд 5: Архитектура оконного класса: WNDCLASSEXW

В операционной системе Windows окно не может быть создано «из ничего». Любое окно является конкретным экземпляром предварительно зарегистрированного класса окна (Window Class). Класс окна задает фундаментальные свойства и атрибуты, общие для всех окон данного типа, важнейшим из которых является указатель на функцию обработки сообщений — оконную процедуру.

Регистрация класса окна в подсистеме USER32 выполняется заполнением структуры WNDCLASSEXW и вызовом функции RegisterClassExW:

 1#include <windows.h>
 2
 3// Прототип оконной процедуры
 4LRESULT CALLBACK MainWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
 5
 6// Регистрация класса окна
 7ATOM RegisterMainWindowClass(HINSTANCE hInstance) {
 8    WNDCLASSEXW wc;
 9    ZeroMemory(&wc, sizeof(wc));
10
11    wc.cbSize        = sizeof(WNDCLASSEXW);
12    // CS_HREDRAW | CS_VREDRAW: полная перерисовка окна при изменении ширины или высоты
13    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
14    wc.lpfnWndProc   = MainWindowProc; // Главный обработчик сообщений
15    wc.cbClsExtra    = 0;              // Дополнительная память класса
16    wc.cbWndExtra    = sizeof(void*);  // Дополнительная память окна (под указатель)
17    wc.hInstance     = hInstance;      // Дескриптор модуля
18    wc.hIcon         = LoadIconW(NULL, IDI_APPLICATION); // Стандартная пиктограмма
19    wc.hCursor       = LoadCursorW(NULL, IDC_ARROW);     // Стандартный курсор-стрелка
20    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);       // Кисть фона клиентской области
21    wc.lpszMenuName  = NULL;                             // Имя ресурса меню
22    wc.lpszClassName = L"SystemProgramming_MainWindow";  // Уникальное строковое имя
23    wc.hIconSm       = LoadIconW(NULL, IDI_APPLICATION); // Малая пиктограмма
24
25    return RegisterClassExW(&wc);
26}

📌 Слайд 6: Архитектура оконного класса и создание окна (HWND)

center

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

 1HWND hwnd = CreateWindowExW(
 2    WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, // Расширенные стили окна
 3    L"SystemProgramming_MainWindow",     // Имя зарегистрированного класса
 4    L"Системное программирование 2026",   // Заголовок окна
 5    WS_OVERLAPPEDWINDOW,                 // Базовые стили (заголовок, рамка, кнопки свернуть/закрыть)
 6    CW_USEDEFAULT, CW_USEDEFAULT,        // Координаты X, Y на экране
 7    800, 600,                            // Ширина и высота окна
 8    NULL,                                // Дескриптор родительского окна (Parent)
 9    NULL,                                // Дескриптор меню
10    hInstance,                           // Дескриптор экземпляра приложения
11    NULL                                 // Пользовательский параметр (lpParam)
12);
13
14if (hwnd == NULL) {
15    DWORD error = GetLastError();
16    // Обработка ошибки создания окна...
17    return 0;
18}
19
20// Отображение и первичная перерисовка окна на экране
21ShowWindow(hwnd, SW_SHOWDEFAULT);
22UpdateWindow(hwnd);

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


3. Предопределенные классы и динамические данные окна

📌 Слайд 7: Предопределенные оконные классы и элементы управления

В операционной системе Windows все стандартные графические элементы управления (кнопки, поля ввода, списки, надписи) сами являются полноценными окнами (HWND), созданными на базе встроенных предопределенных классов окон:

Имя классаТип элемента управленияОсновные стили и особенности
L"BUTTON"Кнопки, переключатели, флажкиBS_PUSHBUTTON, BS_CHECKBOX, BS_RADIOBUTTON
L"EDIT"Однострочные и многострочные текстовые поляES_LEFT, ES_MULTILINE, ES_AUTOVSCROLL, ES_PASSWORD
L"STATIC"Текстовые метки, рамки, статические изображенияSS_LEFT, SS_CENTER, SS_BITMAP, SS_NOTIFY
L"LISTBOX"Список элементов с возможностью выбораLBS_STANDARD, LBS_MULTIPLESEL, LBS_SORT
L"COMBOBOX"Выпадающий комбинированный списокCBS_DROPDOWN, CBS_DROPDOWNLIST, CBS_AUTOHSCROLL
L"SCROLLBAR"Полосы прокруткиSBS_HORZ, SBS_VERT

Создание дочернего элемента управления сводится к вызову CreateWindowExW с указанием предопределенного класса и стиля WS_CHILD | WS_VISIBLE:

1HWND hButton = CreateWindowExW(
2    0, L"BUTTON", L"Вычислить",
3    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
4    50, 50, 120, 35,
5    hwndParent, (HMENU)IDC_MY_BUTTON, hInstance, NULL);

📌 Слайд 8: Дополнительные данные окна и класса: GetWindowLongPtrW

Оконная процедура WndProc в языке Си обязана быть статической функцией или свободной функцией с соглашением вызова CALLBACK (__stdcall). Однако в объектно-ориентированном программировании (C++) логика окна оформляется в виде класса (например, class Window).

Для связывания системного дескриптора HWND с указателем на экземпляр класса C++ (this) Windows предоставляет механизм хранения пользовательских данных в заголовке окна ядра:

  • SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pMyClass) — сохраняет 64-битный указатель на объект приложения в защищенной структуре окна.
  • GetWindowLongPtrW(hwnd, GWLP_USERDATA) — извлекает сохраненный указатель внутри статической оконной процедуры.
 1// Передача указателя this при создании окна:
 2CreateWindowExW(..., (LPVOID)this);
 3
 4// Внутри оконной процедуры при сообщении WM_CREATE:
 5LRESULT CALLBACK GlobalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
 6    if (uMsg == WM_CREATE) {
 7        CREATESTRUCTW *pCreate = (CREATESTRUCTW*)lParam;
 8        SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pCreate->lpCreateParams);
 9    }
10    
11    MyWindow *pWnd = (MyWindow*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
12    if (pWnd) {
13        return pWnd->HandleMessage(uMsg, wParam, lParam);
14    }
15    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
16}

4. Понятие оконного сообщения и структура MSG

📌 Слайд 9: Анатомия оконного сообщения: структура MSG

Любое событие в оконной подсистеме Windows упаковывается в стандартизированную системную структуру MSG:

1typedef struct tagMSG {
2    HWND   hwnd;    // Дескриптор окна, которому адресовано сообщение
3    UINT   message; // 32-битный числовой идентификатор типа сообщения (ID)
4    WPARAM wParam;  // Дополнительный параметр (32 бита на x86, 64 бита на x64)
5    LPARAM lParam;  // Дополнительный параметр (32 бита на x86, 64 бита на x64)
6    DWORD  time;    // Системное время генерации сообщения (в миллисекундах)
7    POINT  pt;      // Экранные координаты курсора мыши в момент генерации
8} MSG;

Смысл параметров wParam и lParam зависит исключительно от кода сообщения message:

  • При сообщении WM_SIZE: wParam содержит флаг изменения размера (SIZE_RESTORED, SIZE_MAXIMIZED), а lParam упаковывает ширину и высоту клиентской области: ширина в младшем слове LOWORD(lParam), высота в старшем HIWORD(lParam).
  • При сообщении WM_MOUSEMOVE / WM_LBUTTONDOWN: wParam содержит битовые флаги нажатых кнопок мыши и клавиш-модификаторов (MK_LBUTTON, MK_CONTROL, MK_SHIFT), а lParam — координаты курсора относительно верхнего левого угла клиентской области: $X = \text{GET_X_LPARAM}(lParam)$, $Y = \text{GET_Y_LPARAM}(lParam)$.
  • При сообщении WM_COMMAND: LOWORD(wParam) содержит идентификатор элемента управления (Control ID), HIWORD(wParam) — код уведомления (Notification Code), а lParam — дескриптор HWND элемента управления, пославшего сигнал.

📌 Слайд 10: Классификация и анатомия оконных сообщений (MSG)

center

Операционная система Windows строго разграничивает диапазоны числовых идентификаторов сообщений:

  • 0x0000 – 0x03FF (0 .. WM_USER - 1): зарезервированы операционной системой для стандартных оконных сообщений (WM_CREATE, WM_PAINT, WM_DESTROY, WM_KEYDOWN).
  • 0x0400 – 0x7FFF (WM_USER .. 0x7FFF): сообщения, определяемые стандартными классами элементов управления (например, кнопками и списками). В собственных окнах их использование не рекомендуется, так как они могут конфликтовать с внутренними сообщениями стандартных компонентов.
  • 0x8000 – 0xBFFF (WM_APP .. 0xBFFF): диапазон, гарантированно свободный от любых системных интерпретаций. Предназначен для внутренних сообщений прикладного программиста в рамках одного приложения.
  • 0xC000 – 0xFFFF: строковые зарегистрированные сообщения, создаваемые системным вызовом RegisterWindowMessageW(L"MyInterprocessSyncMessage"). Уникальны во всей операционной системе и служат безопасным средством межпроцессного взаимодействия (IPC).

5. Архитектура очередей сообщений и диспетчеризация

📌 Слайд 11: Очереди сообщений потока и Raw Input Thread (RIT)

Каким образом механическое движение мыши или нажатие клавиши превращается в сообщение окна? В операционной системе Windows за это отвечает специализированная подсистема ядра win32k.sys и системный поток Raw Input Thread (RIT):

  1. Аппаратное прерывание (IRQ): контроллер клавиатуры или мыши генерирует прерывание. Драйвер устройства считывает аппаратный скан-код и передает его в исполнительную подсистему ядра.
  2. Поток необработанного ввода (RIT): системный поток RIT (работающий с повышенным приоритетом реального времени) извлекает событие ввода. RIT определяет, какому экрану и координатам соответствует курсор, выполняет проверку пересечения (Hit-testing) и находит целевой дескриптор HWND.
  3. Маршрутизация в очередь потока: RIT находит поток, владеющий данным HWND, и помещает сообщение в аппаратную под-очередь ввода (Hardware Input Queue) целевого потока.

📌 Слайд 12: Архитектура очередей и главный цикл сообщений (Message Loop)

center

Устройство очереди сообщений потока:

Очередь сообщений потока (Thread Message Queue) не является простой одиночной структурой FIFO. Она состоит из нескольких компонентов разного приоритета:

  1. Очередь отложенных сообщений (Posted Message Queue): сообщения, отправленные вызовом PostMessage(). Обрабатываются строго в порядке поступления (FIFO).
  2. Очередь аппаратного ввода (Hardware Input Queue): события мыши и клавиатуры от RIT.
  3. Синтетические флаги (События низкого приоритета):
    • Флаг WM_PAINT: сообщение отрисовки не хранится в виде пакета данных. Вместо этого ядро выставляет окну флаг «область недействительна» (Invalid Region). WM_PAINT генерируется планировщиком сообщений только тогда, когда очереди 1 и 2 абсолютно пусты!
    • Флаг WM_TIMER: таймерные сообщения также синтезируются динамически при отсутствии других сообщений, если системное время превысило заданный интервал.
    • Флаг WM_QUIT: формируется при вызове PostQuitMessage(), сигнализируя о выходе из приложения.

📌 Слайд 13: Синхронная и асинхронная отправка: SendMessage vs PostMessage

В системном API Windows существуют два фундаментально различных метода отправки сообщений окну:

ХарактеристикаPostMessageW() (Асинхронный)SendMessageW() (Синхронный)
Тип взаимодействияАсинхронный (Fire-and-Forget)Синхронный (Request-Reply / RPC)
Очередь сообщенийПомещает сообщение в хвост очереди потокаМинует очередь сообщений!
Блокировка вызывающегоЗавершается немедленно (0 мс)Блокирует вызывающий поток до завершения обработки
Возвращаемое значениеТолько статус успешности помещения в очередь (BOOL)LRESULT, возвращенный функцией WndProc
Использование из потоковБезопасно для фоновых рабочих потоковОпасно: может вызвать межпоточный дедлок!
1// Асинхронная отправка (рабочий поток уведомляет GUI о прогрессе):
2PostMessageW(hwndProgress, WM_USER_UPDATE_PROGRESS, (WPARAM)currentPercent, 0);
3
4// Синхронная отправка (немедленное получение длины текста в поле ввода):
5int textLength = (int)SendMessageW(hEdit, WM_GETTEXTLENGTH, 0, 0);

📌 Слайд 14: Главный цикл выборки сообщений: GetMessage, PeekMessage

Главный цикл выборки сообщений (Message Pump / Message Loop) располагается в функции wWinMain:

 1MSG msg;
 2// GetMessage возвращает:
 3// > 0 — извлечено обычное сообщение
 4//   0 — извлечено сообщение WM_QUIT (сигнал штатного выхода)
 5//  -1 — возникла фатальная ошибка (некорректный дескриптор HWND)
 6while (GetMessageW(&msg, NULL, 0, 0) > 0) {
 7    TranslateMessage(&msg); // Шаг 1: трансляция скан-кодов клавиатуры в WM_CHAR
 8    DispatchMessageW(&msg);  // Шаг 2: вызов оконной процедуры целевого окна msg.hwnd
 9}
10return (int)msg.wParam; // Код завершения процесса

Альтернатива: неблокирующий опрос через PeekMessageW

Функция GetMessageW блокирует поток и переводит его в сон ядра, если сообщений нет. В игровых движках, системах реального времени и рендеринга требуется выполнять непрерывные вычисления при отсутствии сообщений. Для этого применяется функция PeekMessageW:

 1while (true) {
 2    if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
 3        if (msg.message == WM_QUIT) break;
 4        TranslateMessage(&msg);
 5        DispatchMessageW(&msg);
 6    } else {
 7        // Очередь пуста: выполняем фоновые вычисления или кадр анимации
 8        RenderNextFrame();
 9    }
10}

6. Оконная процедура и обработка по умолчанию

📌 Слайд 15: Оконная процедура (Window Procedure) и функция DefWindowProc

Оконная процедура (Window Procedure) — это главная функция обратного вызова, в которой программист определяет реакцию окна на все интересующие события.

Функция имеет строго стандартизированную сигнатуру:

 1LRESULT CALLBACK MainWindowProc(
 2    HWND   hwnd,   // Дескриптор окна, получившего сообщение
 3    UINT   uMsg,   // Код сообщения
 4    WPARAM wParam, // Первый параметр сообщения
 5    LPARAM lParam  // Второй параметр сообщения
 6) {
 7    switch (uMsg) {
 8        case WM_PAINT: {
 9            PAINTSTRUCT ps;
10            HDC hdc = BeginPaint(hwnd, &ps); // Получение контекста отображения
11            // Отрисовка графики (GDI / Direct2D)...
12            EndPaint(hwnd, &ps);             // Валидация области, снятие флага WM_PAINT
13            return 0; // Сообщение обработано
14        }
15        case WM_DESTROY: {
16            // Окно уничтожено пользователем: посылаем в свою очередь WM_QUIT
17            PostQuitMessage(0);
18            return 0;
19        }
20        default:
21            // Все необработанные сообщения ОБЯЗАНЫ передаваться в DefWindowProcW!
22            return DefWindowProcW(hwnd, uMsg, wParam, lParam);
23    }
24}

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


7. Обработка мышиного ввода

📌 Слайд 16: Обработка сообщений мыши в системном API

События манипулятора «мышь» подразделяются на сообщения клиентской области и неклиентские сообщения:

  1. Клиентские сообщения (Client Area Messages): генерируются, когда курсор находится внутри рабочей области окна:
    • WM_MOUSEMOVE — любое смещение мыши;
    • WM_LBUTTONDOWN / WM_LBUTTONUP — нажатие/отпускание левой кнопки;
    • WM_RBUTTONDOWN / WM_RBUTTONUP — нажатие/отпускание правой кнопки (контекстные действия);
    • WM_LBUTTONDBLCLK — двойной щелчок (требует стиля класса CS_DBLCLKS);
    • WM_MOUSEWHEEL — вращение колеса мыши; дельта поворота передается в GET_WHEEL_DELTA_WPARAM(wParam).

Координаты курсора относительно клиентской области извлекаются макросами:

1case WM_LBUTTONDOWN: {
2    int xPos = GET_X_LPARAM(lParam);
3    int yPos = GET_Y_LPARAM(lParam);
4    UINT flags = (UINT)wParam; // MK_CONTROL, MK_SHIFT
5    // Реакция на нажатие кнопки...
6    return 0;
7}

📌 Слайд 17: Захват мыши и неклиентские события: SetCapture и WM_NCHITTEST

1. Механизм захвата мыши (Mouse Capture)

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

Для предотвращения потери событий перетаскивания (Drag-and-Drop) применяется захват мыши:

  • SetCapture(hwnd) — приказывает ОС направлять все сообщения мыши данному окну, даже если курсор ушел за пределы монитора.
  • ReleaseCapture() — освобождает захват мыши при отпускании кнопки.

2. Неклиентские сообщения и хит-тестинг (WM_NCHITTEST)

Когда курсор мыши перемещается над заголовком окна, рамкой изменения размера или кнопкой закрытия [X], ядро генерирует неклиентские сообщения с префиксом WM_NC* (WM_NCMOUSEMOVE, WM_NCLBUTTONDOWN).

Прежде чем послать сообщение, ядро опрашивает окно сообщением WM_NCHITTEST («В какой части окна находится точка?»). Стандартная процедура DefWindowProcW возвращает коды зон:

  • HTCLIENT — точка внутри клиентской области;
  • HTCAPTION — точка на заголовке окна (запуск перетаскивания окна);
  • HTLEFT, HTRIGHT, HTTOP, HTBOTTOM — рамки изменения размеров;
  • HTCLOSE — кнопка закрытия окна.

Переопределив WM_NCHITTEST, программист может сделать так, чтобы окно перетаскивалось за любую часть клиентской области, вернув значение HTCAPTION!


8. Обработка клавиатурного ввода

📌 Слайд 18: Обработка клавиатурного ввода: скан-коды и виртуальные клавиши

Клавиатурный ввод в операционной системе Windows обрабатывается на трех последовательных уровнях:

  1. Аппаратные скан-коды (Scan Codes): физические номера контактов на микроконтроллере клавиатуры. Зависят от конкретной модели и раскладки клавиатуры.
  2. Виртуальные коды клавиш (Virtual-Key Codes, VK_*): аппаратно-независимые системные константы Windows (например, VK_LEFT, VK_F1, VK_RETURN, VK_ESCAPE, VK_SPACE). Генерируются сообщения:
    • WM_KEYDOWN — физическое нажатие клавиши;
    • WM_KEYUP — отпускание клавиши;
    • WM_SYSKEYDOWN / WM_SYSKEYUP — нажатие комбинаций с клавишей Alt (системные команды меню). Код виртуальной клавиши передается в параметре wParam.

📌 Слайд 19: Фокус ввода и текстовый ввод: WM_CHAR и TranslateMessage

1. Символьные сообщения (WM_CHAR)

Сообщение WM_KEYDOWN сообщает программисту о факте нажатия физической кнопки (например, клавиши A), но оно не знает, какой символ должен быть напечатан (строчная 'a', заглавная 'A', русская 'ф' или 'Ф').

Трансляцией кодов клавиш в символы занимается функция TranslateMessage(&msg) в главном цикле сообщений:

  • Она сопоставляет состояние WM_KEYDOWN с текущей языковой раскладкой активного потока, а также состоянием клавиш Shift, CapsLock и AltGr.
  • Если нажатие образует печатный символ Юникода, TranslateMessage синтезирует новое сообщение WM_CHAR и помещает его обратно в очередь потока.
  • В сообщении WM_CHAR параметр wParam содержит уже готовый 16-битный код символа UTF-16 (wchar_t).
1case WM_CHAR: {
2    wchar_t ch = (wchar_t)wParam;
3    if (ch >= 32) { // Печатный символ
4        AppendCharacterToDocument(ch);
5    }
6    return 0;
7}

2. Фокус клавиатурного ввода (Keyboard Focus)

Клавиатура на компьютере одна, а окон на экране может быть множество. Сообщения клавиатуры направляются только тому окну, которое в данный момент обладает фокусом ввода:

  • При получении фокуса окно получает сообщение WM_SETFOCUS;
  • При потере фокуса (пользователь переключился на другое окно) — WM_KILLFOCUS.
  • Программно передать фокус ввода окну можно функцией SetFocus(hwnd).

Резюме и выводы

📌 Слайд 20: Резюме лекции

Основные итоги занятия:

  1. Событийно-ориентированная парадигма построена на инверсии управления (Inversion of Control): операционная система диспетчеризирует события, вызывая функции обратного вызова прикладной программы.
  2. Класс окна (WNDCLASSEXW) определяет общие атрибуты окон и связывает их с главной оконной процедурой (lpfnWndProc). Каждое окно создается функцией CreateWindowExW и идентифицируется дескриптором HWND.
  3. Оконные сообщения упаковываются в структуру MSG, содержащую код события и два универсальных параметра WPARAM и LPARAM.
  4. Очереди сообщений потока обслуживаются системным потоком RIT; приоритет отдается отложенным сообщениям (PostMessage) и аппаратному вводу, а события перерисовки (WM_PAINT) и таймеров (WM_TIMER) генерируются синтетически при простое очереди.
  5. SendMessage осуществляет прямой синхронный вызов оконной процедуры в обход очереди, в то время как PostMessage асинхронно ставит сообщение в очередь и немедленно возвращает управление.
  6. Оконная процедура обязана передавать все необработанные сообщения в системную функцию по умолчанию DefWindowProcW.
  7. Ввод мыши подразделяется на клиентский и неклиентский (WM_NCHITTEST), а длительное отслеживание движений требует явного захвата через SetCapture.
  8. Клавиатурный ввод разделен на аппаратно-независимые коды клавиш (WM_KEYDOWN) и готовые символы Юникода (WM_CHAR), формируемые функцией TranslateMessage.

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

📌 Слайд 21: Вопросы для самопроверки

  1. В чем заключается фундаментальная разница между консольной и событийно-ориентированной архитектурой программ?
  2. Какую роль играет структура WNDCLASSEXW и почему окно нельзя создать без предварительной регистрации класса?
  3. Каким образом реализуется связь между системным дескриптором HWND и объектом языка C++ с помощью функции SetWindowLongPtrW?
  4. Какие поля входят в структуру сообщения MSG и какую информацию несут параметры WPARAM и LPARAM?
  5. В чем заключается разница между диапазонами сообщений WM_USER, WM_APP и строковыми сообщениями RegisterWindowMessage?
  6. За какие задачи отвечает системный поток Raw Input Thread (RIT) в архитектуре Windows?
  7. Почему сообщение WM_PAINT называется синтетическим и когда оно извлекается функцией GetMessage?
  8. В чем заключается принципиальная разница в поведении функций SendMessage и PostMessage?
  9. Что произойдет с окном, если в функции WndProc не передавать необработанные сообщения в DefWindowProc?
  10. Каким образом функция TranslateMessage преобразует сообщения WM_KEYDOWN в символьные сообщения WM_CHAR?

Рекомендуемая литература и источники

📌 Слайд 22: Литература и рекомендуемые ресурсы

Основная литература:

  • Устройство и функционирование OC Windows — Коньков К. А. М.: Бином, 2008. 208 с.
  • Современные операционные системы (4-е изд.) — Таненбаум Э., Бос Х. СПб.: Питер, 2021. 1119 с.
  • Операционные системы (2-е изд.) — Гордеев А. В. СПб.: Питер, 2009. 415 с.
  • Системное программирование: методические указания к лабораторным работам — Бизюк А. Н., Соколова А. С. Витебск: УО «ВГТУ», 2024.

Дополнительная литература и справочники:

← 04. Организация параллельной обработки с … 06. Организация графического … →