В этом посте мне хочется рассказать, как создать простейшее приложение с использованием WinAPI и языка программирования C++. Обычное, пустое окошко Windows. Причины, побудившие меня к этому, очень просты: источники, которые я читал до определенного момента не давали мне полного представления о том, что и как работает в приложении Win32. Понимать я это стал, как ни удивительно, только после того, как тот же материал был освещен на лекциях в универе. Почему-то в том виде, в каком преподносилась информация на лекциях, она лучше откладывалась в памяти, нежели “книжные” записи, пусть даже совсем неплохие. Еще одно обстоятельство, способствовавшее идее освящения данной темы – желание лучше закрепить материал, излагая его в письменной форме и, возможно, даже расширить свои знания, заглянув лишний раз в MSDN, чтобы дополнить что-то.
Как, думаю, стало понятно из вступления, материал этого поста и, вероятно, последующих постов на тему WinAPI и программирования под ОС Windows будут основываться на универских лекциях + MSDN с добавлением чего-то от себя (по делу :)).
Наверное, в начале стоит сказать, что Windows API (Application Programming Interface) – это набор готовых классов, функций, структур и констант, при помощи которых любое приложение может взаимодействовать с операционной системой (ОС) Windows.
Также отмечу, что основные функции Windows API появились еще в Win16 и с каждой версией Windows их набор расширяется, утрачивая, однако, при этом поддержку некоторых других функций. Схематично, это можно представить так:
Итак, для любого Windows приложения требуется написать как минимум 2 функции:
Точка входа в приложение, в которой необходимо:
- зарегистрировать класс окна
- создать окно
- запустить цикл обработки сообщений
Сама точка выглядит примерно так:
// точка входа в программу
#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й этап
// регистрируется класс
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);//обрабатываем все остальные сообщения обработчиком "по умолчанию"
}
И, как всегда, в завершении статьи ссылка на архив с примером.
Полезная статья? Их будет больше, если вы поддержите меня!
Архив комментариев
4 комментария из прежней версии блога. Архив закрыт для новых ответов.
Спасибо! Очень понятно изложено!
Пожалуйста :) Рад стараться :)
большое спасибо, очень помогли, все таки сдам завтра лабу)
В первом листинге нужно добавить
#include