Как стать автором
Обновить

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

Время на прочтение6 мин
Количество просмотров31K
Некоторое время назад я увлекался созданием оконной библиотеки под 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<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, добавлем обработчикидля сообщний, добавляем их в карту и приложение готово.
Теги:
Хабы:
0
Комментарии34

Публикации

Истории

Работа

Программист C++
128 вакансий
QT разработчик
7 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн