Простейшее windows api приложение

Пишем на WinAPI с «нуля»


Автор: Яковлев Игорь Сергеевич
Источник: RSDN Magazine #4-2005

Опубликовано: 30.12.2005
Исправлено: 16.03.2006
Версия текста: 1.0

От редакции

Данная статья является введением в программирование на WinAPI. Важно понимать, что она носит скорее информационный характер, чем служит примером кода для реальных приложений. Дело в том, что WinAPI создавался для языка С, и имеет целый ряд недостатков в применении, как-то: невысокая безопасность, большой объем ручного кодирования для решения простейших задач, С-стиль, плохо выглядящий в C++-приложениях. Практически любое средство разработки на языке C++ для Windows включает те или иные высокоуровневые C++-библиотеки (MFC, ATL/WTL для Visual C++, VCL для C++ Builder), значительно упрощающие разработку Windows-приложений, и делающие ее более безопасной.

Предисловие

Эта статья посвящена описанию программирования приложений на «чистом» Win32 API. Она написана в основном для начинающих программистов, пишущих программы на Visual C++ 6 с использованием библиотеки MFC, но я надеюсь, может пригодиться и более опытным людям.

First Blood

После создания нового проекта Win32 Application, в зависимости от выбранных опций, мастер генерирует стартовый код. Из этого кода программисту впоследствии, и придется писать программу. Создавая новый проект Win32 Application, выберите в окне мастера опцию An empty project и добавьте в раздел Source Files новый файл с расширением .cpp.

В этом файле добавьте функцию WinMain вида:

Вот и готова первая программа на WinAPI. Она выводит сообщение, после чего завершает свою работу. Обратите внимание на параметры функции WinMain :

  • HINSTANCE hInstance – дескриптор экземпляра приложения. Этот дескриптор содержит адрес начала кода программы в ее адресном пространстве. Дескриптор hInstance чаще всего требуется функциям, работающим с ресурсами программы.
  • HINSTANCE hPrevInstance – дескриптор предыдущего экземпляра приложения. Этот дескриптор остался от старых версий Windows — скорее всего, вам он никогда не пригодится.
  • LPSTR lpCmdLine – указатель на начало командной строки, введенной при запуске программы.
  • int nCmdShow – это значение содержит желаемый вид окна (например, свернутый или развернутый)

Значение, которое возвращается функцией WinMain (тип int ) – код завершения программы. Принято, что если программа завершила свое выполнение без ошибок, возвращается 0.

Функция WinMain – первая функция, которая выполнятся в программе (ее еще называют «точка входа» или «entry point»). С нее все начинается, и ею (желательно) все должно закончиться.

ПРИМЕЧАНИЕ

Функция WinMain – это первая функция, которую вы можете увидеть и заполнить кодом. На самом деле до этой функции выполняется достаточно много кода из библиотеки C++

You have a Message!

Программисты, незнакомые с программированием на WinAPI, спросят: «Что с этим делать?!», — или: «Где создавать CDialog?». В данном случае ответ прост – нигде! В нашем проекте нет класса CDialog или, предположим, CButton – ведь эта статья посвящена тому, как обойтись без них.

В Windows при каждом событии, произошедшем в системе, отсылается «сообщение Windows» («windows message»). Эти сообщения уведомляют программу о событиях в системе, а программа в свою очередь, может на них реагировать. Сообщения может отсылать не только Windows, но и сами приложения. Это является одним из способов организации связи между процессами в системе. Конечно, программа может отсылать сообщения и самой себе.

СОВЕТ

Сообщение можно отослать функцией SendMessage или ее асинхронным аналогом PostMessage.

Для приема сообщений в программе должен находиться «цикл сообщений» («message loop») который обычно выглядит так:

Функция GetMessage принимает следующие параметры:

  • LPMSG lpMsg – указатель на структуру сообщения, в которую GetMessage вернет результат.
  • HWND hWnd – описатель окна, от которого GetMessage примет сообщение ( NULL означает, что GetMessage принимает сообщения от всех окон, принадлежащих потоку).
  • UINT wMsgFilterMin – наименьший идентификатор сообщения, которое примет GetMessage.
  • UINT wMsgFilterMax – наибольший идентификатор сообщения, которое примет GetMessage (если в значениях параметров wMsgFilterMin и wMsgFilterMax передать 0, функция будет принимать ВСЕ сообщения).

Функция GetMessage не отдает управление программе, пока не придет какое-либо сообщение. Если пришедшее сообщение – WM_QUIT , функция GetMessage вернет 0 . Тогда цикл прервется, и программа завершит свою работу. При любом другом сообщении функция GetMessage возвращает значение больше нуля, и начинатся выполнение тела цикла. При ошибке GetMessage возвращает -1.

СОВЕТ

Сообщение WM_QUIT лучше посылать с помощью специальной функции PostQuitMessage(int iExitCode). Эта функция отошлет сообщение WM_QUIT, а в параметре wParam передаст код завершения программы, указанный в iExitCode.

Функция DispatchMessage должна вызвать «функцию обработки сообщений». В простейшем варианте она выглядит так:

При вызове этой функции ей передаются следующие параметры:

  • HWND hWnd – описатель окна, от которого пришло сообщение.
  • UINT message – идентификатор сообщения.
  • WPARAM wParam и LPARAM lParam – параметры сообщения.

Функция обработки сообщений не обязательно должна иметь имя WndProc . Таких функций в программе может быть несколько, но их прототипы обязательно должны выглядеть так:

При вызове этой функции DispatchMessage передает в параметре message идентификатор сообщения. По этому идентификатору производится выборка и выполняется какое-либо действие ( «реакция на сообщение» ).

В Windows существует очень много сообщений! Писать обработчики для всех сообщений – нереальная задача. Чтобы Windows сама обработала бесполезное для вас сообщение, необходимо вызвать функцию DefWindowProc :

Желательно передавать все необработанные сообщения этой функции, а результат ее выполнения возвращать при выходе из WndProc . Это очень важно, так как от обработки некоторых сообщений Windows ждет возврата конкретных результатов или действий.

Одной функцией обработки сообщений могут пользоваться несколько окон, но для одного окна может существовать только одна функция обработки сообщений! Как же система определяет, какой именно функцией обработки сообщения пользоваться для конкретного окна и где она находится?! За это отвечает «класс окна» («window class»).

CLASSные окна

При создании нового окна ему присваивается «Класс окна» (window class). Класс окна задает оконную функцию, используемую по умолчанию. Кроме этого, класс окна задает другие параметры окна, такие, как стиль, меню окна, цвет рабочей области и т.д. Разные классы окон могут указывать на одну и ту же функцию обработки сообщений. Для создания класса его необходимо зарегистрировать.

Итак, регистрация! За нее отвечает функция RegisterClass . В ее параметре необходимо передать указатель на структуру WNDCLASS . Обычно для заполнения структуры и вызова RegisterClass создают отдельную функцию. Но это — дело вкуса.

Вот простейший пример такой функции:

  • WNDPROC lpfnWndProc – адрес функции обработки сообщений.
  • HINSTANCE hInstance – уже знакомая переменная, описывающая экземпляр.
  • LPCTSTR lpszClassName – имя нового класса.
  • HICON hCursor – описатель курсора мыши.
  • HBRUSH hbrBackground – цвет рабочей области окна.

Функция RegisterClass возвращает уникальный «описатель класса окна» типа ATOM . Если при регистрации класса произошла ошибка, это значение будет равно нулю. Чтобы узнать, что произошло, можно вызвать функцию GetLastError() .

Существует также функция RegisterClassEx. Это аналог функции RegisterClass с возможностью присвоения окнам маленькой иконки. При работе с этой функцией необходимо пользоваться структурой WNDCLASSEX.

ПРИМЕЧАНИЕ

Если вы решились работать с GUI Windows вручную, то пользоваться нужно именно RegisterClassEx, поскольку приложение, не имеющее маленькой иконки, сегодня выглядит в Windows как минимум странно. – прим.ред.

СОВЕТ

Следите, чтобы имя вашего класса не совпадало с именами системных классов (например: button или edit).

ПРИМЕЧАНИЕ

Я описал не всю структуру. Все незаполненные поля, которых нет в примере, сейчас равны нулю. Об их значениях можно узнать из MSDN.

Сообщения от окон, созданных на базе класса, зарегистрированного описанной выше функцией RegMyWindowClass, будут обрабатываться функцией с именем WndProc. Чтобы функция WndProc поняла, от какого именно окна пришло сообщение, ей передается уникальный описатель окна HWND .

Our Windows

На вашем месте у меня возникло бы желание увидеть те самые пресловутые окна, из-за которых столько шума. Окно в Windows создается функцией CreateWindow . Вот ее прототип:

Как видите, у функции множество параметров:

  • LPCTSTR lpClassName – имя класса для создаваемого окна (это имя использовалось при регистрации класса).
  • LPCTSTR lpWindowName – имя окна.
  • DWORD dwStyle – стиль окна.
  • int x – позиция по горизонтали верхнего левого угла окна.
  • int y – позиция по вертикали.
  • int nWidth – ширина окна.
  • int nHeight – высота окна.
  • HWND hWndParent – используется для создания «дочернего окна» («child window»). Сюда передается описатель «родительского окна» («parent window»).
  • HMENU hMenu – описатель меню (если hMenu равно нулю, используется меню класса, указанного в lpClassName ).
  • HINSTANCE hInstance – экземпляр приложения.
  • LPVOID lpParam – указатель на пользовательский параметр окна. Этот указатель со всеми остальными параметрами функции CreateWindow будет занесен в структуру CREATESTRUCT . В сообщениях WM_CREATE или WM_NCCREATE параметр lParam будет содержать указатель на эту структуру.

Функция CreateWindow возвращает уникальный описатель окна HWND . Если функция вернула ноль, значит, во время создания окна произошла ошибка. Какая именно, можно узнать, вызвав функцию GetLastError .

ПРИМЕЧАНИЕ

Существует также функция CreateWindowEx, в которой дополнительно присутствует параметр dwExStyle. С его помощью можно создать окно с дополнительными стилями.

План полета

Итак, сейчас я упрощенно расскажу, что же произойдет, если щелкнуть по окну левой кнопкой мыши.

  1. Пользователь нажимает левую кнопку мыши в то время когда курсор мыши находится над рабочей областью окна.
  2. Windows помещает сообщение WM_LBUTTONDOWN в очередь потока.
  3. Цикл обработки сообщения должен вынуть сообщение с помощью функции GetMessage и передать его на обработку функции DispatchMessage .
  4. Функция DispatchMessage находит окно, которому предназначено сообщение и помещает сообщение в его очередь.
  5. Функция окна обрабатывает сообщение WM_LBUTTONDOWN и возвращает результат.
  6. Тело цикла заканчивается, и управление снова передается функции GetMessage для ожидания новых сообщений.

Итого

WinMain, регистрация класса, цикл сообщений, функция обработки сообщений, создание окна. Как все это связать?! Вот код, который объединяет все написанное выше в одну программу:

Вот, в принципе, и все! Это полноценное приложение на WinAPI.

Программа регистрирует класс, создает окно этого класса и обслуживает сообщение WM_LBUTTONUP (оно приходит по событию отпускания левой кнопки мыши), показывает окно, и после обработки сообщения снова возвращается в цикл сообщений, находящийся в WinMain .

Признаю, что данная статья и программа опускает очень много деталей! Многие вещи были не раскрыты (например, остальные переменные структуры WNDCLASS ). Все это сделано для того, чтобы максимально упростить статью и уменьшить код программы.

Создание простой обертки над WinAPI для оконных приложений

Некоторое время назад я увлекался созданием оконной библиотеки под Windows на C++. И сегодня я расскажу как написать простую обертку над WinAPI для создания оконных приложений.

Как известно, приложение на голом API состоит из функции WinMain(аналог main для оконных приложений) функции WinProc для обработки сообщений.

Главная сложность при «оборачивании» функций API в классы состоит в том чтобы скрыть WinProc и сделать удобоваримую систему обработки сообщений.

Наша обертка будет состоять из двух классов: CApp и CWnd. Первый — это класс приложения, внутри него нахоится основной цикл сообщений. Второй — это класс окна.

Для начала напишем класс приложения. Он совсем простой:

//Класс нашего приложения
class CApp
<
public :
//Функция запуска нашего приложения
//содержит в себе цикл сообщений
void Run()
<
MSG msg;
while (GetMessage(&msg,0,0,0)!=0)
<
TranslateMessage(&msg);
DispatchMessage(&msg);
>
>
>;

* This source code was highlighted with Source Code Highlighter .

Он содержит единственную функцию Run, внутри которой находится цикл сообещний.
В цикле программа получает сообещения и перенаправляет их в оконную функцию(WinProc).

Далее мы создадим класс CWnd:

//Класс простого окна
class CWnd
<
//Тип указателя на функцию
typedef LRESULT (CWnd::*FuncPointer)(LPARAM,WPARAM);

//Структура указателя на функцию-обработчик
struct POINTER
<
CWnd* wnd; //Указатель на класс, которому принадлежит обработчик
FuncPointer func;
>;

protected :
HWND _hwnd; //Хендл нашего окна
map _msgmap;//Карта сообщений

* This source code was highlighted with Source Code Highlighter .

Он содержит хендл(HWND) окна и карту(map) сообщений.
Также объявляем тип указтеля на функцию-обработчик(FuncPointer) и структуру POINTER. Эта структура содержит указатель на функцию и указатель на объект класса, которому она принадлежит.

Затем добавим функцию создания окна:

//Функция создания окна
bool Create(
HWND parent, //Родительское окно, если 0 — то главное окно
LPCWSTR text, //Заголовок окна
DWORD exstyle,DWORD style, //Стили окна
int x, int y, int w, int h, //Размеры и положение
UINT id //Идентификатор окна
)
<
//Регистрируем класс окна
WNDCLASSEX wndc;
wndc.lpszClassName=L «MyWnd» ;
wndc.cbSize= sizeof (WNDCLASSEX);
wndc.lpfnWndProc=WNDPROC(_WndProc); //Оконная процедура
wndc.cbClsExtra=0;
wndc.cbWndExtra=0;
wndc.hbrBackground=HBRUSH(COLOR_WINDOW); //Цвет фона окна
wndc.hInstance=GetModuleHandle(0); //Хендл приложения
wndc.hCursor=LoadCursor(0,IDC_ARROW); //Загружаем старндартный курсор
wndc.style=CS_HREDRAW|CS_VREDRAW;
wndc.hIcon=0;
wndc.hIconSm=0;
wndc.lpszMenuName=0;
RegisterClassEx(&wndc);

//Создаем само окно
_hwnd=CreateWindowEx(exstyle,L «MyWnd» ,text,
style|WS_CLIPCHILDREN, //Стиль WS_CLIPCHILDREN нужен для того, чтобы дочерние контролы не мигали при перерисовке
x,y,w,h,parent,HMENU(id),
GetModuleHandle(0),
this //Передаем в оконную функцию указатель на класс нашего окна
);

if (!_hwnd) return false ;
return true ;
>

* This source code was highlighted with Source Code Highlighter .

В ней мы регистрируем класс окна и создаем само окно с помощью функции CreateWindowEx.
Так же мы передаем в CreateWindowEx указатель на экземпляр CWnd, чтобы в дальнейшем можно было связать APIшный хендл(HWND) и наш экземпляр CWnd.

Теперь передем к главному.
Добавляем оконную функцию в наш класс. Она должна быть статической.

//Оконная функция
//функция в которую поступают сообщения для обработки
static LRESULT CALLBACK _WndProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
<
CWnd *wnd=0;
//Сообщения WM_NCCREATE приходит до WM_CREATE
//т.е при создании окна
if (message==WM_NCCREATE)
<
//Получаем указатель на экземпляр нашего окна, который мы передали в функцию CreateWindowEx
wnd=(CWnd*)LPCREATESTRUCT(lparam)->lpCreateParams;
//И сохраняем в поле GWL_USERDATA
SetWindowLong(hwnd,GWL_USERDATA,LONG(LPCREATESTRUCT(lparam)->lpCreateParams));
wnd->_hwnd=hwnd;
>
//Теперь получаем указатель на наш экземлпяр окна, но уже из поля GWL_USERDATA
wnd=(CWnd*)GetWindowLong(hwnd,GWL_USERDATA);
if (wnd)
<
//Ищем сообщение в карте
map ::iterator it;
it=wnd->_msgmap.find(message);

//Если сообщение не найдено, то обрабатываем его по умолчанию
if (it==wnd->_msgmap.end()) return DefWindowProc(hwnd,message,wparam,lparam);
else
<
POINTER msg=it->second;
//Вызываем функцию обработчик
LRESULT result=(msg.wnd->*msg.func)(lparam,wparam);
if (result) return result;
>
>
return DefWindowProc(hwnd,message,wparam,lparam);
>
>;

* This source code was highlighted with Source Code Highlighter .

В ней происходит следующее. Сначала мы отлавливаем сообщение WM_NCCREATE. В нем мы получаем переданный в CreateWindowEx указатель и сохраняем его в поле GWL_USERDATA окна. Теперь мы в любой момент можем получить указатель на экземпляр CWnd имея на руках только HWND.
Далее ищем в карте текущее сообщение, и если оно есть вызываем по указателю обработчик из этой карты.

Теперь напишем функцию которая будет добавлять сообщение в карту:

//Функкция добавления сообщения в карту
//Приводит указатель на функцию-член класса T к указателю на функцию-член CWnd
template
bool AddMessage(UINT message,CWnd* wnd,LRESULT (T::*funcpointer)(LPARAM,WPARAM))
<
if (!wnd || !funcpointer) return false ;

POINTER msg;
msg.wnd=wnd;
msg.func=reinterpret_cast (funcpointer);

* This source code was highlighted with Source Code Highlighter .

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

Вот и все, наша обертка готова.
Пример использования:

//Наследуем класс нового окна от CWnd
class CMyWnd: public CWnd
<
public :
CMyWnd()
<
//Добавляем обработчики сообщений WM_CREATE и WM_DESTROY
AddMessage(WM_CREATE, this ,&CMyWnd::OnCreate);
AddMessage(WM_DESTROY, this ,&CMyWnd::OnDestroy);
>
LRESULT OnCreate(LPARAM lparam,WPARAM wparam)
<
MessageBox(0,_T( «HelloHabr!» ),_T( «» ),0);
return 0;
>
LRESULT OnDestroy(LPARAM lparam,WPARAM wparam)
<
PostQuitMessage(0);
return 0;
>
>;

int APIENTRY WinMain(HINSTANCE hinst,HINSTANCE prev,LPSTR cmd, int showcmd)
<
//Создаем наше окно
CMyWnd *wnd= new CMyWnd;
wnd->Create(0,L «HelloHabr!» ,0,WS_OVERLAPPEDWINDOW|WS_VISIBLE,300,300,500,400,0);

//Запускаем приложение
CApp *app= new CApp;
app->Run();
return 0;
>

* This source code was highlighted with Source Code Highlighter .

Читайте также:  Чем хорош linux от windows
Оцените статью