В этом посте мне хочется рассказать, как создать простейшее приложение с использованием WinAPI и языка программирования C++. Обычное, пустое окошко Windows. Причины, побудившие меня к этому, очень просты: источники, которые я читал до определенного момента не давали мне полного представления о том, что и как работает в приложении Win32. Понимать я это стал, как ни удивительно, только после того, как тот же материал был освещен на лекциях в универе. Почему-то в том виде, в каком преподносилась информация на лекциях, она лучше откладывалась в памяти, нежели “книжные” записи, пусть даже совсем неплохие. Еще одно обстоятельство, способствовавшее идее освящения данной темы – желание лучше закрепить материал, излагая его в письменной форме и, возможно, даже расширить свои знания, заглянув лишний раз в MSDN, чтобы дополнить что-то.
Как, думаю, стало понятно из вступления, материал этого поста и, вероятно, последующих постов на тему WinAPI и программирования под ОС Windows будут основываться на универских лекциях + MSDN с добавлением чего-то от себя (по делу :)).
Наверное, в начале стоит сказать, что Windows API (Application Programming Interface) – это набор готовых классов, функций, структур и констант, при помощи которых любое приложение может взаимодействовать с операционной системой (ОС) Windows.
Также отмечу, что основные функции Windows API появились еще в Win16 и с каждой версией Windows их набор расширяется, утрачивая, однако, при этом поддержку некоторых других функций. Схематично, это можно представить так:
Итак, для любого Windows приложения требуется написать как минимум 2 функции:
Точка входа в приложение, в которой необходимо:
- зарегистрировать класс окна
- создать окно
- запустить цикл обработки сообщений
Сама точка выглядит примерно так:
1 2 3 4 5 6 7 8 9 10 |
// точка входа в программу #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:
1 2 3 4 5 6 |
LPSTR – char* LOWSTR – wchar_t* LPISTR – TCHAR* LPCSTR – const char* LPCWSTR – const wchar_t* LPCTSTR – const TCHAR* |
об остальных можно посмотреть в MSDN
Вопрос №2: Что значит WinAPI? Ответ: WinAPI скрывает под собой используемое соглашение вызова — __stdcall, по которому параметры заносятся в стек справа налево (что позволяет вызывать функции с переменным числом параметров), а очищается он вызываемой функцией. Подробнее о различных соглашениях вызова можно почитать в Википедии.
2. Обработчик сообщений.
Начнем по порядку.
Регистрация окна осуществляется в два этапа:
заполнение структуры окна
передача структуры на регистрацию
Когда выглядит примерно так:
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 |
// 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)
Функция обработки сообщений:
1 2 3 4 5 6 7 8 9 10 |
// главная функция обработки сообщений 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);//обрабатываем все остальные сообщения обработчиком "по умолчанию" } |
И, как всегда, в завершении статьи ссылка на архив с примером.
Полезная статья? Их будет больше, если вы поддержите меня!