📜 ⬆️ ⬇️

Creating a simple WinAPI wrapper for windowed applications

Some time ago I was fond of creating a window library under Windows in C ++. And today I will tell you how to write a simple wrapper over WinAPI to create window applications.

As you know, an application on a bare API consists of the WinMain function (the analog main for window applications) of the WinProc message handling function.

The main difficulty in wrapping API functions in classes is to hide WinProc and make a digestible message processing system.
')
Our wrapper will consist of two classes: CApp and CWnd. The first is the application class; inside it is the main message loop. The second is the window class.

First, let's write an application class. It is quite simple:
//
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 .

It contains a single Run function, inside which there is a message loop.
In the loop, the program receives messages and redirects them to the window function (WinProc).

Next, we will create a CWnd class:
//
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 .

It contains a handle (HWND) window and a map (map) of messages.
We also declare the pointer type to the handler function (FuncPointer) and the POINTER structure. This structure contains a function pointer and a pointer to the object of the class to which it belongs.

Then add the window creation function:
//
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 .

In it, we register the window class and create the window itself using the CreateWindowEx function.
We also pass a pointer to the instance of CWnd to CreateWindowEx, so that in future we can link the API handle (HWND) and our instance of CWnd.

Now go to the main thing.
Add a window function to our class. It must be static.
//
//
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 .

The following happens in it. First we catch the WM_NCCREATE message. In it, we get the pointer passed to CreateWindowEx and save it in the GWL_USERDATA field of the window. Now, at any time, we can get a pointer to the CWnd instance with only HWND on hand.
Next, look for the current message in the map, and if it is, call the handler from this map by the pointer.

Now we will write a function that will add a message to the map:
//
// - 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 .

This is a template function and it does the following. Converts a pointer to a member function of any CWnd derived class into a pointer to a CWnd member function. This is necessary in order to bring all pointers to the same type.

That's it, our wrapper is ready.
Example of use:
// 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 .

To create a simple window, inherit from CWnd, add handlers for messages, add them to the map and the application is ready.

Source: https://habr.com/ru/post/123648/


All Articles