while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
以上是Windows应用程序最基本的消息循环处理算法(本文先不考虑
TranslateAccelerator)。各种消息的最终处理由
DispatchMessage来完成。
TranslateMessage在此前会进行一些键盘消息预处理操作。在《Windows程序设计第五版》中,Charles Petzold只提到
TranslateMessage的作用是从
WM_KEYDOWN、
WM_SYSKEYDOWN中产生‘字符消息’(
WM_CHAR、
WM_DEADCHAR、
WM_SYSCHAR或
WM_SYSDEADCHAR)。典型的一组击键消息过程为
WM_KEYDOWN——
WM_CHAR——
WM_KEYUP。MSDN中则指明当msg被
TranslateMessage进行一定处理后将返回非零值,否则如果
TranslateMessage不处理这个消息就返回0,此外对于
TranslateMessage的说明不比Charles Petzold多多少。
那么
WM_IME_CHAR是怎么来的呢?这个问题一直困扰着我,在阅读了一堆关于输入法的资料后,我依然对
IME的机制不是很清楚。所以用尽了笨办法调试程序以期寻找一些线索。
在中文输入法下
TranslateMessage并不产生
WM_CHAR。此时它处理的
WM_KEYDOWN消息中的
wParam参数比较特殊,是
VK_PROCESSKEY,而不是一般的virtual-key code。假设我们要打一个‘进’字,如果用微软拼音输入法的话,按键的顺序是 'J' 'I' 'N' 'SPACE' 'SPACE'。当按下'J'时
GetMessage得到一个
WM_KEYDOWN,wParam是
VK_PROCESSKEY。
TranslateMessage处理过程中会向消息队列里面放入
WM_IME_STARTCOMPOSITION和WM_IME_COMPOSITION。在依次按下'I' 'N' 'SPACE'时只有
WM_IME_COMPOSITION消息。按下最后一个'SPACE'时
TranslateMessage会产生
WM_IME_COMPOSITION和 WM_IME_ENDCOMPOSITION。我用下面的方法结合其他现象得出的结论:
在
TranslateMessage之前和
TranslateMessage之后两处都添加以下代码,调试后察看hFile指向的文件。
if(msg.message==WM_KEYDOWN&&msg.wParam== VK_PROCESSKEY)
{
while(PeekMessage(&msg2,msg.hwnd,0,0,PM_REMOVE))
{
glen=wsprintf(buf,L"%d,",msg2.message);
WriteFile (hFile, buf,length*2 , &rlength, NULL) ;
}
}
if(msg.message==WM_KEYDOWN&&msg.wParam== VK_PROCESSKEY)
{
while(PeekMessage(&msg2,msg.hwnd,0,0,PM_REMOVE))
{
glen=wsprintf(buf,L"%d,",msg2.message);
WriteFile (hFile, buf,length*2 , &rlength, NULL) ;
}
}
进一步调试,我看到我创建的
WINPROC在处理按下最后一个'SPACE'产生的
WM_IME_COMPOSITION时,在
DefWindowProc中根据这个
WM_IME_COMPOSITION,产生一个
WM_IME_CHAR,然后再SEND给本地线程,实际上也就是以
WM_IME_CHAR为参数,递归调用了一次
WINPROC,在这次调用的
WINPROC中产生
WM_CHAR,POST给本地线程,达到通知应用程序我们想输入'进'的目的。所以
TranslateMessage对于中文输入也是非常重要的。经过进一步反汇编研究,大概看清了
TranslateMessage的结构类似以下代码:
BOOL TranslateMessage(const MSG *pmsg)
{
if(pmsg->wParam==VK_PROCESSKEY) // #define VK_PROCESSKEY 0x00e5
ImmTranslateMessage( pmsg->hwnd, pmsg->message, pmsg->wParam, pmsg->lParam );
else
TranslateMessageEx( pmsg, 0 );
}
BOOL TranslateMessage(const MSG *pmsg)
{
if(pmsg->wParam==VK_PROCESSKEY) // #define VK_PROCESSKEY 0x00e5
ImmTranslateMessage( pmsg->hwnd, pmsg->message, pmsg->wParam, pmsg->lParam );
else
TranslateMessageEx( pmsg, 0 );
}
反汇编,我又加了两个注释
77D48BF6 mov edi,edi
77D48BF8 push ebp
77D48BF9 mov ebp,esp
77D48BFB push esi
77D48BFC mov esi,dword ptr [ebp+8]
77D48BFF cmp word ptr [esi+8],0E5h
77D48C05 je 77D70A49 ; 77D70A49 处call ImmTranslateMessage
77D48C0B push 0
77D48C0D push esi
77D48C0E call 77D48A19 ; 77D48A19是TranslateMessageEx
77D48C13 pop esi
77D48C14 pop ebp
77D48C15 ret 4
77D48BF8 push ebp
77D48BF9 mov ebp,esp
77D48BFB push esi
77D48BFC mov esi,dword ptr [ebp+8]
77D48BFF cmp word ptr [esi+8],0E5h
77D48C05 je 77D70A49 ; 77D70A49 处call ImmTranslateMessage
77D48C0B push 0
77D48C0D push esi
77D48C0E call 77D48A19 ; 77D48A19是TranslateMessageEx
77D48C13 pop esi
77D48C14 pop ebp
77D48C15 ret 4
ImmTranslateMessage和
TranslateMessageEx我是
dumpbin来的,MSDN中也没有说明.对
ImmTranslateMessage的参数列表我还是比较肯定:
77D70A49 push dword ptr [esi+0Ch]
77D70A4C push dword ptr [esi+8]
77D70A4F push dword ptr [esi+4]
77D70A52 push dword ptr [esi]
77D70A54 call dword ptr ds:[77DA03A0h]
77D70A49 push dword ptr [esi+0Ch]
77D70A4C push dword ptr [esi+8]
77D70A4F push dword ptr [esi+4]
77D70A52 push dword ptr [esi]
77D70A54 call dword ptr ds:[77DA03A0h]
TranslateMessageEx的参数列表我不大肯定
77D48C0B push 0
77D48C0D push esi
77D48C0B push 0
77D48C0D push esi
应该是MSG指针和一个32位的整数,用以下代码处理消息,程序可以正常运行,正常处理字符输入。
typedef BOOL (WINAPI *POSTTYPE)(HWND,UINT,WPARAM,LPARAM);
typedef BOOL (WINAPI *TRANTYPE)(const MSG *, DWORD);
PROC pfnImm = GetProcAddress(GetModuleHandleA( "imm32.dll" ), "ImmTranslateMessage" );
PROC pfnTran = GetProcAddress(GetModuleHandleA( "user32.dll" ), "TranslateMessageEx" );
while (GetMessage(&msg, NULL, 0, 0))
{
if(msg.wParam==VK_PROCESSKEY)
((POSTTYPE)pfnImm)(msg.hwnd,msg.message,msg.wParam,msg.lParam);
else
((TRANTYPE)pfnTran)(&msg,0);
DispatchMessage(&msg);
}
typedef BOOL (WINAPI *TRANTYPE)(const MSG *, DWORD);
PROC pfnImm = GetProcAddress(GetModuleHandleA( "imm32.dll" ), "ImmTranslateMessage" );
PROC pfnTran = GetProcAddress(GetModuleHandleA( "user32.dll" ), "TranslateMessageEx" );
while (GetMessage(&msg, NULL, 0, 0))
{
if(msg.wParam==VK_PROCESSKEY)
((POSTTYPE)pfnImm)(msg.hwnd,msg.message,msg.wParam,msg.lParam);
else
((TRANTYPE)pfnTran)(&msg,0);
DispatchMessage(&msg);
}
PS:在处理游戏中的中文输入的时候死活收不到
WM_IME_STARTCOMPOSITION 消息,开始的时候没想到什么问题,找了好久。后来猛然想到是虚拟键消息转换的问题TranslateMessage。转一篇文章了解一下。