のらぬこの日常を描く

ノージャンルのお役立ち情報やアニメとゲームの話、ソフトウェア開発に関する話などを中心としたブログです。

ATL/WTLのメッセージマップについて

今日、WTL*1で書かれたアプリのデバッグをしておりました。

その関係で、ATLのメッセージ処理周りを調べていたんですが、その過程で(僕的には)かなり面白いコードを見ることができたので、それについて少し書いてみます。

C++で書かれたフレームワークの場合、大抵1つの親windowの実体が、1つの(C++)classのインスタンスに対応付けられています。

Window毎に、HWNDを保持する1つのc++クラスが生成される感じです。

class CWnd {
public:
  LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam );
protected:
  HWND hWnd;
};

さて、class に定義された CWnd::WindowProc内でメッセージ処理を行うには、

RegisterClass() で登録した ::WindowProc( HWND hWnd, ... ); にて、hWnd から 対応するthisポインタを取得する必要がある。

MFCではこれを、グローバル定義された CMapもどきを使ってメッセージ処理の度に hWnd から CWnd * を取得しているのですが、

ATL 場合は・・・・・・

template <class TBase /* = CWindow */, class TWinTraits /* = CControlWinTraits */>
class CContainedWindowT : public TBase
	// Windowメッセージを最初に受信する関数がこれ。
	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
		....
		if(uMsg != WM_NCDESTROY)
			lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
		....
	}
}
	// そして、CallWindowProcで、メッセージを再postした後
	LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		return ::CallWindowProc(m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam);
	}
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	// ちょwwwwwwwwwww
	// HWND を CWindowImplBaseT< TBase, TWinTraits >* にキャストとかwwwwwwwww
	CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
	....

::CallWindowProc 呼び出し時の m_hWnd には、確かに正規のWindowHandle が格納されている。

ところが

LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc( HWND hWnd, ....

で受け取った hWnd は、正しいpThis が格納されている???

いやでも、少なくともWin32NativeAPIが気を聞かせてhWnd を pThis に置き換えてくれるわけないし・・・・

まてよ、、、

LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc

これが呼ばれる直前に、スタックに積まれた引数を細工することができれば・・・・

SetWindowLongPtr(hWnd, GWLP_WNDPROC, ...

してるところを見てみる。

template <class TBase, class TWinTraits>
BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hWnd)
{
	result = m_thunk.Init(GetWindowProc(), this);

	WNDPROC pProc = m_thunk.GetWNDPROC();
	WNDPROC pfnWndProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
	m_pfnSuperWindowProc = pfnWndProc;
	m_hWnd = hWnd;
	return TRUE;
}

m_thunk ってすごく怪しい。サンクって名前からしてあからさまに怪しい!

変数ウォッチで中身を見てみる。

f:id:mikenekoDX:20100412211716p:image

う、うわぁ・・・・

mov dword ptr [esp + 4h], pThis
jmp 構造体の終端アドレスとCWindowImplBaseT< TBase, TWinTraits >::WindowProcアドレスの差分が格納されてるはず。

昔、OS作ってた時も、こんなテクを随所で見かけた気がしますが、まさかATLでこんなことやってたとは。

*1Windows Template Library、MS製のWindowsアプリケーション開発フレームワークの1つ