WindowProc の呼ばれ方

はじめに

「ウィンドウは特定のスレッドに関連付けされて、そのスレッド にてメッセージが処理される。そして、そのメッセージを処理するのは WindowsProc (ウィンドウプロシージャ) である。メッセージループは GetMessage にてメッセージを取り出して、DispatchMessage を使って WindowProc へメッセージがディスパッチされる」

というように、私は理解していました (6/1/2005現在)。 しかし、今コードを書いていてふと

「いや、まてよ。何気なくテンプレートみたいに WinMain を書いてきたけど、WinMain で GetMessage を呼び出す以前に WM_CREATE とかが呼び出されているじゃないか。メッセージループが GetMessage を必要とするなら、そこまで WindowProc が呼び出されるのはおかしいぞ」

という疑問が起きました。 そこで、単純なコードを書いて、それで実 際にどんな動きをするのかデバッガでコツコツと追ってみました

さて、どんな結論になるのか....

ちなみに、テストに使用したコードはこちらです。
サンプルコードのダウンロード [wnd.zip makefile プロジェクト]

1. メッセージの追跡と推測

追跡の方法は単純です。デバッガで上記サンプルプログラム wnd.exe を起動して、WinProc (私のコードのウィンドウプロシージャ) にブレークポイントをはって、どこから呼ばれているか確認しました。以下はそのデバッグログです。

デバッグログに慣れていない方にはちょっと、ごちゃごちゃしてて読みにくいかもしれませんので、コマンドについても簡単にコメントしていきます。

0:000> bl
 0 e 77d4ff50     0001 (0001)  0:*** USER32!CreateWindowExW
 1 e 00401270     0001 (0001)  0:*** wnd!OnCreate
 2 e 004012c0     0001 (0001)  0:*** wnd!OnDestroy
 3 e 004012d0     0001 (0001)  0:*** wnd!OnSize
 4 e 00401240     0001 (0001)  0:*** wnd!OnCommand
 5 e 00401170     0001 (0001)  0:*** wnd!WndProc
 6 e 77d491c6     0001 (0001)  0:*** USER32!GetMessageW
 7 e 77d48bf6     0001 (0001)  0:*** USER32!TranslateMessage
 8 e 77d48a19     0001 (0001)  0:*** USER32!TranslateMessageEx

まず、ブレークポイントは上記の関数に設定してスタートしてみました。私のコードは Unicode ビルドでしたので 「なんとか W」 系の関数にブレークをはりました。

さて、コードの実行開始です。g で実行します。

0:000> g
ModLoad: 76390000 763ad000   C:\WINDOWS\system32\IMM32.DLL
ModLoad: 77dd0000 77e6b000   C:\WINDOWS\system32\ADVAPI32.dll
ModLoad: 77e70000 77f01000   C:\WINDOWS\system32\RPCRT4.dll
ModLoad: 629c0000 629c9000   C:\WINDOWS\system32\LPK.DLL
ModLoad: 74d90000 74dfb000   C:\WINDOWS\system32\USP10.dll
ModLoad: 77c10000 77c68000   C:\WINDOWS\system32\msvcrt.dll
Breakpoint 0 hit
eax=00400000 ebx=7ffdd000 ecx=00001e58 edx=0000c23e esi=00000000 edi=7c80b529
eip=77d4ff50 esp=0012fe18 ebp=0012fe98 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
USER32!CreateWindowExW:
77d4ff50 8bff             mov     edi,edi

まずヒットしたのは CreateWindowExW です。これは順当ですよね。確かに呼び出してますから。ここは実行を進めます。するとさっそく WndProc がヒットしました。 いよいよトレース開始。

0:000> g
ModLoad: 74720000 7476b000   C:\WINDOWS\system32\MSCTF.dll
Breakpoint 5 hit
eax=7ffdf000 ebx=00000000 ecx=00000000 edx=00000012 esi=00401014 edi=0012f798
eip=00401170 esp=0012f734 ebp=0012f75c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
wnd!WndProc:
00401170 55               push    ebp
0:000> kbn
 # ChildEBP RetAddr  Args to Child             
00 0012f730 77d48734 00930878 00000024 00000000 wnd!WndProc [d:\src\test\wnd\wnd.cpp @ 72]
01 0012f75c 77d48816 00401014 00930878 00000024 USER32!InternalCallWinProc+0x28
02 0012f7c4 77d4b4c0 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
03 0012f818 77d4ff37 006c9fb8 00000024 00000000 USER32!DispatchClientMessage+0xa3
04 0012f840 7c90eae3 0012f850 0000003c 006c9fb8 USER32!__fnINOUTLPPOINT5+0x27
05 0012f888 77d5013e 77d50104 00000000 0012fdb0 ntdll!KiUserCallbackDispatcher+0x13
06 0012fd2c 77d501f7 00000000 0012fdb0 0012fdc4 USER32!NtUserCreateWindowEx+0xc
07 0012fdd8 77d4ff83 00000000 00412a30 0012fdc4 USER32!_CreateWindowEx+0x1ed
08 0012fe14 0040110c 00000000 00412a30 0040f01c USER32!CreateWindowExW+0x33
09 0012fe98 00401609 00400000 00000000 00141efb wnd!WinMain+0xbc [d:\src\test\wnd\wnd.cpp @ 47]
0a 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
0b 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000> dv
           hwnd = 0x00930878
            Msg = 0x24
         wParam = 0
         lParam = 1243228

この dv というコマンドはローカル変数を表示します。この場合、WndProc への引数が見えます。

Msg が 0x24 と出ました。0x24 は WM_GETMINMAXINFO です。これが一番最初に WndProc に渡されたメッセージということになります。ちなみに、WinMain のコード行は 47 行目の CreateWindowExW を呼び出して、その内部からの呼び出しで WndProc が呼ばれたことがわかります。

0:000> !vprot 1243228
BaseAddress:       01243000
AllocationBase:    00000000
RegionSize:        6177d000
State:             00010000  MEM_FREE
Protect:           00000001  PAGE_NOACCESS

0:000> dc 0012f730
0012f730  00629258 77d48734 00930878 00000024  X.b.4..wx...$...
0012f740  00000000 0012f85c 00401014 dcbaabcd  ....\.....@.....
0012f750  00000000 0012f798 00401014 0012f7c4  ..........@.....
0012f760  77d48816 00401014 00930878 00000024  ...w..@.x...$...
0012f770  00000000 0012f85c 00000000 00930878  ....\.......x...
0012f780  00000000 00000014 00000001 00000000  ................
0012f790  00000000 00000010 00000000 0012f824  ............$...
0012f7a0  00000000 00000000 00000000 0012f778  ............x...
0:000> ?0012f85c
Evaluate expression: 1243228 = 0012f85c

ちなみに lParam の値は 10 進数で表示されることに気をつけてください。WM_GETMINMAXINFO の時の lParam は MINMAXINFO 構造体へのポインタが渡されるはずですが 1243228 を 16 進数として扱ってそのままメモリを見ますと変なアドレスをさしてしまいます。上記 !vprot コマンドの結果のように割り当てられていないアドレスをさしていることが確認できます。そうではなく、0012f85c をみます。0x0012f85c の 10進数表記が 1243228 です。

下記の緑色で示したところが MINMAXINFO です。

0:000> dc 0012f85c
0012f85c  000000a0 00000018 00000508 00000328  ............(...
0012f86c  fffffffc fffffffc 00000070 0000001b  ........p.......
0012f87c  0000050c 0000032c
00401014 77d4b473  ....,.....@.s..w
0012f88c  77d5013e 77d50104 00000000 0012fdb0  >..w...w........
0012f89c  0012f8dc 0012fdc4 00cf0000 80000000  ................
0012f8ac  80000000 0000012c 00000096 00000000  ....,...........
0012f8bc  00000000 00400000 00000000 40000400  ......@........@
0012f8cc  00000000 0012fdb0 0040f01c 40000400  ..........@....@

ちょっと脱線しました。次へ進みます。

すると、また WndProc がヒットしました。 

0:000> g
Breakpoint 5 hit
eax=7ffdf000 ebx=00000000 ecx=00000000 edx=0000000a esi=00401014 edi=0012f76c
eip=00401170 esp=0012f708 ebp=0012f730 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
wnd!WndProc:
00401170 55               push    ebp
0:000> kbn 100
 # ChildEBP RetAddr  Args to Child             
00 0012f704 77d48734 00930878 00000081 00000000 wnd!WndProc [d:\src\test\wnd\wnd.cpp @ 72]
01 0012f730 77d48816 00401014 00930878 00000081 USER32!InternalCallWinProc+0x28
02 0012f798 77d4b4c0 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
03 0012f7ec 77d4fd29 006c9fb8 00000081 00000000 USER32!DispatchClientMessage+0xa3
04 0012f81c 7c90eae3 0012f82c 00000060 00000060 USER32!__fnINLPCREATESTRUCT+0x8b
05 0012f888 77d5013e 77d50104 00000000 0012fdb0 ntdll!KiUserCallbackDispatcher+0x13
06 0012fd2c 77d501f7 00000000 0012fdb0 0012fdc4 USER32!NtUserCreateWindowEx+0xc
07 0012fdd8 77d4ff83 00000000 00412a30 0012fdc4 USER32!_CreateWindowEx+0x1ed
08 0012fe14 0040110c 00000000 00412a30 0040f01c USER32!CreateWindowExW+0x33
09 0012fe98 00401609 00400000 00000000 00141efb wnd!WinMain+0xbc [d:\src\test\wnd\wnd.cpp @ 47]
0a 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
0b 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23

0x81 は WM_NCCREATE です。 相変わらず CreateWindowEx の内部から呼び出されています。

0:000> g
ModLoad: 77c00000 77c08000   C:\WINDOWS\system32\version.dll
ModLoad: 755c0000 755ee000   C:\WINDOWS\system32\msctfime.ime
ModLoad: 774e0000 7761d000   C:\WINDOWS\system32\ole32.dll
ModLoad: 3a700000 3a756000   C:\WINDOWS\system32\imjp81.ime
ModLoad: 3a600000 3a6d0000   C:\WINDOWS\system32\imjp81k.dll
ModLoad: 5d090000 5d127000   C:\WINDOWS\system32\COMCTL32.dll
ModLoad: 7c9c0000 7d1d4000   C:\WINDOWS\system32\SHELL32.dll
ModLoad: 77f60000 77fd6000   C:\WINDOWS\system32\SHLWAPI.dll
ModLoad: 773d0000 774d2000   C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.2180_x-ww_a84f1ff9\comctl32.dll
ModLoad: 5ad70000 5ada8000   C:\WINDOWS\system32\uxtheme.dll
Breakpoint 5 hit
eax=7ffdf000 ebx=00000000 ecx=00000000 edx=00000000 esi=00401014 edi=0012f7b0
eip=00401170 esp=0012f74c ebp=0012f774 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
wnd!WndProc:
00401170 55               push    ebp
0:000> kbn
 # ChildEBP RetAddr  Args to Child             
00 0012f748 77d48734 00930878 00000083 00000000 wnd!WndProc [d:\src\test\wnd\wnd.cpp @ 72]
01 0012f774 77d48816 00401014 00930878 00000083 USER32!InternalCallWinProc+0x28
02 0012f7dc 77d4b4c0 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
03 0012f830 77d4d1ea 006c9fb8 00000083 00000000 USER32!DispatchClientMessage+0xa3
04 0012f858 7c90eae3 0012f868 00000024 006c9fb8 USER32!__fnINOUTNCCALCSIZE+0x34
05 0012f888 77d5013e 77d50104 00000000 0012fdb0 ntdll!KiUserCallbackDispatcher+0x13
06 0012fd2c 77d501f7 00000000 0012fdb0 0012fdc4 USER32!NtUserCreateWindowEx+0xc
07 0012fdd8 77d4ff83 00000000 00412a30 0012fdc4 USER32!_CreateWindowEx+0x1ed
08 0012fe14 0040110c 00000000 00412a30 0040f01c USER32!CreateWindowExW+0x33
09 0012fe98 00401609 00400000 00000000 00141efb wnd!WinMain+0xbc [d:\src\test\wnd\wnd.cpp @ 47]
0a 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
0b 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000> dc 0012f748
0012f748  00000000 77d48734 00930878 00000083  ....4..wx.......
0012f758  00000000 0012f87c 00401014 dcbaabcd  ....|.....@.....
0012f768  00000000 0012f7b0 00401014 0012f7dc  ..........@.....
0012f778  77d48816 00401014 00930878 00000083  ...w..@.x.......
0012f788  00000000 0012f87c 00000000 00930878  ....|.......x...
0012f798  00000000 00000014 00000001 00000000  ................
0012f7a8  00000000 00000010 00000000 0012f7d0  ................
0012f7b8  00000000 00000000 00000000 0012f790  ................

次は 0x83, WM_NCCALCSIZE です。 この調子でどんどん進みます。

0:000> g
Breakpoint 5 hit
eax=7ffdf000 ebx=00000000 ecx=00000000 edx=0012f878 esi=00401014 edi=0012f754
eip=00401170 esp=0012f6f0 ebp=0012f718 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
wnd!WndProc:
00401170 55               push    ebp
0:000> kbn 100
 # ChildEBP RetAddr  Args to Child             
00 0012f6ec 77d48734 00930878 00000001 00000000 wnd!WndProc [d:\src\test\wnd\wnd.cpp @ 72]
01 0012f718 77d48816 00401014 00930878 00000001 USER32!InternalCallWinProc+0x28
02 0012f780 77d4b4c0 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
03 0012f7d4 77d4fd29 006c9fb8 00000001 00000000 USER32!DispatchClientMessage+0xa3
04 0012f804 7c90eae3 0012f814 00000078 00000078 USER32!__fnINLPCREATESTRUCT+0x8b
05 0012f888 77d5013e 77d50104 00000000 0012fdb0 ntdll!KiUserCallbackDispatcher+0x13
06 0012fd2c 77d501f7 00000000 0012fdb0 0012fdc4 USER32!NtUserCreateWindowEx+0xc
07 0012fdd8 77d4ff83 00000000 00412a30 0012fdc4 USER32!_CreateWindowEx+0x1ed
08 0012fe14 0040110c 00000000 00412a30 0040f01c USER32!CreateWindowExW+0x33
09 0012fe98 00401609 00400000 00000000 00141efb wnd!WinMain+0xbc [d:\src\test\wnd\wnd.cpp @ 47]
0a 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
0b 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23

やっと 0x1 == WM_CREATE が来ました。経験から既にわかっていたように、CreateWindowExW から WndProc が呼び出されています。 

0:000> g
Breakpoint 1 hit
eax=00000001 ebx=00000000 ecx=0012f83c edx=00930878 esi=00401014 edi=0012f754
eip=00401270 esp=0012f6dc ebp=0012f6ec iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
wnd!OnCreate:
00401270 55               push    ebp
0:000> kbn
 # ChildEBP RetAddr  Args to Child             
00 0012f6d8 004011da 00930878 0012f83c 00000001 wnd!OnCreate [d:\src\test\wnd\wnd.cpp @ 102]
01 0012f6ec 77d48734 00930878 00000001 00000000 wnd!WndProc+0x6a [d:\src\test\wnd\wnd.cpp @ 76]
02 0012f718 77d48816 00401014 00930878 00000001 USER32!InternalCallWinProc+0x28
03 0012f780 77d4b4c0 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
04 0012f7d4 77d4fd29 006c9fb8 00000001 00000000 USER32!DispatchClientMessage+0xa3
05 0012f804 7c90eae3 0012f814 00000078 00000078 USER32!__fnINLPCREATESTRUCT+0x8b
06 0012f888 77d5013e 77d50104 00000000 0012fdb0 ntdll!KiUserCallbackDispatcher+0x13
07 0012fd2c 77d501f7 00000000 0012fdb0 0012fdc4 USER32!NtUserCreateWindowEx+0xc
08 0012fdd8 77d4ff83 00000000 00412a30 0012fdc4 USER32!_CreateWindowEx+0x1ed
09 0012fe14 0040110c 00000000 00412a30 0040f01c USER32!CreateWindowExW+0x33
0a 0012fe98 00401609 00400000 00000000 00141efb wnd!WinMain+0xbc [d:\src\test\wnd\wnd.cpp @ 47]
0b 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
0c 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000> dt tagCREATESTRUCTW 0x0012f83c
   +0x000 lpCreateParams   : (null)
   +0x004 hInstance        : 0x00400000
   +0x008 hMenu            : (null)
   +0x00c hwndParent       : (null)
   +0x010 cy               : 150
   +0x014 cx               : 300
   +0x018 y                : 110
   +0x01c x                : 110
   +0x020 style            : 13565952
   +0x024 lpszName         : 0x0012f878  -> 0x57
   +0x028 lpszClass        : 0x00412a30  -> 0x5f
   +0x02c dwExStyle        : 0x100
0:000> du 0x0012f878
0012f878  "Window #1"
0:000> du 0x00412a30
00412a30  "_WND_"

さて、ここまで少し振り返ってみます。

コールスタックに見える関数名を良く見ると、

05 0012f888 77d5013e 77d50104 00000000 0012fdb0 ntdll!KiUserCallbackDispatcher+0x13

というのがあるのが見えます。名前からして、これが本当のディスパッチャー (DispatchMessage ではなく) ではないかと察しをつけまてみます。

次へ進みます。

0:000> g
Breakpoint 0 hit
eax=00930878 ebx=00000000 ecx=0012f83c edx=00930878 esi=00401014 edi=0012f754
eip=77d4ff50 esp=0012f6a4 ebp=0012f6d8 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
USER32!CreateWindowExW:
77d4ff50 8bff             mov     edi,edi
0:000> kbn
 # ChildEBP RetAddr  Args to Child             
00 0012f6a0 0040129c 00000000 0040f068 0040f060 USER32!CreateWindowExW
01 0012f6d8 004011da 00930878 0012f83c 00000001 wnd!OnCreate+0x2c [d:\src\test\wnd\wnd.cpp @ 110]
02 0012f6ec 77d48734 00930878 00000001 00000000 wnd!WndProc+0x6a [d:\src\test\wnd\wnd.cpp @ 76]
03 0012f718 77d48816 00401014 00930878 00000001 USER32!InternalCallWinProc+0x28
04 0012f780 77d4b4c0 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
05 0012f7d4 77d4fd29 006c9fb8 00000001 00000000 USER32!DispatchClientMessage+0xa3
06 0012f804 7c90eae3 0012f814 00000078 00000078 USER32!__fnINLPCREATESTRUCT+0x8b
07 0012f888 77d5013e 77d50104 00000000 0012fdb0 ntdll!KiUserCallbackDispatcher+0x13
08 0012fd2c 77d501f7 00000000 0012fdb0 0012fdc4 USER32!NtUserCreateWindowEx+0xc
09 0012fdd8 77d4ff83 00000000 00412a30 0012fdc4 USER32!_CreateWindowEx+0x1ed
0a 0012fe14 0040110c 00000000 00412a30 0040f01c USER32!CreateWindowExW+0x33
0b 0012fe98 00401609 00400000 00000000 00141efb wnd!WinMain+0xbc [d:\src\test\wnd\wnd.cpp @ 47]
0c 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
0d 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000> dc 0012f6a0
0012f6a0  00401014 0040129c 00000000 0040f068  ..@...@.....h.@.
0012f6b0  0040f060 50000000 00000000 00000000  `.@....P........
0012f6c0  00000000 00000000 00930878 00000001  ........x.......
0012f6d0  00000000 00000000 0012f6ec 004011da  ..............@.
0012f6e0  00930878 0012f83c 00000001 0012f718  x...<...........
0012f6f0  77d48734 00930878 00000001 00000000  4..wx...........
0012f700  0012f83c 00401014 dcbaabcd 00000000  <.....@.........
0012f710  0012f754 00401014 0012f780 77d48816  T.....@........w
0:000> du 0040f068
0040f068  "button"
0:000> du 0040f060
0040f060  "OK"

自分で書いた OnCreate からの CreateWindowExW の呼び出しなので問題ありません。引数から button とか OK という文字が見えることからもボタンの生成箇所です。

ちなみに、 私が 0x0012f6a0 とかを見ているのに疑問を持っている方もいると思いますので簡単に補足しておきます。これはスタックフレームのベースポインタです。ここからスタックのメ モリを見ると、EBP フレームなら最初にリターンアドレス、次に関数への引数が続きます。Windows API の呼び出し規則の基本は __stdcall です。__stdcall というのは、スタックの巻き戻しは呼び出された関数側、引数をスタックにプッシュするときはパラメータリストの右から左へプッシュする、ということを決め ているものです。(右から左、というのは、例えば Foo (a, b, c) という関数ならば、c , b, a の順番でスタックにプッシュする、ということです。スタックはアドレスの若い方に伸張しますから、こうすると、アドレスの若い方から a, b, c が見えます。デバッガのメモリ出力はアドレスの若い方から見えますので、a, b, c の順番で見えます。これはソースコードで a, b, c と書いた順番と同じですからわかりやすいですよね)

さて、話を戻してどんどん進みます。

が、以後、たくさんのメッセージが呼び出されて、その都度スタックを書くのは大変ですし、読みにくいので GetMessage のところまで一気に進みます。途中で確認できたメッセージは次の通りです。

0:000> * 0x210 == WM_PARENTNOTIFY
0:000> * 0x18 == WM_SHOWWINDOW
0:000> * 0x46 == WM_WINDOWPOSCHANGING
0:000> * 0x46 == WM_WINDOWPOSCHANGING
0:000> * 0x1c == WM_ACTIVATEAPP
0:000> * 0x86 == WM_NCACTIVATE
0:000> * 0xd == WM_GETTEXT
0:000> * 0x6 == WM_ACTIVATE
0:000> * 0x281 == WM_IME_SETCONTEXT

この後、imejpstmain81 などというウィンドウが作られるのも見えました。テキストを編集するようなのは何も書いてないんですけどね。

0:000> kbn 100
 # ChildEBP RetAddr  Args to Child             
00 0012f4fc 77d9b30d 00000000 0012f594 0012f594 USER32!CreateWindowExW
01 0012f6d0 77d9bf58 00143ec0 e0010411 00734b60 USER32!CreateIMEUI+0xa4
02 0012f710 77d9c4de 00000000 00000281 00000001 USER32!ImeSetContextHandler+0x40
03 0012f734 77d5b82a 003d0b96 00000281 00000001 USER32!ImeWndProcWorker+0x2f4
04 0012f764 77d7b3a2 00734b60 006c9c78 00000001 USER32!SendMessageWorker+0x448
05 0012f7ac 77d4b3f9 00930878 00000281 00000001 USER32!RealDefWindowProcWorker+0x136c
06 0012f7c8 77d4b393 00930878 00000281 00000001 USER32!RealDefWindowProcW+0x47
07 0012f810 00401236 00930878 00000281 00000001 USER32!DefWindowProcW+0x72
08 0012f82c 77d48734 00930878 00000281 00000001 wnd!WndProc+0xc6 [d:\src\test\wnd\wnd.cpp @ 84]
09 0012f858 77d48816 00401014 00930878 00000281 USER32!InternalCallWinProc+0x28
0a 0012f8c0 77d4b89b 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
0b 0012f8fc 77d4b903 006c9fb8 006c9f50 00000001 USER32!SendMessageWorker+0x4a5
0c 0012f91c 76392b32 00930878 00000281 00000001 USER32!SendMessageW+0x7f
0d 0012f950 77d9b4c6 00930878 05180777 00000001 IMM32!ImmSetActiveContext+0x134
0e 0012f96c 77d9b9aa 00930878 00000001 00734b60 USER32!FocusSetIMCContext+0x28
0f 0012fbc0 77d9c487 00143ec0 00000287 00000017 USER32!ImeSystemHandler+0x2a0
10 0012fbe4 77d4b50c 003d0b96 00000287 00000017 USER32!ImeWndProcWorker+0x29d
11 0012fc0c 7c90eae3 0012fc1c 00000018 00734b60 USER32!__fnDWORD+0x24
12 0012fc30 77d494be 77d4bfe9 00930878 00000006 ntdll!KiUserCallbackDispatcher+0x13
13 0012fc84 77d4b3f9 00930878 00000006 00000001 USER32!NtUserMessageCall+0xc
14 0012fca0 77d4b393 00930878 00000006 00000001 USER32!RealDefWindowProcW+0x47
15 0012fce8 00401236 00930878 00000006 00000001 USER32!DefWindowProcW+0x72
16 0012fd04 77d48734 00930878 00000006 00000001 wnd!WndProc+0xc6 [d:\src\test\wnd\wnd.cpp @ 84]
17 0012fd30 77d48816 00401014 00930878 00000006 USER32!InternalCallWinProc+0x28
18 0012fd98 77d4b4c0 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
19 0012fdec 77d4b50c 006c9fb8 00000006 00000001 USER32!DispatchClientMessage+0xa3
1a 0012fe14 7c90eae3 0012fe24 00000018 006c9fb8 USER32!__fnDWORD+0x24
1b 0012fe38 77d4d8b0 0040112f 00930878 0000000a ntdll!KiUserCallbackDispatcher+0x13
1c 0012fe98 00401609 00400000 00000000 00141efb USER32!NtUserShowWindow+0xc
1d 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
1e 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000> du 0012f594
0012f594  "imejpstmain81"

それから処理を流し、、、

0:000> * 0x282 == WM_IME_NOTIFY
0:000> * 0x7 == WM_SETFOCUS
0:000> * 0x85 == WM_NCPAINT
0:000> * 0x47 == WM_WINDOWPOSCHANGED
0:000> * 0x5 == WM_SIZE
0:000> * 0x3 == WM_MOVE
0:000> * 0x135 == WM_CTLCOLORBTN
0:000> * 0x7f == WM_GETICON
0:000> * 0x7f == WM_GETICON
0:000> * 0x7f == WM_GETICON
0:000> * 0x88 == WM_SYNCPAINT

などなど、、ウィンドウハンドルを書かないとメッセージだけじゃ大した情報になりませんが、とにかくたくさんメッセージは続きました。

さて、ここまでスタックを全て確認しつつ (えぇ、全て確認したんですよ...疲れた...) 、 処理をウィンドウが表示されるところまで進めました。もともと、このテストプログラムは、ボタンが一個あってそれを押すと Hello と書いたメッセージボックスを表示するものなのです。

オーナードローじゃないのに、メッセージボックスにオーナードローと書いてあるのはご愛嬌。。(オーナードローのところで書いたテストコードを、少し書き 換えて使ってます (^^;;))

こんな風に、明らかに自分のコードで処理をしている箇所でとめてもう一度スタックを見てみます。

0:000> kbn 100
 # ChildEBP RetAddr  Args to Child             
00 0012f63c 77d49418 77d5e2a2 00930878 00000001 ntdll!KiFastSystemCallRet
01 0012f674 77d561c6 00140c36 00930878 00000001 USER32!NtUserWaitMessage+0xc
02 0012f69c 77d6a92e 77d40000 0015d8a0 00930878 USER32!InternalDialogBox+0xd0
03 0012f95c 77d6a294 0012fab8 0012fbdc 00401014 USER32!SoftModalMessageBox+0x938
04 0012faac 77d95fbb 0012fab8 00000028 00930878 USER32!MessageBoxWorker+0x2ba
05 0012fb04 77d80553 00930878 0040f054 0040f030 USER32!MessageBoxTimeoutW+0x7a
06 0012fb24 77d96137 00930878 0040f054 0040f030 USER32!MessageBoxExW+0x1b
07 0012fb40 0040125f 00930878 0040f054 0040f030 USER32!MessageBoxW+0x45
08 0012fb58 004011c6 00930878 00000001 00310b90 wnd!OnCommand+0x1f [d:\src\test\wnd\wnd.cpp @ 96]
09 0012fb74 77d48734 00930878 00000111 00000001 wnd!WndProc+0x56 [d:\src\test\wnd\wnd.cpp @ 75]
0a 0012fba0 77d48816 00401014 00930878 00000111 USER32!InternalCallWinProc+0x28
0b 0012fc08 77d4b89b 00000000 00401014 00930878 USER32!UserCallWinProcCheckWow+0x150
0c 0012fc44 77d4b903 006c9fb8 006c9f50 00000001 USER32!SendMessageWorker+0x4a5
0d 0012fc64 77d7fc7d 00930878 00000111 00000001 USER32!SendMessageW+0x7f
0e 0012fc7c 77d764e8 00737a40 00000000 00737a40 USER32!xxxButtonNotifyParent+0x41
0f 0012fc98 77d577de 00146a04 00000001 00000000 USER32!xxxBNReleaseCapture+0xf8
10 0012fd1c 77d56b96 00737a40 00000202 00000000 USER32!ButtonWndProcWorker+0x6d5
11 0012fd3c 77d48734 00310b90 00000202 00000000 USER32!ButtonWndProcW+0x4c
12 0012fd68 77d48816 77d56b36 00310b90 00000202 USER32!InternalCallWinProc+0x28
13 0012fdd0 77d489cd 00000000 77d56b36 00310b90 USER32!UserCallWinProcCheckWow+0x150
14 0012fe30 77d48a10 0012fe4c 00000000 0012fe98 USER32!DispatchMessageWorker+0x306
15 0012fe40 00401163 0012fe4c 00310b90 00000202 USER32!DispatchMessageW+0xf
16 0012fe98 00401609 00400000 00000000 00141efb wnd!WinMain+0x113 [d:\src\test\wnd\wnd.cpp @ 62]
17 0012ffc0 7c816d4f 00000000 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
18 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000> du 0040f054
0040f054  "Hello"

スタックを見ると、残念ながら上で察しをつけた KiUserCallbackDispatcher  は見えないですね。そこを常に通るならばここからの呼び出しとして見えると思ったのですが。

そこでもう一度方針を立て直すと、USER32!InternalCallWinProc というのが必ず見えるようですね。これをどう呼ぶか、ということに違いがあるようです。メッセージボックスでは、DispatchMessage から UserCallWinProcCheckWow というのを通して ButtonWndProcW というのが呼び出されています。

ググってみると、MS のサポート技術情報に 次のようなスタックが書いてあります。抜粋します。

user32.dll!UserCallWinProcCheckWow(_ACTIVATION_CONTEXT * pActCtx=0x00000000, long (HWND__ *, unsigned int, unsigned int, long)* pfn=0x6388e690, HWND__ * hwnd=0x0001150e, unsigned int msg=513, unsigned int wParam=1, long lParam=30867963, void * pww=0x0102b10c, int fEnableLiteHooks=1)  Line 165 + 0x37

なるほど、これは ACTIVATION_CONTEXT というデータを第一引数に取り、 それから (HWN, INT, INT) の型の関数ポインタ、ウィンドウハンドル、メッセージ、などというパラメータを受け取るようですね。

ソースコードがない以上、この辺は推測して、その推論と動作と矛 盾がないか検証し、そして正解でなくてもいいから (^^;;) 理解の役に立つ程度の推測が立てられればヨシとするほかないでしょう。たとえて言えば、化石から古代の生活を推測するようなものです。私はそこを目指して います

と、言い訳がましいことをいいつつ、考えると UserCallWinProcCheckWow というのはウィンドウプロシージャのフックのようなものが存在しないかどうか含めチェックしてそれを呼び出すためのスタブ関数なのではないか、と思われま す。とすると、DispatchMessage は、UserCallWinProcCheckWow にてウィンドウプロシージャをチェックしてそこで処理が可能ならば、その中で処理をする。ことになります。考えてみれば、先に KiUserCallbackDispatcher  の予想を立てたときは GetMessage と DispatchMessage のメッセージループに入る前だったのですから、ディスパッチャが他に用意されているのは自然ですよね。

それでは、GetMessage のループに入ってから、KiUserCallbackDispather が呼び出されることは無いのか確認してみると、そうではなく、ちゃんと必要に応じて呼び出されるようです。ウィンドウを非表示状態からアクティベートする ときにヒットしました。

0:000> kbn 100
 # ChildEBP RetAddr  Args to Child             
00 0012f5d4 77d493e9 77d493a8 0012f654 00000000 ntdll!KiUserCallbackDispatcher
01 0012f600 77d49402 0012f654 00000000 00000000 USER32!NtUserPeekMessage+0xc
02 0012f62c 77d5e1a9 0012f654 00000000 00000000 USER32!PeekMessageW+0xbc
03 0012f674 77d561c6 0064087e 00d60632 00000001 USER32!DialogBox2+0xe4
04 0012f69c 77d6a92e 77d40000 00160668 00d60632 USER32!InternalDialogBox+0xd0
05 0012f95c 77d6a294 0012fab8 0012fbdc 00401014 USER32!SoftModalMessageBox+0x938
06 0012faac 77d95fbb 0012fab8 00000028 00d60632 USER32!MessageBoxWorker+0x2ba
07 0012fb04 77d80553 00d60632 0040f054 0040f030 USER32!MessageBoxTimeoutW+0x7a
08 0012fb24 77d96137 00d60632 0040f054 0040f030 USER32!MessageBoxExW+0x1b
09 0012fb40 0040125f 00d60632 0040f054 0040f030 USER32!MessageBoxW+0x45
0a 0012fb58 004011c6 00d60632 00000001 006a06ee wnd!OnCommand+0x1f [d:\src\test\wnd\wnd.cpp @ 96]
0b 0012fb74 77d48734 00d60632 00000111 00000001 wnd!WndProc+0x56 [d:\src\test\wnd\wnd.cpp @ 75]
0c 0012fba0 77d48816 00401014 00d60632 00000111 USER32!InternalCallWinProc+0x28
0d 0012fc08 77d4b89b 00000000 00401014 00d60632 USER32!UserCallWinProcCheckWow+0x150
0e 0012fc44 77d4b903 0071a0d8 0071a058 00000001 USER32!SendMessageWorker+0x4a5
0f 0012fc64 77d7fc7d 00d60632 00000111 00000001 USER32!SendMessageW+0x7f
10 0012fc7c 77d764e8 00734850 00000000 00734850 USER32!xxxButtonNotifyParent+0x41
11 0012fc98 77d577de 00146494 00000001 00000000 USER32!xxxBNReleaseCapture+0xf8
12 0012fd1c 77d56b96 00734850 00000202 00000000 USER32!ButtonWndProcWorker+0x6d5
13 0012fd3c 77d48734 006a06ee 00000202 00000000 USER32!ButtonWndProcW+0x4c
14 0012fd68 77d48816 77d56b36 006a06ee 00000202 USER32!InternalCallWinProc+0x28
15 0012fdd0 77d489cd 00000000 77d56b36 006a06ee USER32!UserCallWinProcCheckWow+0x150
16 0012fe30 77d48a10 0012fe4c 00000000 0012fe98 USER32!DispatchMessageWorker+0x306
17 0012fe40 00401163 0012fe4c 006a06ee 00000202 USER32!DispatchMessageW+0xf
18 0012fe98 00401609 00400000 00000000 00142351 wnd!WinMain+0x113 [d:\src\test\wnd\wnd.cpp @ 62]
19 0012ffc0 7c816d4f 0000000e 00000000 7ffdd000 wnd!WinMainCRTStartup+0x184 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
1a 0012fff0 00000000 00401485 00000000 78746341 kernel32!BaseProcessStart+0x23

疲れてきたので (苦笑)、だいたいこのくらいでやめておきます。とりあえず、CreateWindowEx からウィンドウプロシージャが呼び出される箇所 (そして KiUserCallbackDispatcher というメッセージディスパッチャらしきものが存在してそこから複数回呼び出される)が見えましたので、今回は良しとしておきます。

2. 結論

上で見てきた簡単な実験と考察から、元の考えを次のように改めて考えることにしました。

元の理解

「ウィンドウは特定のスレッドに関連付けされて、そのスレッド にてメッセージが処理される。そして、そのメッセージを処理するのは WindowsProc (ウィンドウプロシージャ) である。メッセージループは GetMessage にてメッセージを取り出して、DispatchMessage を使って WindowProc へメッセージがディスパッチされる」

→ 新しい理解

「ウィンドウは特定のスレッドに関連付けされて、そのスレッド にてメッセージが処理される。そして、そのメッセージを処理するのは WindowsProc (ウィンドウプロシージャ) である。メッセージループは GetMessage にてメッセージを取り出して、DispatchMessage を使って WindowProc へメッセージがディスパッチされる。しかし、ウィンドウを生成中 (CreateWindowEx) に DispatchMessage まで到達していないのに WndProc が呼び出される。これは、Windows が KiUserCallbackDispatcher というメッセージディスパッチャを用意しており、CreateWindowEx の内部で必要に応じて KiUserCallbackDispather が呼び出され、そこから WindoProc が (必要に応じて複数回) 呼び出されるからである。


スタックは次のように見える。

wnd!OnCreate
wnd!WndProc+0x6a
USER32!InternalCallWinProc+0x28
USER32!UserCallWinProcCheckWow+0x150
USER32!DispatchClientMessage+0xa3
USER32!__fnINLPCREATESTRUCT+0x8b
ntdll!KiUserCallbackDispatcher+0x13
USER32!NtUserCreateWindowEx+0xc
USER32!_CreateWindowEx+0x1ed
USER32!CreateWindowExW+0x33
wnd!WinMain+0xbc
wnd!WinMainCRTStartup+0x184
kernel32!BaseProcessStart+0x23

GetMessage と DispatchMessage のループに入ると、DispatchMessage の中の UserCallWinProcCheckWow を通してウィンドウプロシージャが呼び出される。その中でモーダルダイアログを出すなどで停止する場合には、画面を再描画するためなどのため、 KiUserCallbackDispather が呼び出される場合がある 

関連資料

ウィンドウのあるプログラム作成 ~ Hello, world 以前

当サイト内「Windows プログラミング入門」の資料。ウィンドウクラスの登録、メッセージループ、ウィンドウプロシージャに関する基本的な解説があります。

おすすめ技術書籍 ~ ウィンドウズプログラミング

私が厳選したウィンドウズプログラミングの参考書をご紹介しています。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説