Как известно, приложение на голом 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<UINT,POINTER> _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<UINT,POINTER>::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<typename T>
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>(funcpointer);
_msgmap.insert(pair<UINT,POINTER>(message,msg));
return true;
}
* 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.Чтобы создать простое окно, наследуемся от CWnd, добавлем обработчикидля сообщний, добавляем их в карту и приложение готово.


