Форум программистов, компьютерный форум, киберфорум
Наши страницы
nobless1368
Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 4.

Знакомство с WinAPI

Запись от nobless1368 размещена 23.01.2013 в 17:30
Обновил(-а) nobless1368 27.01.2013 в 00:37

В своем блоге я хочу познакомить вас с основами программирования под самую распространенную на данный момент ОС - Windows.
Не буду тратить ваше и свое время на длинные вступительные речи. Постараюсь сделать все коротко и ясно.
Я предполагаю, что вы знакомы с синтаксисом языка С++ и имеете представление о том, что же такое ООП. Итак, приступим.

Наша первая программа для Windows будет создавать окно с надписью "Hello, mustdie!".
Вопреки традиционному подходу "сначала теория, потом практика", я буду рассказывать все и сразу на примерах.
Итак, вот наша первая программа:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <Windows.h>
#include "mmsystem.h"
 
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    const wchar_t szAppName[] = L"Hello, Mustdie";  
    HWND hwnd;
    MSG msg;
    WNDCLASSEX wndclass;
 
    wndclass.cbSize = sizeof (wndclass);
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName; 
    wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
 
    RegisterClassEx (&wndclass);
 
    hwnd = CreateWindow (szAppName,  // Имя класса окна
    L"The Hello Program",    // Надпись на окне
    WS_OVERLAPPEDWINDOW,    // стиль окна
    CW_USEDEFAULT,   // позиция по оси х
    CW_USEDEFAULT,   // позиция по оси у
    CW_USEDEFAULT,   // размер по оси х
    CW_USEDEFAULT,   // размер по оси у
    NULL,    // заголовок родительского окна
    NULL,    // заголовок меню
    hInstance,   // названия экземпляра программы
    NULL);   // дополнительные параметры
 
    ShowWindow (hwnd, iCmdShow);
    UpdateWindow (hwnd);
 
    while (GetMessage (&msg, NULL, 0, 0))
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    return msg.wParam ;
}
 
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;
 
    switch(iMsg)
    {
        case WM_CREATE :
            //раскомментировать, если нужно будет воспроизвести звук
            //PlaySound ("forthelinux.wav", NULL, SND_FILENAME | SND_ASYNC);
            return 0;
 
        case WM_PAINT :
            hdc = BeginPaint (hwnd, &ps);
            GetClientRect (hwnd, &rect);
            DrawText (hdc, L"Hello, mustdie!", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
            EndPaint (hwnd, &ps);
            return 0;
 
        case WM_DESTROY :
            PostQuitMessage (0);
            return 0;
    }
 
    return DefWindowProc (hwnd, iMsg, wParam, lParam);
}
Итак, нам предстоит докопаться до сокрытого в этих восьмидесяти строчках кода смысла.
В любой Windows-программе имеется как минимум две функции: WinMain и WndProc. WinMain - это аналог стандартной функции main языка С. WndProc - это "оконная процедура" для окна HELLOWIN. Не пугаемся страшных терминов, которые все равно нам придется запомнить. Оконная процедура - это способ инкапсулирования кода, отвечающего за ввод/вывод информации. Вот тут я сделаю небольшое отступление.

Дело в том, что с этого момента вам придется уяснить, что ОС непрерывно общается с вашей программой, отправляя ей сообщения. Эти сообщения ваша программа должна уметь обрабатывать и обработка этих сообщений осуществляется в функции WndProc. Как конкретно это делается разберемся позже, а сейчас, если вы вдумчиво посмотрите в код программы, то увидите, что в нём отсутствуют инструкции для вызова WndProc. WndProc вызывает сама Windows. Однако, в WinMain имеется ссылка на WndProc, поэтому эта функция описывается в самом начале программы, еще до определения WinMain.

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

LoadIcon - загружает значок для использования в программе.
LoadCursor - загружает курсор мыши для использования в программе.
GetStockObject - получает графический объект (в этом случае для закрашивания окна используется кисть)
RegisterClassEx - регистрирует класс окна.
CreateWindow - создает окно на основе класса окна.
ShowWindow - выводит окно на экран.
UpdateWindow - заставляет окно перерисовать свое содержимое.
GetMessage - получает сообщения из очереди сообщений.
TranslateMessage - преобразует некоторые сообщения, полученные с помощью клавиатуры.
DispatchMessage - отправляет сообщение оконной процедуре.
PlaySound - воспроизводит звуковой файл.
BeginPaint - инициализирует процесс рисования окна.
GetClientRect - получает размер рабочей области окна.
DrawText - выводит на экран строку текста.
EndPaint - прекращает рисование окна.
PostQuitMessage - вставляет сообщение о завершении программы в очередь сообщений.
DefWindowProc - выполняет обработку сообщений по умолчанию.

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

CS - опция стиля класса.
IDI - идентификационный номер иконки.
IDC - идентификационный номер курсора.
WS - стиль окна.
CW - опция создания окна.
WM - сообщение окна.
SND - опция звука.
DT - опция рисования текста.

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

MSG - структура сообщения.
WNDCLASSEX - структура класса окна.
PAINTSTRUCT - структура рисования.
RECT - структура прямоугольника.

Первые две структуры данных используются WinMain для определения двух структур, названных msg и wndclass. Две вторые используются WndProc для определения структур ps и rect.

Наконец, в программе имеются еще три идентификатора, которые предназначены для различных типов описателей (handles):

HINSTANCE - описатель экземпляра (instance) самой программы.
HWND - описатель окна.
HDC - описатель контекста устройства (об этом позже).

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

с - символ
by - BYTE (беззнаковый символ)
n - короткое целое
i -целое
x, y - целые числа, используемые в качестве координат
cx, cy - целые числа, используемые в качестве длины. c означает "cout" - счёт.
b - BOOL (булево целое).
f - флаг.
w - WORD - беззнаковое короткое целое.
l - длинное целое.
dw - DWORD - беззнаковое длинное целое.
fn - функция.
s - строка.
sz - строка, завершаемая нулём.
h - описатель (handle).
p - указатель (pointer).

Ну вот и все, с зубрёжкой можно покончить.

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

Ниже следует объявление WndProc:
C++
1
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
Это объявление в начале программы необходимо, потому что в тексте WinMain имеются ссылки на WndProc. Саму функцию мы рассмотрим немного позже.

В отличие от традиционной точки входа main в языке С, в программировании для Windows точкой входа является функция WinMain. WinMain всегда определяется следующим образом:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
Рассмотрим её параметры.
Параметр hInstance называется описателем экземпляра (instance handle). Это уникальное число, идентифицирующее вашу программу, когда она работает под WIndows. У каждого запущенного экземпляра программы уникальный описатель, что позволяет им не конфликтовать.
gПараметр hPrevInstance - описатель предыдущего экземпляра (previous instance) - в настоящее время устарел и остался для совместимости.
szCmdLine - это указатель на строку, в которой содержатся любые параметры, переданные из командной строки.
iCmdShow - число, показывающее, каким должно быть выведено окно на экран (например, свёрнутым).

Разберем подробнее регистрацию класса окна.
Окно всегда создается на основе класса окна. Класс окна идентифицирует оконную процедуру, которая выполняет процесс обработки сообщений, поступающих окну. На основе одного класса окна можно создать несколько окон, достаточно только определить некоторые дополнительные характеристики.
Итак, чтобы создать окно для программы необходимо сначала зарегистрировать для него класс окна. Делается это при помощи функции RegisterClassEx, у которой имеется всего один параметр: указатель на структуру типа WNDCLASSEX, которая определяется в заголовочных файлах Windows следующим образом:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct tagWNDCLASSEX
{
    UINT cbSize;
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCSTR lpszMenuName;
    LPCSTR lpszClassName;
    HICON hIconSm;
} WNDCLASSEX;
По названиям переменных не сложно догадаться за что они отвечают.

Особое внимание хотелось бы уделить следующим двум инструкциям:
C++
1
wndclass.cbClsExtra = 0;
C++
1
wndclass.cbWndExtra = 0;
Они резервируют некоторое дополнительное пространство в структуре класса и структуре окна, которое внутренне поддерживается Windows. Программа может использовать это свободное пространство (в байтах) для своих нужд. В данной программе эта возможность не используется, поэтому соответствующие значения равны 0.

В следующем поле находится просто описатель экземпляра программы:
C++
1
wndclass.hInstance = hInstance;
Инструкции
C++
1
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
и
C++
1
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
устанавливают значок для всех окон, созданных на основе данного класса.

То же самое, только для курсора:
C++
1
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
Задаем цвет рабочей области:
C++
1
wndclass.hbrBackground = (HBRUSH) GetStockObject(WHIRE_BRUSH);
Следующее поле задает меню класса окна (которое у нас отсутствует, поэтому NULL):
C++
1
wndclass.lpszMenuName = NULL;
На последнем этапе классу должно быть присвоено имя, которое у нас будет совпадать с именем программы.
C++
1
wndclass.lpszClassName = szAppName;
После того, как инициализированы все 12 полей структуры, регистрируем класс окна путем вызова RegisterClassEx, единственным параметром которой является указатель на нашу структуру WNDCLASSEX:
C++
1
RegisterClassEx(&wndclass);
С классом окна закончили, теперь нам необходимо создать это окно. Тут есть свои нюансы.
Вместо использования структуры данных, как в случае с RegisterClassEx, вызов функции CreateWindow требует, чтобы вся информация передавалась функции в качестве параметров (см. листинг программы).

Итак, создали окно внутри Windows, осталось его отобразить.
Делается это при помощи всего двух вызовов. Первый:

C++
1
ShowWindow(hwnd, iCmdShow);
Первым параметром является описатель только что созданного функцией CreateWindow окна. Вторым - величина iCmdShow, которая задает начальный вид окна на экране.

Второй:

C++
1
UpdateWindow(hwnd);
Вызывает перерисовку рабочей области. Для этого windows в оконную процедуру посылает сообщение WM_PAINT. Как WndProc его обрабатывает я расскажу в одной из следующих статей.

Жду ваших отзывов, комментариев, исправлений и, как бы банально это ни звучало, заинтересованности.
Размещено в Без категории
Просмотров 3519 Комментарии 2
Всего комментариев 2
Комментарии
  1. Старый комментарий
    Аватар для nobless1368
    Цитата:
    Сообщение от Jupiter Просмотреть комментарий
    1. где теги кода?
    2. ни слова о юникоде, к тому же код непортабелен между юникод/анси версиями
    Статья предназначается для людей, которые только начали изучать WinAPI, и если я сразу начну говорить о юникоде, портах и прочих вещах, для изучения которых нужно хоть что-то знать, то у читателей после первой же статьи такая каша в голове образуется, что потом лопатой не разгрести.
    Теги добавил
    Запись от nobless1368 размещена 27.01.2013 в 00:27 nobless1368 вне форума
    Обновил(-а) nobless1368 27.01.2013 в 00:38
  2. Старый комментарий
    Аватар для nobless1368
    Цитата:
    Сообщение от Pure Просмотреть комментарий
    возможна ли работа виндовс программы без этой функции?

    C++
    1
    
    LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
    Нет. Для Windows-программы она обязательна, т.к. именно в этой функции обрабатываются сообщения от ОС.
    Запись от nobless1368 размещена 27.01.2013 в 00:30 nobless1368 вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.