WinAPI. Создание простейшего приложения под Windows

В этом посте мне хочется рассказать, как создать простейшее приложение с использованием WinAPI и языка программирования C++. Обычное, пустое окошко Windows. Причины, побудившие меня к этому, очень просты: источники, которые я читал до определенного момента не давали мне полного представления о том, что и как работает в приложении Win32. Понимать я это стал, как ни удивительно, только после того, как тот же материал был освещен на лекциях в универе. Почему-то в том виде, в каком преподносилась информация на лекциях, она лучше откладывалась в памяти, нежели “книжные” записи, пусть даже совсем неплохие. Еще одно обстоятельство, способствовавшее идее освящения данной темы – желание лучше закрепить материал, излагая его в письменной форме и, возможно, даже расширить свои знания, заглянув лишний раз в MSDN, чтобы дополнить что-то.

Как, думаю, стало понятно из вступления, материал этого поста и, вероятно, последующих постов на тему WinAPI и программирования под ОС Windows будут основываться на универских лекциях + MSDN с добавлением чего-то от себя (по делу :)).

Наверное, в начале стоит сказать, что Windows API (Application Programming Interface) – это набор готовых классов, функций, структур и констант, при помощи которых любое приложение может взаимодействовать с операционной системой (ОС) Windows.

Также отмечу, что основные функции Windows API появились еще в Win16 и с каждой версией Windows их набор расширяется, утрачивая, однако, при этом поддержку некоторых других функций. Схематично, это можно представить так:

Итак, для любого Windows приложения требуется написать как минимум 2 функции:
Точка входа в приложение, в которой необходимо:

  1. зарегистрировать класс окна
  2. создать окно
  3. запустить цикл обработки сообщений

Сама точка выглядит примерно так:

// точка входа в программу
#include <windows.h> //добавляем к проекту заголовочный файл с основными функциями и макросами Windows API

int WINAPI WinMain(HINSTANCE hInst, //хендл на это приложение
HINSTANCE hPrev, //оставлен для совместимости с Win16, всегда = 0
LPSTR szCmdLine, //командная строка этого приложения
int nShowCmd) //параметры, указывающие, как надо запускать приложение
{
//здесь будет текст программы
}

Вопрос №1: Что значит LPSTR? В ответе представлю небольшой список “особенностей” строковых типов WINAPI.

char работает с ASCII

wchar_t – с Unicode

Они оба используются в макросе TCHAR, который позволяет их “перещелкивать” в зависимости от настроек проекта (компилируется под ASCII или под Unicode)

Также, в WinAPI зачастую есть 2 по сути одинаковые функции:

FuncNameA – для работы с ASCII

FuncNameW – для работы с Unicode

Строковые типы в WinAPI:

LPSTR – char*
LOWSTR – wchar_t*
LPISTR – TCHAR*
LPCSTR – const char*
LPCWSTR – const wchar_t*
LPCTSTR – const TCHAR*

об остальных можно посмотреть в MSDN

Вопрос №2: Что значит WinAPI? Ответ: WinAPI скрывает под собой используемое соглашение вызова — __stdcall, по которому параметры заносятся в стек справа налево (что позволяет вызывать функции с переменным числом параметров), а очищается он вызываемой функцией. Подробнее о различных соглашениях вызова можно почитать в Википедии.

  1. Обработчик сообщений.

Начнем по порядку.

Регистрация окна осуществляется в два этапа:
заполнение структуры окна
передача структуры на регистрацию

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

// 1й этап
// регистрируется класс
WNDCLASSEX wcx = {0};//обнуляем сразу все поля структуры, чтобы ничего не забыть, т.к. понадобятся нам пока не все// я же говорил что WNDCLASSEX можно не юзать, но MSDN ругается
wcx.cbSize = sizeof(WNDCLASSEX); //по размеру структуры Windows определит, какая версия API была использована
wcx.style = CS_HREDRAW | CS_VREDRAW;// говорим окну перерисовываться при изменении размеров окна
wcx.lpfnWndProc = WndProc;// указываем функцию обработки сообщений
wcx.hInstance = hInst; // хендл на наше приложение
wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // GetStockObject возвращает хендл на белую кисточку, для фона окна
wcx.lpszClassName = TEXT("[lexpenz.com] Win32.");// имя данного класса. Должно быть уникальным, иначе, если класс с таким именем уже зарегестрирован, то в регистрации будет отказано

if ( !RegisterClassEx(&wcx) )
return 1;// регистрируем ( не получилось - уходим по английски ) с кодом ошибки (1)

Вопрос №3: Что делает макрос TEXT? Ответ: Он, если идет компиляция с использованием Unicode, добавляет префикс «l» к переданной ему строке, что делает её Unicode-строкой для компилятора.

Далее идет создание окна:
// 2й этап
// создается окно
HWND hWnd = CreateWindowEx(0,
TEXT("[lexpenz.com] Win32."),//имя класса
TEXT("[lexpenz.com] Win32. Первое приложение Win32."),//заголовок окна
WS_OVERLAPPEDWINDOW, //тип окошка (включает отображение системного меню, кнопок в верхнем правом углу и т.п.)
CW_USEDEFAULT,0,//место появления окна (координаты х и y). Здесь указано место “по умолчанию”, поэтому второй параметр игнорируется
CW_USEDEFAULT,0,//ширина окна (определяется аналогично месту появления)
0, //ссылка на родительское окно
0,//хендл меню
hInst,
0);//код, передаваемый с сообщением WM_CREATE

if (!hWnd)    //проверяем успешность создания окна
return 2;

// теперь показываем окошко ( nShowCmd - как его показать? минимизированным, обычным или ... )
ShowWindow(hWnd, nShowCmd);
// говорим окну обновиться
UpdateWindow(hWnd);

И, наконец,
// 3й этап
// запуск главного цикла обработки сообщений
MSG msg = {0};// создаем структуру сообщения, которую будем обрабатывать
while( GetMessage(&msg,
0,//говорим получать сообщения от всех окон
0, 0) )//диапазон значений получаемых сообщений (сейчас получаем все)
{ // ждем сообщение
TranslateMessage(&msg); // преобразуем виртуальную клавишу в ASCII-код и посылаем сообщение WM_CHAR (тут не нужно.Необходимо, если надо работать с текстом, вводимым с клавиатуры)
DispatchMessage(&msg); // передаем сообщения для обработки в "главную функцию обработки сообщений"
}

Для завершения основной функции, осталось прописать только код возврата:
return( (int)msg.wParam );// т.к. это функция, то вернем параметр WM_QUIT сообщения (см. PostQuitMessage)

Функция обработки сообщений:

// главная функция обработки сообщений
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) {
case WM_DESTROY:// если этого не сделать, то все ваши жалкие попытки закрыть окно будут проигнорированы
PostQuitMessage(0);// отправляет приложению сообщение WM_QUIT. Принимает код ошибки, который заносится в wParam сообщения WM_QUIT
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);//обрабатываем все остальные сообщения обработчиком "по умолчанию"
}

И, как всегда, в завершении статьи ссылка на архив с примером.

Полезная статья? Их будет больше, если вы поддержите меня!

https://money.yandex.ru/embed/small.xml?account=410011399843532&quickpay=small&yamoney-payment-type=on&button-text=06&button-size=l&button-color=orange&targets=%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%B8%D0%B2%D0%B0%D1%8E+%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82+lexpenz.com&default-sum=50&successURL=

Архив комментариев

4 комментария из прежней версии блога. Архив закрыт для новых ответов.

  1. Спасибо! Очень понятно изложено!

  2. Rosskyавтор

    Пожалуйста :) Рад стараться :)

  3. большое спасибо, очень помогли, все таки сдам завтра лабу)

  4. Алексей

    В первом листинге нужно добавить
    #include