2008年10月26日日曜日

ウィンドウプロシージャとメンバ関数

コールバック関数の1つウィンドウプロシージャWindowProc()をクラスメンバ関数にしたいという話。
調べたところ、ATLはlinked listでクラスインスタンスを管理しておき、atlbase.inlファイルのAtlWinModuleExtractCreateWndData()関数でthread idをキーにして検索をしていました。これウィンドウメッセージが届くたびにやってる気がします。
読んで勉強になったのは、CreateWindowを呼び出したスレッドにウィンドウメッセージが届くのね。だからthread idがキーとして使える、と。

なら1歩進めてthread local storageに情報を置いてしまえばいいのでは?

class Window{
LRESULT WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam ){
// ここが本体
...;
}
static __declspec(thread) Window* window;
static LRESULT CALLBACK StaticWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){
return window->WindowProc( hwnd, uMsg, wParam, lParam );
}
void CreateWindowOnThisThread(){
Window::window = this;
CreateWindowEx( .../* 適切に */ );
}
};
Window* Window::window = NULL;

こうすれば無難な関数に仕上がりました。ちなみにStaticWindowProc()はかなり負担の少ない実装になっていました。
mov eax, DWORD PTR _lParam$[esp-4]
mov ecx, DWORD PTR _wParam$[esp-4]
mov edx, DWORD PTR fs:__tls_array
push ebx
mov ebx, DWORD PTR _hwnd$[esp]
push eax
mov eax, DWORD PTR _uMsg$[esp+4]
push ecx
mov ecx, DWORD PTR [edx]
mov ecx, DWORD PTR Window::window[ecx]
call Window::WindowProc
pop ebx
ret 16
; 一部構文に矛盾がありますが、読みやすさのためのデマングルですので…
呼び出し規約がCALLBACKから__thiscallに変わるので、無意味なスタックの積み直しが行われてますが、まぁ仕方がありませんね。

0 件のコメント: