ご存知のように、ベアAPI上のアプリケーションは、メッセージを処理するためのWinProc関数のWinMain関数(ウィンドウアプリケーションのmainに類似)で構成されています。
クラスでAPI関数をラップする際の主な難点は、WinProcを非表示にして、消化可能なメッセージ処理システムを作成することです。
ラッパーは、CAppとCWndの2つのクラスで構成されます。 最初はアプリケーションクラスで、内部はメインメッセージループです。 2番目はウィンドウクラスです。
最初に、アプリケーションクラスを記述します。 それは非常に簡単です:
//
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関数が含まれており、その中にpromiseループがあります。
ループ内で、プログラムはメッセージを受信し、ウィンドウ関数(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)とメッセージのマップが含まれています。
また、ハンドラー関数(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関数を使用してウィンドウ自体を作成します。
また、CWndのインスタンスへのポインターをCreateWindowExに渡し、後で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フィールドに保存します。 これで、HWNDのみを手元に置いてCWndインスタンスへのポインターをいつでも取得できます。
次に、マップ内の現在のメッセージを探し、もしそうであれば、ポインターでこのマップからハンドラーを呼び出します。
次に、メッセージをマップに追加する関数を作成します。
//
// - 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から継承し、メッセージのハンドラーを追加し、それらをマップに追加すると、アプリケーションの準備が整います。