2757com 116

每个窗口会有一个称为窗口过程的回调函数(2757com:WndProc),系统消息

1. 问题

1. 窗口过程 
每个窗口会有一个称为窗口过程的回调函数(WndProc),它带有四个参数,分别为:窗口句柄(Window
Handle),消息ID(Message ID),和两个消息参数(wParam,
lParam),当窗口收到消息时系统就会调用此窗口过程来处理消息。(所以叫回调函数)

消息是指什么?
    
消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向
Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。
   
消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息,它在Windows中声明如下:

关于windows操作系统之消息和消息队列

关于消息和消息队列
不像基于MS-DOS的应用程序,基于Windows的程序是事件驱动的。他们不做任何显示调用来获取输入。而是通过等待系统传递给他们。

系统为应用程序传递所有输入到程序中的不同窗口。每个窗口都有一个称为窗口过程的函数,用于处理所有到该窗口的输入。窗口处理过程处理输入,并将控制返回给系统。

如果一个顶层窗口停止响应消息超过两秒,系统将会认为该窗口为非响应状态。在这种情况下,系统将隐藏该窗口并用拥有同样Z顺序,位置,尺寸和可视化属性的ghost窗口替代该窗口。这种情况下,允许用户移动它,或者改变他的尺寸,甚至关闭应用程序。然后,这也是仅仅可以做的动作,因为应用程序现在是不响应的。当在调试状态下,系统不会产生ghost窗口。

这个段落,讨论如下主题:
windows消息
系统以消息的形式传递输入到窗口的处理过程。系统和应用程序均可产生消息。系统在每次输入事件时,产生一个消息,比如,当用于敲击,移动鼠标或者点击滚动条一类的控件。应用程序引起系统改变也会导致系统产生消息,比如一个应用程序改变了系统的字体资源池或者改变了他自己窗口的大小。一个应用程序可以产生这样的消息,该消息可以引导他的窗口直接执行任务或者和其他应用程序的窗口进行交互。

消息分类:
系统定义消息
当系统和应用程序交互时,系统发送系统消息,以控制应用程序的操作以及给程序传递输入或者其他消息。应用程序也可以发送系统消息,应用程序通常用这些消息来控制通过预先注册的窗口类创造的窗口的行为。

消息常量标记指定了其所属系统预定义消息种类。前缀确定可以翻译或者处理的消息种类。如下。
AMB/ABN ===application desktop toolbar
acm/acn ===animation control
cb/cbn ===combobox control
ccm ===generatl control
cdm ===common dialog box
dfm ===default contex menu
dl ===drag list box
sb ===status bar
tvm/tvn ===tree view contro
udm/udm === up-down controm
wm === general
……
tcm/tcn === tab control
{
Clipboard Messages Clipboard Notifications Common Dialog Box
Notifications Cursor Notifications Data Copy Message Desktop Window
Manager Messages Device Management Messages Dialog Box Notifications
Dynamic Data Exchange Messages Dynamic Data Exchange Notifications Hook
Notifications Keyboard Accelerator Messages Keyboard Accelerator
Notifications Keyboard Input Messages Keyboard Input Notifications Menu
Notifications Mouse Input Notifications Multiple Document Interface
Messages Raw Input Notifications Scroll Bar Notifications Timer
Notifications Window Messages Window Notifications
}

大体上,windows消息覆盖了一个比较宽的范围,包括鼠标键盘,菜单,对话框输入,窗口创建管理,DDE动态数据交换

应用程序定义的消息
应用程序可以创建消息,其自身窗口可以使用,也可以用于和其他进程进行交互。

消息标记符的值应用如下:
1.系统保留了0x0000-0x03ff(即wm_user-1),应用程序不可以使用这些值用于私有消息
2.0×0400(WM_USER)-0x7fff可以用于私有消息
3.如果应用程序在4.0系统上,你可以使用0x8000(wm_app)-0xbfff于私有消息
4.RegisterWindowMessage返回的值在0XC000-0XFFFF之间。这个函数的返回值,可以避免其他进程用同样值而引起的冲突

消息路由
使用使用两种方式来窗口过程消息的路线:post类消息是通过先进先出的消息队列方式,消息队列是临时存储消息的系统定义内存对象,以及sending类消息直接到达窗口过程。

队列消息1
系统在同一时间可以显示任意数量的窗口。为了路由鼠标键盘输入到正确的窗口,系统采用了消息队列。

系统维护了一个系统消息队列,并为每个GUI线程维护了而一个线程专有消息队列。为了避免为非GUI线程过多创建消息队列,所有线程在创建时没有消息队列。系统仅仅在线程第一次发起某个专门用户函数时,创建线程消息队列;没有GUI函数调用将引起消息队列的创建。

未懂:
The system creates a thread-specific message queue only when the thread
makes its first call to one of the specific user functions; no GUI
function calls result in the creation of a message queue.

队列消息2
任何时候,用户移动鼠标,点击按钮或者敲击键盘,鼠标或者键盘驱动将转换这些输入为消息,并将它们放到系统消息队列中。系统在检测它们的目窗口时,同时从系统消息队列中移除它们。然后将他们发送到消息相关窗口的窗口创建线程。线程从它们的消息队列中接收所有鼠标和键盘消息。线程从它们的队列中删除消息,并指引系统将它们发送到正确的窗口过程进行处理。

除了WM_PATIN,WM_TIMER,WM_QUIT消息外,系统一直将它们发送到消息队列的末尾,以确保输入消息的FIFO序列,仅当消息对用中没有其他消息事后,WM_PATIN,WM_TIMER,WM_QUIT才被向前推至窗口处理过程。再就是,多个WM_PAINT消息将被合并为一个,确定所有客户端无效区域到一个单独的区域。合并WM_PATINT就是为了减少窗口冲回客户区内容的次数。

从消息队列中删除一个消息后,应用程序将用DispatchMessage函数direct系统发送这个消息到窗口处理过程以紧凑处理。DispatchMessage没有发送消息位置和时间到窗口过程,应用程序可以通过GetmessageTime和GetMessagePos函数。

当消息队列中没有消息的时候,线程可以使用WaitMessage函数来将控制器交给其他线程,这个函数暂停线程,知道一个新消息到来,该函数才返回。

你也可以调用SetMessageExtraInfo来为当前消息队列附加一个值,通过GetMessageExtraInfo来获取这个值。

非队列消息
绕过了系统和线程消息队列,非队列消息直接发送至窗口过程。系统典型发送非队列消息来通知一个窗口,一个事件影响了它。例如,当用户激活一个新窗口,系统发送给窗口
WM_ACTIVATE, WM_SETFOCUS, and
WM_SETCURSOR消息。这些消息通知窗口它已经被激活了,键盘输入正指向该窗口,鼠标光标已经移至了窗口边框内。当应用程序调用某些系统函数时,也会窗口非队列消息,比如,应用程序在调用SetWindowPos时,系统将发送WM_WINDOWPOSCHANGED消息。

有些消息发送非队列消息:BroadcastSystemMessage,
BroadcastSystemMessageEx, SendMessage, SendMessageTimeout, and
SendNotifyMessage.

消息处理
多线程应用程序,会在每个创建了窗口的线程包含一个消息队列。

MSG msg;
BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
一个应用程序可以通过调用PostQuitMessage来结束其自身的消息循环,响应应用程序主窗口的WM_DESTROY消息,就比较典型。

PostMessage发送一个NULL窗口句柄的消息,该消息将会被放在当前线程消息队列中,应用程序必须处理这个消息。PostMessage也可以通过HWND_TOPMOST
句柄来给所有顶层窗口发送消息。

PostMessage一直能够成功发送消息,通常是一个错误的假设,比如消息队列是满的。一个应用程序应该核查PostMessage的返回值。如果失败了,需要重新发送消息。

SendMessage通常用户父子窗口之间的交互。

SendMessageCallback函数发送一个消息,并立即返回,窗口过程在处理完这个消息后,系统将调用指定的回调函数。该回调函数的具体,请看SendAsyncProc

偶尔,你可能想向所有顶层窗口发送消息。例如,应用程序改变了时间,可以通过SendMessage,并制定HWND_TOPMOST,发送WM_TIMECHANGE.你也可以通过BroadcastSystemMessage函数,并给lpdwRecipients参数制定BSN_APPLICATIONS

消息死锁
1.SendMessage会等待窗口过程处理完毕后才返回,如果窗口过程此时所在线程激昂控制权放弃,那么僵早晨死锁。
2.如果接收线程附加到了和发送线程同一个消息队列,也将导致应用程序死锁的发送

注意,正在接收消息的线程,不应该显示放弃控制权;调用下面函数将引起线程隐私放弃控制权。
DialogBox
?DialogBoxIndirect
?DialogBoxIndirectParam
?DialogBoxParam
?GetMessage
?MessageBox
?PeekMessage
?SendMessage

为了避免潜在死锁,考虑使用SendNotifyMessage或者SendMessageTimeout。要不然,窗口过程可以通过InSendMessage或者InSendMessageEx检测其接收到的消息是否来自其他线程.在处理一个消息时,在调用上面列表中任何函数前,窗口过程应该调用InSendMessage(Ex).如果返回TRUE,窗口过程必须在yeild前,调用ReplyMessage函数。

系统广播消息-略

总结:
1.消息分为系统定义消息和用户自定义消息,其ID值皆有自己的范围。
2.每个线程默认是没有消息队列的,线程只有在第一次调用用户接口时(比如创建窗口),系统才为其创建消息队列。
3.系统自身维护一个系统消息队列,然后还为每个GUI线程线程维护一个线程专门消息队列。
4.鼠标、键盘等驱动,首先将事件转换为消息放置在系统消息队列中,然后系统又通过窗口来确定将其放入到哪个线程消息队列中。
5.线程消息循环取出消息,进行处理,将消息再派发给系统,系统调用消息对应的窗口过程。
6.PostMessage不一定成功,比如队列是满的。
7.避免消息死锁,比如接收消息的窗口过程,在弃权前,需要检测消息是否发自其它线程。否则其它线程将长时间等待。其实我感觉这里不能成为死锁嘛,毕竟还是可能再执行的,只是时间长短而已。
8.需要注意wm_paint,wm_timer,wm_quit等特殊消息
9.系统预定义消息其实大都是那些控件消息,通知消息,系统广播消息等等。

表A-1  Windows消息分布

当在console中调用API
ShellExecuteEx打开”test.iqy”文件时,发现excel会hang住,console退出后excel才会响应,但直接双击”test.iqy”是没有问题的,有意思的是这个情况只有在xp发生,在win7上没有这个问题。

2 消息类型 
1) 系统定义消息(System-Defined Messages)
 
在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间,
可以分为以下三类:
1>窗口消息(Windows Message) 
与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL…
2>命令消息(Command Message):注意这类消息通称为WM_COMMAND
与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。
WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。如果是控件,
HIWORD(wParam)表示控件消息类型
3> 控件通知(Notify Message) 
控件通知消息, 这是最灵活的消息格式, 其Message, wParam,
lParam分别为:WM_NOTIFY,
控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。
2) 程序定义消息(Application-Defined Messages) 
用户自定义的消息, 对于其范围有如下规定:
WM_USER: 0x0400-0x7FFF    (ex. WM_USER+10)
WM_APP(winver>4.0): 0x8000-0xBFFF (ex.WM_APP+4)
RegisterWindowMessage: 0xC000-0xFFFF

2757com 1typedef struct tagMsg
2757com 2{
2757com 3       HWND    hwnd;       //接受该消息的窗口句柄
2757com 4       UINT    message;    //消息常量标识符,也就是我们通常所说的消息号
2757com 5       WPARAM  wParam;     //32位消息的特定附加信息,确切含义依赖于消息值
2757com 6       LPARAM  lParam;     //32位消息的特定附加信息,确切含义依赖于消息值
2757com 7       DWORD   time;       //消息创建时的时间
2757com 8       POINT   pt;         //消息创建时的鼠标/光标在屏幕坐标系中的位置
2757com 9}MSG;
2757com 10

消息相关函数:

DispatchMessage

LONG DispatchMessage(
const MSG* lpmsg
);
1.该函数将消息,通过系统派发给窗口过程
2.如果是一个定时器消息,lParam参数不是空,

消息范围

 

3 消息队列(Message Queues) 
Windows中有两种类型的消息队列
1) 系统消息队列(System Message Queue) 这是一个系统唯一的Queue,设备驱动(mouse,
keyboard)会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列(thread-specific
message queue)中等待处理
2) 线程消息队列(Thread-specific Message Queue) 每一个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用GDI函数时才会创建,默认不创建)。然后线程消息队列中的消息会被送到相应的窗口过程(WndProc)处理.
注意:
线程消息队列中WM_PAINT,WM_TIMER只有在Queue中没有其他消息的时候才会被处理,WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。

   
消息可以由系统或者应用程序产生。系统在发生输入事件时产生消息。举个例子,
当用户敲键,
移动鼠标或者单击控件。系统也产生消息以响应由应用程序带来的变化,
比如应用程序改变系统字体改变窗体大小。应用程序可以产生消息使窗体执行任务,或者与其他应用程序中的窗口通讯。

lParam指向一个函数地址,被调用的将是这个函数,而非窗口过程

GetMessage
应用程序使用该函数返回值来决定是否终止消息循环,并退出程序。

说 明

2. 重现步骤

4 队列消息(Queued Messages)和非队列消息(Non-Queued Messages)
1)队列消息(Queued Messages)
 
消息会先保存在消息队列中,消息循环会从此队列中取消息并分发到各窗口处理
如鼠标,键盘消息。
2) 非队列消息(NonQueued Messages) 消息会绕过系统消息队列和线程消息队列直接发送到窗口过程被处理
如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED 
注意: postMessage发送的消息是队列消息,它会把消息Post到消息队列中;
SendMessage发送的消息是非队列消息, 被直接送到窗口过程处理

消息中有什么?
   我们给出了上面的注释,是不是会对消息结构有了一个比较清楚的认识?如果还没有,那么我们再试着给出下面的解释:
     hwnd
32位的窗口句柄。窗口可以是任何类型的屏幕对象,因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。
    
message用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。消息标识符以常量命名的方式指出消息的含义。当窗口过程接收到消息之后,他就会使用消息标识符来决定如何处理消息。例如、WM_PAINT告诉窗口过程窗体客户区被改变了需要重绘。符号常量指定系统消息属于的类别,其前缀指明了处理解释消息的窗体的类型。
     wParam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
     lParam
通常是一个指向内存中数据的指针。由于WParam、lParam和Pointer都是32位的,因此,它们之间可以相互转换。

该函数将获取和hWnd或者其子窗口相关的消息。

DWORD GetMessagePos(void);

0 ~ WM_USER – 1

重现环境:XP sp3 / Office 2007(其他office版本应该也可以,没有测试)

5 PostMessage(PostThreadMessage), SendMessage 
PostMessage:把消息放到指定窗口所在的线程消息队列中后立即返回。
PostThreadMessage:把消息放到指定线程的消息队列中后立即返回。
SendMessage:直接把消息送到窗口过程处理,处理完了才返回。

消息标识符的值
    
系统保留消息标识符的值在0x0000在0x03ff(WM_USER-1)范围。这些值被系统定义消息使用。应用程序不能使用这些值给自己的消息。应用程序消息从WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到
0X7FFF范围的消息由应用程序自己使用;0XC000到0XFFFF范围的消息用来和其他应用程序通信,我们顺便说一下具有标志性的消息值:
     WM_NULL—0x0000    空消息。
     0x0001—-0x0087    主要是窗口消息。
     0x00A0—-0x00A9    非客户区消息 
     0x0100—-0x0108    键盘消息
     0x0111—-0x0126    菜单消息
     0x0132—-0x0138    颜色控制消息
     0x0200—-0x020A    鼠标消息
     0x0211—-0x0213    菜单循环消息
     0x0220—-0x0230    多文档消息
     0x03E0—-0x03E8    DDE消息
     0x0400              WM_USER
     0x8000              WM_APP
     0x0400—-0x7FFF    应用程序自定义私有消息

该函数返回消息x,y坐标,在多重monitor下,可能有负值。

GetMessageQueueReadyTimeStamp

系统消息

6 GetMessage, PeekMessage 
PeekMessage会立即返回可以保留消息
GetMessage在有消息时返回会删除消息

消息有哪几种?
   其实,windows中的消息虽然很多,但是种类并不繁杂,大体上有3种:窗口消息、命令消息和控件通知消息。
    
窗口消息大概是系统中最为常见的消息,它是指由操作系统和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都会激发窗口消息,还有我们在上面谈到的单击鼠标所产生的消息也是一种窗口消息。
    
命令消息,这是一种特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,例如按下一个按钮,他就会向主窗口发送一个命令消息。
    
控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。她类似于命令消息,当用户与控件窗口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。例如按下一个按钮,他向父窗口发送的消息也可以看作是一个控件通知消息;单击鼠标所产生的消息可以由主窗口直接处理,然后交给控件窗口处理。
   
其中窗口消息及控件通知消息主要由窗口类即直接或间接由CWND类派生类处理。相对窗口消息及控件通知消息而言,命令消息的处理对象范围就广得多,它不仅可以由窗口类处理,还可以由文档类,文档模板类及应用类所处理。
   
由于控件通知消息很重要的,人们用的也比较多,但是具体的含义往往令初学者晕头转向,所以我决定把常见的几个列出来供大家参考:
按扭控件
BN_CLICKED        用户单击了按钮
 BN_DISABLE 按钮被禁止
 BN_DOUBLECLICKED  用户双击了按钮
 BN_HILITE  用/户加亮了按钮
 BN_PAINT  按钮应当重画
 BN_UNHILITE 加亮应当去掉

获取线程最近一次准备处理一个消息的系统时间(GetTickCount)

GetMessageSource
MSGSRC_SOFTWARE_POST表面键盘消息来自software(postmessage标记为software).
MSGSRC_HARDWARE_KEYBOARD 表面消息来自keyboard. MSGSRC_UNKNOWN

WM_USER ~ 0x7FFF

1> 解压iqy_test.zip

7 TranslateMessage, TranslateAccelerator 
TranslateMessage: 把一个virtual-key消息转化成字符消息(character
message),并放到当前线程的消息队列中,消息循环下一次取出处理。
TranslateAccelerator:将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN 或
WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND或WM_SYSCOMMAND消息,
然后把转化后的 WM_COMMAND或WM_SYSCOMMAND直接发送到窗口过程处理,
处理完后才会返回。

组合框控件
 CBN_CLOSEUP 组合框的列表框被关闭
 CBN_DBLCLK 用户双击了一个字符串
 CBN_DROPDOWN 组合框的列表框被拉出
 CBN_EDITCHANGE 用户修改了编辑框中的文本
 CBN_EDITUPDATE 编辑框内的文本即将更新
 CBN_ERRSPACE 组合框内存不足
 CBN_KILLFOCUS 组合框失去输入焦点
 CBN_SELCHANGE 在组合框中选择了一项
 CBN_SELENDCANCEL 用户的选择应当被取消
 CBN_SELENDOK 用户的选择是合法的
 CBN_SETFOCUS 组合框获得输入焦点

消息来源未知

DWORD GetQueueStatus(
UINT flags
);
在消息队列中的消息的类型
flags为要检测的消息类型。

自定义窗口类整数消息

2> 运行http_server.py(需先安装python)

8(消息死锁( Message Deadlocks) 
假设有线程A和B, 现在有以下下步骤
1) 线程A SendMessage给线程B, A等待消息在线程B中处理后返回
2) 线程B收到了线程A发来的消息,并进行处理, 在处理过程中,B也向线程A
SendMessgae,然后等待从A返回。
因为此时, 线程A正等待从线程B返回, 无法处理B发来的消息,
从而导致了/线程A,B相互等待, 形成死锁。多个线程也可以形成环形死锁。
可以使用 SendNotifyMessage或SendMessageTimeout来避免出现死锁。

编辑框控件
 EN_CHANGE 编辑框中的文本己更新
 EN_ERRSPACE 编辑框内存不足
 EN_HSCROLL 用户点击了水平滚动条
 EN_KILLFOCUS 编辑框正在失去输入焦点
 EN_MAXTEXT 插入的内容被截断
 EN_SETFOCUS 编辑框获得输入焦点
 EN_UPDATE 编辑框中的文本将要更新
 EN_VSCROLL 用户点击了垂直滚动条消息含义

返回值得高字节表示当前在消息队列中的消息类型。低字节表示从上次GetQueueStatus,GetMessage或者PeekMessage后被加入队列的消息类型。

InSendMessage

WM_APP ~ 0xBFFF

3> 执行”shell_execute.exe test.iqy”

9 BroadcastSystemMessage 
我们一般所接触到的消息都是发送给窗口的,其实,
消息的接收者可以是多种多样的,它可以是应用程序(applications),
可安装驱动(installable drivers),网络设备(network drivers),
系统级设备驱动(system-level device drivers)等, 
BroadcastSystemMessage这个API可以对以上系统组件发送消息。

列表框控件
 LBN_DBLCLK 用户双击了一项
 LBN_ERRSPACE 列表框内存不够
 LBN_KILLFOCUS 列表框正在失去输入焦点
 LBN_SELCANCEL 选择被取消
 LBN_SELCHANGE 选择了另一项
 LBN_SETFOCUS 列表框获得输入焦点

用于判断当前窗口过程所处理的消息,是否来自其他线程的SendMessage调用。

PeekMessage
1.该函数核查线程消息队列中是否有消息,并将消息放在参数结构体中
2.如果hWnd参数=-1,则只返回hWnd=NULL的消息,这种消息来自PostThreadMessage
3.参数wRemoveMsg需要注意

应用程序自定义消息

shell_execute.exe的主要code:

队列消息和非队列消息
   从消息的发送途径来看,消息可以分成2种:队列消息和非队列消息。消息队列由可以分成系统消息队列和线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列。队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。
    
对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、
WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由
Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
    
一般来讲,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外,同一个窗口的多个
WM_PAINT被合并成一个 WM_PAINT 消息,
合并所有的无效区域到一个无效区域。合并WM_PAIN的目的是为了减少刷新窗口的次数。
2757com 11

4.如果应用程序正在创建顶层窗口时调用PeekMessage,将导致窗口窗口被创建在Z-Order的最后。你需要在PeekMessage后,显式调用SetForegroundWindow。如果应用程序以及有一个前置窗口了,那么新窗口将被前置。

PostMessage
应用程序要用HWND_BROADCAST进行程序间的交互,消息应该获取于RegisterWindowMessage()

0xC000 ~ 0xFFFF

bool shell_execute_file(wstring file_path)
{
    SHELLEXECUTEINFOW shell_exec_info = { 0 };
    shell_exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
    shell_exec_info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
    shell_exec_info.hwnd = NULL;
    shell_exec_info.lpVerb = NULL;
    shell_exec_info.lpFile = file_path.c_str();
    shell_exec_info.lpParameters = NULL;
    shell_exec_info.lpDirectory = NULL;
    shell_exec_info.nShow = SW_SHOW;
    shell_exec_info.hInstApp = NULL;
    bool ret = ShellExecuteExW(&shell_exec_info);
    printf("process handle is %p\n", shell_exec_info.hProcess);

    return ret;
}

2757com 12
   
非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。例如,当用户激活一个窗口系统发送WM_ACTIVATE,
WM_SETFOCUS, and
WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息,例如下面我们要谈到的函数。
     
消息的发送
    
了解了上面的这些基础理论之后,我们就可以进行一下简单的消息发送与接收。
     把一个消息发送到窗口有3种方式:发送、寄送和广播。
    
发送消息的函数有SendMessage、SendMessageCallback、SendNotifyMessage、
SendMessageTimeout;寄送消息的函数主要有PostMessage、PostThreadMessage、
PostQuitMessage;广播消息的函数我知道的只有BroadcastSystemMessage、
BroadcastSystemMessageEx。
     SendMessage的原型如下:LRESULT SendMessage(HWND hWnd,UINT
Msg,WPARAM wParam,LPARAM
lParam),这个函数主要是向一个或多个窗口发送一条消息,一直等到消息被处理之后才会返回。不过需要注意的是,如果接收消息的窗口是同一个应用程序的一部分,那么这个窗口的窗口函数就被作为一个子程序马上被调用;如果接收消息的窗口是被另外的线程所创建的,那么窗口系统就切换到相应的线程并且调用相应的窗口函数,这条消息不会被放进目标应用程序队列中。函数的返回值是由接收消息的窗口的窗口函数返回,返回的值取决于被发送的消息。
     PostMessage的原型如下:BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM
wParam,LPARAM
lParam),该函数把一条消息放置到创建hWnd窗口的线程的消息队列中,该函数不等消息被处理就马上将控制返回。需要注意的是,如果hWnd参数为
HWND_BROADCAST,那么,消息将被寄送给系统中的所有的重叠窗口和弹出窗口,但是子窗口不会收到该消息;如果hWnd参数为NULL,则该函数类似于将dwThreadID参数设置成当前线程的标志来调用PostThreadMEssage函数。
  从上面的这2个具有代表性的函数,我们可以看出消息的发送方式和寄送方式的区别所在:被发送的消息是否会被立即处理,函数是否立即返回。被发送的消息会被立即处理,处理完毕后函数才会返回;被寄送的消息不会被立即处理,他被放到一个先进先出的队列中,一直等到应用程序空线的时候才会被处理,不过函数放置消息后立即返回。
2757com 13
  实际上,发送消息到一个窗口处理过程和直接调用窗口处理过程之间并没有太大的区别,他们直接的唯一区别就在于你可以要求操作系统截获所有被发送的消息,但是不能够截获对窗口处理过程的直接调用。
  以寄送方式发送的消息通常是与用户输入事件相对应的,因为这些事件不是十分紧迫,可以进行缓慢的缓冲处理,例如鼠标、键盘消息会被寄送,而按钮等消息则会被发送。
  广播消息用得比较少,BroadcastSystemMessage函数原型如下:
      long BroadcastSystemMessage(DWORD dwFlags,LPDWORD
lpdwRecipients,UINT uiMessage,WPARAM wParam,LPARAM
lParam);该函数可以向指定的接收者发送一条消息,这些接收者可以是应用程序、可安装的驱动程序、网络驱动程序、系统级别的设备驱动消息和他们的任意组合。需要注意的是,如果dwFlags参数是BSF_QUERY并且至少一个接收者返回了BROADCAST_QUERY_DENY,则返回值为0,如果没有指定BSF_QUERY,则函数将消息发送给所有接收者,并且忽略其返回值。

如果发送消息低于WM_USER范围,到异步消息队列函数(PostMessage、SendNotifyMessage),消息参数不应该包含指针,不然的话,操作将失败。该函数将在接收线程有机会处理该消息前返回,发送者将释放刚刚用到的内存。

PostQuitMessage

应用程序字符串消息

 

消息的接收
 消息的接收主要有3个函数:GetMessage、PeekMessage、WaitMessage。
  GetMessage原型如下:BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT
wMsgFilterMin,UINT
wMsgFilterMax);该函数用来获取与hWnd参数所指定的窗口相关的且wMsgFilterMin和wMsgFilterMax参数所给出的消息值范围内的消息。需要注意的是,如果hWnd为NULL,则GetMessage获取属于调用该函数应用程序的任一窗口的消息,如果
wMsgFilterMin和wMsgFilterMax都是0,则GetMessage就返回所有可得到的消息。函数获取之后将删除消息队列中的除
WM_PAINT消息之外的其他消息,至于WM_PAINT则只有在其处理之后才被删除。
   PeekMessage原型如下:BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT
wMsgFilterMin,UINT wMsgFilterMax,UINT
wRemoveMsg);该函数用于查看应用程序的消息队列,如果其中有消息就将其放入lpMsg所指的结构中,不过,与GetMessage不同的是,PeekMessage函数不会等到有消息放入队列时才返回。同样,如果hWnd为NULL,则PeekMessage获取属于调用该函数应用程序的任一窗口的消息,如果hWnd=-1,那么函数只返回把hWnd参数为NULL的PostAppMessage函数送去的消息。如果
wMsgFilterMin和wMsgFilterMax都是0,则PeekMessage就返回所有可得到的消息。函数获取之后将视最后一个参数来决定是否删除消息队列中的除
WM_PAINT消息之外的其他消息,至于WM_PAINT则只有在其处理之后才被删除。
   WaitMessage原型如下:BOOL
WaitMessage();当一个应用程序无事可做时,该函数就将控制权交给另外的应用程序,同时将该应用程序挂起,直到一个新的消息被放入应用程序的队列之中才返回。

该函数只是简单表明被请求终止的线程将会终止。接收WM_QUIT的线程,应该终止消息循环,并将控制权交给系统。返回给系统的退出值,一定是WM_QUIT的wParam参数

BOOL PostThreadMessage(
DWORD idThread,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);

接收消息的线程,通过GetMessage/PeekMessage来获取消息,hWnd成员将会是空


RegisterWindowMessage
同一字符串,注册的值,在整个系统中是唯一的


SendMessage

> 0xFFFF

3. 原因分析

消息的处理
  接下来我们谈一下消息的处理,首先我们来看一下VC中的消息泵:

非消息队列方式,直接调用窗口过程,系统立即切换到接收线程执行,发送线程锁住,知道接收线程处理完毕

SendMessageTimeout

为以后系统应用保留

3.1 excel hang在哪里?

2757com 14while(GetMessage(&msg, NULL, 0, 0))
2757com 15{
2757com 16       if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))
2757com 17      { 
2757com 18            TranslateMessage(&msg);
2757com 19            DispatchMessage(&msg);
2757com 20       }
2757com 21}

该函数通过调用窗口过程的方式发送消息,如果窗口属于不同线程,SendMessageTimerout将知道消息处理完毕才返回或者指定的超时已经过去,如果窗口就在当前线程,则直接调用窗口过程,并忽略time-out超时

SendNotifyMessage
如果窗口创建于属于发送消息的线程,则调用窗口过程,并等待窗口过程处理完毕该消息。如果是不同线程,则将消息传递到窗口过程,并立即返回,不等待窗口过程的消息处理过程。


TranslateMessage
1.将虚拟键消息转换为字符消息,然后将字符消息发送到调用线程的消息队列中,该字符消息将在下次调用GetMessage或者PeekMessage消息的时候获取到。
2.WM_(SYS)KEYDOWN/UP—>WM_(SYS)_CHAR
3.如果应用程序为了其他目的,处理虚拟键消息,那么就不应该调用TranslateMessage.与一个实例,应用程序不应该在TranslateAccelerator函数返回非0值时调用TranslateMessage

关于消息和消息队列
不像基于MS-DOS的应用程序,基于Windows的程序是事件驱动的。他们不做任何显示调…

表A-2  常用Windows消息

3.1.1 用windbg附加到excel上,输入如下命令查看主线程hang住的地方

 

消息名称

2757com 22

  
首先,GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage函数将等待一个消息的到来以后才返回。如果你将一个窗口句柄作为第二个参数传入GetMessage,那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。这时候就要利用GetMessage/PeekMessage指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄,或者两者同时指定。当应用程序要查找一个后入消息队列的消息是很有用。WM_KEYFIRST
和 WM_KEYLAST 常量用于接受所有的键盘消息。 WM_MOUSEFIRST 和
WM_MOUSELAST 常量用于接受所有的鼠标消息。 
 然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息,如果是,则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后,函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个
WM_CHAR,不过需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。     
 处理完之后,DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT,则
GetMessage返回0,从而退出循环体。应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的
WM_DESTROY消息中调用。
 下面我们举一个常见的小例子来说明这个消息泵的运用:

可以看到Excel
hang在NtUserMessageCall()中,经过查询知,SendMessage()内部就是调用NtUserMessageCall()来发送消息的。

2757com 23if (::PeekMessage(&msg, m_hWnd, WM_KEYFIRST,WM_KEYLAST, PM_REMOVE))
2757com 24{
2757com 25          if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)...
2757com 26}
2757com 27

说  明

查看参数知excel调用NtUserMessageCall()类似如下:

  这里我们接受所有的键盘消息,所以就用WM_KEYFIRST 和
WM_KEYLAST作为参数。最后一个参数可以是PM_NOREMOVE 或者
PM_REMOVE,表示消息信息是否应该从消息队列中删除。                 
   所以这段小代码就是判断是否按下了Esc键,如果是就进行处理。

WM_NULL 

NtUserMessageCall(HWND_BROADCAST, WM_DDE_INITIATE)

窗口过程
 窗口过程是一个用于处理所有发送到这个窗口的消息的函数。任何一个窗口类都有一个窗口过程。同一个类的窗口使用同样的窗口过程来响应消息。系统发送消息给窗口过程将消息数据作为参数传递给他,消息到来之后,按照消息类型排序进行处理,其中的参数则用来区分不同的消息,窗口过程使用参数产生合适行为。
 一个窗口过程不经常忽略消息,如果他不处理,它会将消息传回到执行默认的处理。窗口过程通过调用DefWindowProc来做这个处理。窗口过程必须
return一个值作为它的消息处理结果。大多数窗口只处理小部分消息和将其他的通过DefWindowProc传递给系统做默认的处理。窗口过程被所有属于同一个类的窗口共享,能为不同的窗口处理消息。下面我们来看一下具体的实例:

0x0000

说明excel给所有顶层窗口发送一个WM_DDE_INITIATE消息,但是有窗口没有response

2757com 28LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
2757com 29{
2757com 30 int wmId, wmEvent;
2757com 31 PAINTSTRUCT ps;
2757com 32 HDC hdc;
2757com 33 TCHAR szHello[MAX_LOADSTRING];
2757com 34 LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
2757com 35
2757com 36 switch (message) 
2757com 37 {
2757com 38  case WM_COMMAND:
2757com 39         wmId    = LOWORD(wParam); 
2757com 40         wmEvent = HIWORD(wParam); 
2757com 41         // Parse the menu selections:
2757com 42         switch (wmId)
2757com 43         {
2757com 44          case IDM_ABOUT:
2757com 45             DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
2757com 46             break;
2757com 47          case IDM_EXIT:
2757com 48             DestroyWindow(hWnd);
2757com 49             break;
2757com 50          default:
2757com 51             return DefWindowProc(hWnd, message, wParam, lParam);
2757com 52         }
2757com 53   break;
2757com 54
2757com 55  case WM_PAINT:
2757com 56         hdc = BeginPaint(hWnd, &ps);
2757com 57         // TODO: Add any drawing code here2757com 58
2757com 59         RECT rt;
2757com 60         GetClientRect(hWnd, &rt);
2757com 61         DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
2757com 62         EndPaint(hWnd, &ps);
2757com 63         break;
2757com 64
2757com 65  case WM_DESTROY:
2757com 66         PostQuitMessage(0);
2757com 67         break;
2757com 68  default:
2757com 69         return DefWindowProc(hWnd, message, wParam, lParam);
2757com 70  }
2757com 71  return 0;
2757com 72}
2757com 73

空消息,此消息将被接收窗口忽略

由此可以怀疑是由于console进程在和excel用DDE消息通信时,console没有响应excel发送的DDE消息,导致excel
hang住

 

WM_CREATE 

 

消息分流器
  通常的窗口过程是通过一个switch语句来实现的,这个事情很烦,有没有更简便的方法呢?有,那就是消息分流器,利用消息分流器,我们可以把switch语句分成更小的函数,每一个消息都对应一个小函数,这样做的好处就是对消息更容易管理。
  之所以被称为消息分流器,就是因为它可以对任何消息进行分流。下面我们做一个函数就很清楚了:

0x0001

3.2 为了验证3.1.1的猜想,用API Monitor一下ShellExecuteEx

2757com 74void MsgCracker(HWND hWnd,int id,HWND hWndCtl,UINT codeNotify)
2757com 75{
2757com 76      switch(id)
2757com 77      {
2757com 78     case ID_A:
2757com 79                  if(codeNotify==EN_CHANGE)2757com 80
2757com 81                  break;
2757com 82     case ID_B:
2757com 83                  if(codeNotify==BN_CLICKED)2757com 84
2757com 85                  break;
2757com 86             2757com 87.
2757com 88       }
2757com 89}
2757com 90

应用程序创建一个窗口

3.2.1
根据微软的文档可知,发送DDE消息除了WM_DDE_INITIATE和WM_DDE_ACK之外用的都是PostMessage

然后我们修改一下窗口过程:

WM_DESTROY

2757com 91LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
2757com 92{
2757com 93       switch(message)
2757com 94      {
2757com 95             HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);
2757com 96             HANDLE_MSG(hWnd,WM_DESTROY,MsgCracker);
2757com 97           default:
2757com 98                    return DefWindowProc(hWnd, message, wParam, lParam);
2757com 99   }
2757com 100  return 0;
2757com 101}

0x0002

在API Monitor中搜索一下PostMessage的调用,果然搜到一条

在WindowsX.h中定义了如下的HANDLE_MSG宏:

一个窗口被销毁

2757com 102

2757com 103   #define HANDLE_MSG(hwnd,msg,fn) \
2757com 104             switch(msg): return HANDLE_##msg((hwnd),(wParam),(lParam),(fn));

WM_MOVE

call stack显示确实是ShellExecuteEx所调用

实际上,HANDLE_WM_XXXX都是宏,例如:HANDLE_MSG(hWnd,WM_COMMAND,MsgCracker);将被转换成如下定义:

0x0003

2757com 105

2757com 106   #define HANDLE_WM_COMMAND(hwnd,wParam,lParam,fn)\ 
2757com 107             ((fn)((hwnd),(int)(LOWORD(wParam)),(HWND)(lParam),(UINT)HIWORD(wParam)),0L);

移动一个窗口

消息1000为WM_DDE_EXECUTE,Post窗口句柄为0x00310172。

好了,事情到了这一步,应该一切都明朗了。
不过,我们发现在windowsx.h里面还有一个宏:FORWARD_WM_XXXX,我们还是那WM_COMMAND为例,进行分析:

WM_SIZE

注意到下一个API GetWindowThreadProcessId ( 0x00310172 , 0x0012fb70
),刚好是获取这个窗口的pid和tid,查看下参数窗口:

2757com 108   #define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \
2757com 109     (void)(fn)((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))

0x0005

这个窗口所属的进程PID =
0xc54,正好是excel的进程,说明ShellExecuteEx确实发送了DDE消息给excel,而且可执发送的消息的thread就是主线程

所以实际上,FORWARD_WM_XXXX将消息参数进行了重新构造,生成了wParam &&
lParam,然后调用了我们定义的函数。

改变一个窗口的大小

2757com 110

前面,我们分析了消息的基本理论和基本的函数及用法,接下来,我们将进一步讨论消息传递在MFC中的实现。

WM_ACTIVATE

根据DDE的消息参数,可知wParam就是发送消息的窗口,其句柄为2425190 =
0x250166,反向查询知这是ShellExecuteEx创建的”WorkerW”窗口

MFC消息的处理实现方式
  初看MFC中的各种消息,以及在头脑中根深蒂固的C++的影响,我们可能很自然的就会想到利用C++的三大特性之一:虚拟机制来实现消息的传递,但是经过分析,我们看到事情并不是想我们想象的那样,在MFC中消息是通过一种所谓的消息映射机制来处理的。
  为什么呢?在潘爱民老师翻译的《Visual
C++技术内幕》(第4版)中给出了详细的原因说明,我再简要的说一遍。在CWnd类中大约有110个消息,还有其它的MFC的类呢,算起来消息太多了,在C++中对程序中用到的每一个派生类都要有一个vtable,每一个虚函数在vtable中都要占用一个4字节大小的入口地址,这样一来,对于每个特定类型的窗口或控件,应用程序都需要一个440KB大小的表来支持虚拟消息控件函数。
  如果说上面的窗口或控件可以勉强实现的话,那么对于菜单命令消息及按钮命令消息呢?因为不同的应用程序有不同的菜单和按钮,我们怎么处理呢?在MFC
库的这种消息映射系统就避免了使用大的vtable,并且能够在处理常规Windows消息的同时处理各种各样的应用程序的命令消息。
  说白了,MFC中的消息机制其实质是一张巨大的消息及其处理函数的一一对应表,然后加上分析处理这张表的应用框架内部的一些程序代码.这样就可以避免在SDK编程中用到的繁琐的CASE语句。

0x0006

2757com 111

MFC的消息映射的基类CCmdTarget
  如果你想让你的控件能够进行消息映射,就必须从CCmdTarget类中派生。CCmdTarget类是MFC处理命令消息的基础、核心。MFC为该类设计了许多成员函数和一些成员数据,基本上是为了解决消息映射问题的,所有响应消息或事件的类都从它派生,例如:应用程序类、框架类、文档类、视图类和各种各样的控件类等等,还有很多。
不过这个类里面有2个函数对消息映射非常重要,一个是静态成员函数DispatchCmdMsg,另一个是虚函数OnCmdMsg。
DispatchCmdMsg专门供MFC内部使用,用来分发Windows消息。OnCmdMsg用来传递和发送消息、更新用户界面对象的状态。
CCmdTarget对OnCmdMsg的默认实现:在当前命令目标(this所指)的类和基类的消息映射数组里搜索指定命令消息的消息处理函数。
  这里使用虚拟函数GetMessageMap得到命令目标类的消息映射入口数组_messageEntries,然后在数组里匹配命令消息ID相同、控制通知代码也相同的消息映射条目。其中GetMessageMap是虚拟函数,所以可以确认当前命令目标的确切类。
如果找到了一个匹配的消息映射条目,则使用DispachCmdMsg调用这个处理函数;
如果没有找到,则使用_GetBaseMessageMap得到基类的消息映射数组,查找,直到找到或搜寻了所有的基类(到CCmdTarget)为止;
如果最后没有找到,则返回FASLE。
  每个从CCmdTarget派生的命令目标类都可以覆盖OnCmdMsg,利用它来确定是否可以处理某条命令,如果不能,就通过调用下一命令目标的
OnCmdMsg,把该命令送给下一个命令目标处理。通常,派生类覆盖OnCmdMsg时,要调用基类的被覆盖的OnCmdMsg。
  在MFC框架中,一些MFC命令目标类覆盖了OnCmdMsg,如框架窗口类覆盖了该函数,实现了MFC的标准命令消息发送路径。必要的话,应用程序也可以覆盖OnCmdMsg,改变一个或多个类中的发送规定,实现与标准框架发送规定不同的发送路径。例如,在以下情况可以作这样的处理:在要打断发送顺序的类中把命令传给一个非MFC默认对象;在新的非默认对象中或在可能要传出命令的命令目标中。

一个窗口被激活或失去激活状态

2757com 112

消息映射的内容
   
通过ClassWizard为我们生成的代码,我们可以看到,消息映射基本上分为2大部分:
   
在头文件(.h)中有一个宏DECLARE_MESSAGE_MAP(),他被放在了类的末尾,是一个public属性的;与之对应的是在实现部分(.cpp)增加了一章消息映射表,内容如下:
    BEGIN_MESSAGE_MAP(当前类, 当前类的基类)
       //{{AFX_MSG_MAP(CMainFrame)
         消息的入口项
       //}}AFX_MSG_MAP
   END_MESSAGE_MAP()
  
但是仅是这两项还远不足以完成一条消息,要是一个消息工作,必须有以下3个部分去协作:
1.在类的定义中加入相应的函数声明;
2.在类的消息映射表中加入相应的消息映射入口项;
3.在类的实现中加入相应的函数体;

WM_SETFOCUS

 

消息的添加
  
有了上面的这些只是作为基础,我们接下来就做我们最熟悉、最常用的工作:添加消息。MFC消息的添加主要有2种方法:自动/手动,我们就以这2种方法为例,说一下如何添加消息。
   1、利用Class Wizard实现自动添加
      在菜单中选择View–>Class
Wizard,也可以用单击鼠标右键,选择Class Wizard,同样可以激活Class
Wizard。选择Message Map标签,从Class
name组合框中选取我们想要添加消息的类。在Object
IDs列表框中,选取类的名称。此时,
Messages列表框显示该类的大多数(若不是全部的话)可重载成员函数和窗口消息。类重载显示在列表的上部,以实际虚构成员函数的大小写字母来表示。其他为窗口消息,以大写字母出现,描述了实际窗口所能响应的消息ID。选中我们向添加的消息,单击Add
Function按钮,Class Wizard自动将该消息添加进来。
     
有时候,我们想要添加的消息本应该出现在Message列表中,可是就是找不到,怎么办?不要着急,我们可以利用Class
Wizard上Class Info标签以扩展消息列表。在该页中,找到Message
Filter组合框,通过它可以改变首页中Messages列表框中的选项。这里,我们选择Window,从而显示所有的窗口消息,一把情况下,你想要添加的消息就可以在Message列表框中出现了,如果还没有,那就接着往下看:)

0x0007

3.2.2 为了验证3.2.1的结论,在PostMessageW上下断点跟踪一下

   2、手动地添加消息处理函数
   
如果在Messages列表框中仍然看不到我们想要的消息,那么该消息可能是被系统忽略掉或者是你自己创建的,在这种情况下,就必须自己手工添加。根据我们前面所说的消息工作的3个部件,我们一一进行处理:
      1) 在类的.
h文件中添加处理函数的声明,紧接在//}}AFX_MSG行之后加入声明,注意:一定要以afx_msg开头。
     通常,添加处理函数声明的最好的地方是源代码中Class
Wizard维护的表下面,但是在它标记其领域的{{}}括弧外面。这些括弧中的任何东西都将会被Class
Wizard销毁。
      2)
接着,在用户类的.cpp文件中找到//}}AFX_MSG_MAP行,紧接在它之后加入消息入口项。同样,也是放在{
{} }的外面
      3) 最后,在该文件中添加消息处理函数的实体。

获得焦点后

2757com 113

 

WM_KILLFOCUS

查看一下buff的地址:

 

0x0008

2757com 114

消息范围

说 明

0 ~ WM_USER – 1

系统消息

WM_USER ~ 0x7FFF

自定义窗口类整数消息

WM_APP ~ 0xBFFF

应用程序自定义消息

0xC000 ~ 0xFFFF

应用程序字符串消息

> 0xFFFF

为以后系统应用保留

失去焦点

刚好就是打开test.iqy的命令,说明ShellExecuteEx就是先创建了excel的进程,然后发送test.iqy的文件命令给excel打开。

表A-2  常用Windows消息

WM_ENABLE

 

消息名称

说  明

WM_NULL 

0x0000

空消息,此消息将被接收窗口忽略

WM_CREATE 

0x0001

应用程序创建一个窗口

WM_DESTROY

0x0002

一个窗口被销毁

WM_MOVE

0x0003

移动一个窗口

WM_SIZE

0x0005

改变一个窗口的大小

WM_ACTIVATE

0x0006

一个窗口被激活或失去激活状态

WM_SETFOCUS

0x0007

获得焦点后

WM_KILLFOCUS

0x0008

失去焦点

WM_ENABLE

0x000A

应用程序Enable状态改变时产生

WM_SETREDRAW

0x000B

设置窗口是否能重画

WM_SETTEXT

0x000C

应用程序发送此消息来设置一个窗口的文本

WM_GETTEXT

0x000D

应用程序发送此消息来复制对应窗口的文本到缓冲区

WM_GETTEXTLENGTH

0x000E

得到与一个窗口有关的文本的长度(不包含空字符)

WM_PAINT

0x000F

要求一个窗口重绘自己

WM_CLOSE

0x0010

当一个窗口或应用程序要关闭时发送一个信号

WM_QUERYENDSESSION

0x0011

用户选择结束对话框或应用程序自己调用ExitWindows()函数

WM_QUIT

0x0012

用来结束程序运行或应用程序调用Postquitmessage()函数来产生此消息

WM_QUERYOPEN

0x0013

当用户窗口恢复以前的大小位置时,把此消息发送给某个图标

WM_ERASEBKGND

0x0014

当窗口背景必须被擦除时(例如在窗口改变大小时)

WM_SYSCOLORCHANGE

0x0015

当系统颜色改变时,发送此消息给所有顶级窗口

WM_ENDSESSION

0x0016

当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序

WM_SHOWWINDOW

0x0018

当隐藏或显示窗口是发送此消息给这个窗口

WM_ACTIVATEAPP

0x001C

当某个窗口将被激活时,将被激活窗口和当前活动(即将失去激活)窗口会收到此消息,发此消息给应用程序哪个窗口是激活的,哪个是非激活的

WM_FONTCHANGE

0x001D

当系统的字体资源库变化时发送此消息给所有顶级窗口

WM_TIMECHANGE

0x001E

当系统的时间变化时发送此消息给所有顶级窗口

WM_CANCELMODE

0x001F

发送此消息来取消某种正在进行的操作

WM_SETCURSOR

0x0020

如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,发消息给该窗口

WM_MOUSEACTIVATE

0x0021

当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给当前窗口

WM_CHILDACTIVATE

0x0022

发送此消息给MDI子窗口当用户点击此窗口的标题栏,或当窗口被激活、移动、改变大小

WM_QUEUESYNC

0x0023

此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的Hook程序分离出用户输入消息

WM_GETMINMAXINFO

0x0024

当窗口要将要改变大小或位置时,发送此消息给该窗口

WM_PAINTICON

0x0026

当窗口图标将要被重绘时,发送此消息给该窗口

WM_ICONERASEBKGND

0x0027

在一个最小化窗口的图标在重绘前,当图标背景必须被重绘时,发送此消息给该窗口

WM_NEXTDLGCTL

0x0028

发送此消息给一个对话框程序以更改焦点位置

WM_SPOOLERSTATUS

0x002A

当打印管理列队增加或减少一条作业时发出此消息

WM_DRAWITEM

0x002B

当Button,ComboBox,Listbox,Menu控件的外观改变时,发送此消息给这些控件的所有者

WM_MEASUREITEM

0x002C

当Button,ComboBox,list box,ListView,Menu 项被创建时,发送此消息给控件的所有者

WM_DELETEITEM

0x002D

当ListBox 或 ComboBox 被销毁或当某些项通过发送LB_DELETESTRING、LB_RESETCONTENT、 CB_DELETESTRING、CB_RESETCONTENT 消息被删除时,发送此消息给控件的所有者

WM_VKEYTOITEM

0x002E

一个具有LBS_WANTKEYBOARDINPUT风格的ListBox控件发送此消息给它的所有者,以此来响应WM_KEYDOWN消息

WM_CHARTOITEM

0x002F

一个具有LBS_WANTKEYBOARDINPUT风格的ListBox控件发送此消息给它的所有者,以此来响应WM_CHAR消息

WM_SETFONT

0x0030

应用程序绘制控件时,发送此消息得到以何种字体绘制控件中的文本

WM_GETFONT

0x0031

应用程序发送此消息得到当前控件绘制文本的字体

WM_SETHOTKEY

0x0032

应用程序发送此消息让一个窗口与一个热键相关联

WM_GETHOTKEY

0x0033

应用程序发送此消息来判断热键与某个窗口是否有关联

WM_QUERYDRAGICON

0x0037

此消息发送给最小化窗口,当此窗口将要被拖放而它的类中没有定义图标,应用程序就返回一个图标或光标的句柄,当用户拖放图标时系统显示这个图标或光标

WM_COMPAREITEM

0x0039

发送此消息来判定ComboBox或ListBox新增加的项的相对位置

WM_COMPACTING

0x0041

显示内存已经很少了

WM_WINDOWPOSCHANGING

0x0046

当调用SetWindowPos()函数改变窗口的大小和位置后,发送此消息给该窗口

WM_POWER

0x0048

当系统将进入挂起状态时发送此消息给所有进程

WM_COPYDATA

0x004A

当一个应用程序传递数据给另一个应用程序时发送此消息

WM_CANCELJOURNAL

0x004B

当某个用户取消程序日志激活状态,发送此消息给应用程序

WM_NOTIFY

0x004E

当某个控件的某个事件已经发生或这个控件需要得到一些信息时,发送此消息给它的父窗口

WM_INPUTLANGCHANGEREQUEST

0x0050

当用户选择某种输入语言,或输入语言的热键改变

WM_INPUTLANGCHANGE

0x0051

当应用程序输入语言改变后发送此消息给受影响的最顶级窗口

WM_TCARD

0x0052

当应用程序已经初始化Windows帮助例程时发送此消息给应用程序

WM_HELP

0x0053

当用户按下了F1,如果某个菜单是激活的,就发送此消息给此窗口关联的菜单,否则就发送给有焦点的窗口,如果当前都没有焦点,就把此消息发送给当前激活的窗口

WM_USERCHANGED

0x0054

当用户已经登录或退出后发送此消息给所有的窗口,当用户登录或退出时系统更新用户的具体设置信息,在用户更新设置时系统马上发送此消息

WM_NOTIFYFORMAT

0x0055

公用控件和它们的父窗口通过此消息来判断在WM_NOTIFY消息中是使用ANSI还是UNICODE形式的结构,使用此控件能使某个控件与它的父控件进行相互通信

WM_CONTEXTMENU

0x007B

当用户在某个窗口中点击右键,则发送此消息给该窗口

WM_STYLECHANGING

0x007C

当将要调用SetWindowLong()函数窗口的一个或多个风格时,发送此消息给该窗口

WM_STYLECHANGED

0x007D

当调用SetWindowLong()函数改变了窗口的一个或多个风格后,发送此消息给该窗口

WM_DISPLAYCHANGE

0x007E

当显示器的分辨率改变后发送此消息给所有的窗口

WM_GETICON

0x007F

发送此消息给某个窗口,返回与某个窗口有关联的大图标或小图标的句柄

WM_SETICON

0x0080

应用程序发送此消息让一个新的大图标或小图标与某个窗口关联

WM_NCCREATE

0x0081

当某个窗口第一次被创建时,此消息在WM_CREATE消息被发送前发送

WM_NCDESTROY

0x0082

此消息通知某个窗口,正在销毁非客户区

WM_NCCALCSIZE

0x0083

当计算某个窗口的客户区大小和位置时发送此消息

WM_NCHITTEST

0x0084

移动鼠标,按住或释放鼠标时产生此消息

WM_NCPAINT

0x0085

当某个窗口的框架必须被绘制时,应用程序发送此消息给该窗口

WM_NCACTIVATE

0x0086

通过改变某个窗口的非客户区来表示窗口是处于激活还是非激活状态时,此消息被发送给该窗口

WM_NCMOUSEMOVE

0x00A0

当光标在窗口的非客户区(窗口标题栏及边框)内移动时发送此消息给该窗口

WM_NCLBUTTONDOWN

0x00A1

当光标在窗口的非客户区并按下鼠标左键时发送此消息

WM_NCLBUTTONUP

0x00A2

当光标在窗口的非客户区并释放鼠标左键时发送此消息

WM_NCLBUTTONDBLCLK

0x00A3

当光标在窗口的非客户区并双击鼠标左键时发送此消息

WM_NCRBUTTONDOWN

0x00A4

当光标在窗口的非客户区并按下鼠标右键时发送此消息

WM_NCRBUTTONUP

0x00A5

当光标在窗口的非客户区并释放鼠标右键时发送此消息

WM_NCRBUTTONDBLCLK

0x00A6

当光标在窗口的非客户区并双击鼠标右键时发送此消息

WM_NCMBUTTONDOWN

0x00A7

当光标在窗口的非客户区并按下鼠标中键时发送此消息

WM_NCMBUTTONUP

0x00A8

当光标在窗口的非客户区并释放鼠标中键时发送此消息

WM_NCMBUTTONDBLCL

0x00A9

当光标在窗口的非客户区并双击鼠标中键时发送此消息

WM_KEYDOWN

0x0100

按下一个非系统键(按下键时未按下“ALT”键)

WM_KEYUP

0x0101

释放一个非系统键

WM_CHAR

0x0102

按下某键,当TranslateMessage()转发WM_KEYDOWN后发送本消息

WM_DEADCHAR

0x0103

释放某键,当TranslateMessage()转发WM_KEYUP后发送本消息

WM_SYSKEYDOWN

0x0104

当按住ALT键同时按下其他键时发送此消息给拥有键盘焦点的窗口

WM_SYSKEYUP

0x0105

当释放一个键同时按住ALT键时发送此消息给拥有键盘焦点的窗口

WM_SYSCHAR

0x0106

当TranslateMessage()转发WM_SYSKEYDOWN后发送此消息给拥有键盘焦点的窗口

WM_SYSDEADCHAR

0x0107

当TranslateMessage()转发WM_SYSKEYUP后发送此消息给拥有键盘焦点的窗口

WM_INITDIALOG

0x0110

在被显示前发送此消息对话框,通常用此消息初始化控件和执行其他任务

WM_COMMAND

0x0111

选择窗口菜单项或某个控件发送一条消息给它的父窗口或按下一个快捷键时产生此消息

WM_SYSCOMMAND

0x0112

选择窗口菜单项或选择最大化或最小化时,发送此消息给该窗口

WM_TIMER

0x0113

发生了定时器事件

WM_HSCROLL

0x0114

当窗口水平滚动条产生一个滚动事件时发送此消息给该窗口和滚动条的所有者

WM_VSCROLL

0x0115

当窗口垂直滚动条产生一个滚动事件时发送此消息给该窗口和滚动条的所有者

WM_INITMENU

0x0116

当一个菜单将要被激活时发送此消息,它发生在按下菜单项或按下菜单快捷键时,它允许程序在显示前更改菜单

WM_INITMENUPOPUP

0x0117

当一个下拉菜单或子菜单将要被激活时发送此消息,它允许显示前在修改菜单而不必更改整个菜单

WM_MENUSELECT

0x011F

选择一条菜单项时发送此消息给菜单的所有者(一般是窗口)

WM_MENUCHAR

0x0120

当菜单已被激活且用户按下了某个键(非快捷键),发送此消息给菜单的所有者

WM_ENTERIDLE

0x0121

当一个有模式对话框或菜单进入空闲状态时发送此消息给它的所有者,空闲状态指在处理完一条或几条先前的消息后,消息列队为空

WM_MENURBUTTONUP

0x0122

当光标位于菜单项上时,释放鼠标右键产生此消息

WM_MENUDRAG

0x0123

当拖动菜单项时,发送此消息给拖放菜单的所有者

WM_MENUGETOBJECT

0x0124

当光标移入菜单项或者从菜单项中心移到菜单项顶部或底部时,发送此消息给拖放菜单的所有者

WM_UNINITMENUPOPUP

0x0125

当下拉菜单或者子菜单被销毁时产生此消息

WM_MENUCOMMAND

0x0126

当用户选择菜单项时产生此消息

WM_CHANGEUISTATE

0x0127

应用程序发送此消息表明用户界面(UI)状态应当被改变

WM_UPDATEUISTATE

0x0128

应用程序发送此消息改变指定窗口及其子窗口的用户界面(UI)状态

WM_QUERYUISTATE

0x0129

应用程序发送此消息得到某个窗口的用户界面(UI)状态

WM_CTLCOLORMSGBOX

0x0132

绘制消息框前发送此消息给它的父窗口,通过响应这条消息,父窗口可以通过使用给定的相关显示设备的句柄来设置消息框的文本和背景颜色

WM_CTLCOLOREDIT

0x0133

绘制编辑型控件前发送此消息给它的父窗口,可用来设置编辑框的文本和背景颜色

WM_CTLCOLORLISTBOX

0x0134

绘制列表框控件前发送此消息给它的父窗口,可用来设置编辑框的文本和背景颜色

WM_CTLCOLORBTN

0x0135

绘制按钮控件前发送此消息给它的父窗口,可用来设置编辑框的文本和背景颜色

WM_CTLCOLORDLG

0x0136

绘制对话框前发送此消息给它的父窗口,可用来设置编辑框的文本和背景颜色

WM_CTLCOLORSCROLLBAR

0x0137

绘制滚动条控件前发送此消息给它的父窗口,可用来设置滚动条控件的文本和背景颜色

WM_CTLCOLORSTATIC

0x0138

绘制静态控件前发送此消息给它的父窗口,可用来设置静态控件的文本和背景颜色

WM_MOUSEMOVE

0x0200

鼠标移动

WM_LBUTTONDOWN

0x0201

按下鼠标左键

WM_LBUTTONUP

0x0202

释放鼠标左键

WM_LBUTTONDBLCLK

0x0203

双击鼠标左键

WM_RBUTTONDOWN

0x0204

按下鼠标右键

WM_RBUTTONUP

0x0205

释放鼠标右键

WM_RBUTTONDBLCLK

0x0206

双击鼠标右键

WM_MBUTTONDOWN

0x0207

按下鼠标中键

WM_MBUTTONUP

0x0208

释放鼠标中键

WM_MBUTTONDBLCLK

0x0209

双击鼠标中键

WM_MOUSEWHEEL

0x020A

当鼠标滚轮转动时发送此消息给当前获得焦点的窗口

WM_PARENTNOTIFY

0x0210

当MDI子窗口被创建或被销毁,或当光标位于子窗口上且用户按了一下鼠标键时,发送此消息给它的父窗口

WM_ENTERMENULOOP

0x0211

发送此消息通知应用程序的主窗口进程已经进入了菜单模式循环

WM_EXITMENULOOP

0x0212

发送此消息通知应用程序的主窗口进程已经退出了菜单模式循环

WM_SIZING

0x0214

调整窗口大小时发送此消息给窗口,通过此消息应用程序可以监视或修改窗口大小和位置

WM_CAPTURECHANGED

0x0215

当窗口设定为不捕获鼠标事件时,发送此消息给该窗口

WM_MOVING

0x0216

移动窗口时发送此消息给窗口,通过此消息应用程序可以监视或修改窗口大小和位置

WM_POWERBROADCAST

0x0218

发送此消息给应用程序通知它有关电源管理事件

WM_DEVICECHANGE

0x0219

当设备的硬件配置改变时发送此消息给应用程序或设备驱动程序

WM_MDICREATE

0x0220

应用程序发送此消息给多文档的客户窗口来创建一个MDI 子窗口

WM_MDIDESTROY

0x0221

应用程序发送此消息给多文档的客户窗口来关闭一个MDI 子窗口

WM_MDIACTIVATE

0x0222

应用程序发送此消息给多文档的客户窗口通知客户窗口激活另一个MDI子窗口,当客户窗口收到此消息后,它发出WM_MDIACTIVE消息给MDI子窗口(未激活)来激活它

WM_MDIRESTORE

0x0223

应用程序发送此消息给MDI客户窗口通知子窗口恢复到原来大小

WM_MDINEXT

0x0224

应用程序发送此消息给MDI客户窗口激活下一个或前一个窗口

WM_MDIMAXIMIZE

0x0225

应用程序发送此消息给MDI客户窗口以最大化一个MDI子窗口

WM_MDITILE

0x0226

应用程序发送此消息给MDI客户窗口以平铺方式重新排列所有MDI子窗口

WM_MDICASCADE

0x0227

应用程序发送此消息给MDI客户窗口以层叠方式重新排列所有MDI子窗口

WM_MDIICONARRANGE

0x0228

应用程序发送此消息给MDI客户窗口重新排列所有最小化的MDI子窗口

WM_MDIGETACTIVE

0x0229

应用程序发送此消息给MDI客户窗口以找到激活的子窗口的句柄

WM_MDISETMENU

0x0230

应用程序发送此消息给MDI客户窗口用MDI菜单代替子窗口的菜单

WM_ENTERSIZEMOVE

0x0231

当窗口进入移动或改变大小模式循环时,发送此消息给该窗口

WM_EXITSIZEMOVE

0x0232

当窗口退出移动或改变大小模式循环时,发送此消息给该窗口

WM_DROPFILES

0x0233

当用户在应用程序窗口中拖动某个文件时,产生此消息

WM_MDIREFRESHMENU

0x0234

应用程序发送此消息给MDI客户窗口以刷新窗口菜单

WM_MOUSEHOVER

0x02A1

当光标在窗口客户区悬停超过TrackMouseEvent()指定的时间时,发送此消息给该窗口

WM_MOUSELEAVE

0x02A3

当光标离开窗口客户区超过TrackMouseEvent()指定的时间时,发送此消息给该窗口

WM_CUT

0x0300

应用程序发送此消息给一个编辑框或ComboBox以删除当前选择的文本

WM_COPY

0x0301

应用程序发送此消息给一个编辑框或ComboBox以复制当前选择的文本到剪贴板

WM_PASTE 

0x0302

应用程序发送此消息给一个编辑框或ComboBox以从剪贴板中得到数据

WM_CLEAR

0x0303

应用程序发送此消息给一个编辑框或ComboBox以清除当前选择的内容

WM_UNDO

0x0304

应用程序发送此消息给一个编辑框或ComboBox以撤消最后一次操作

WM_DESTROYCLIPBOARD

0x0307

当调用EmptyClipboard()清空剪贴板时,发送此消息给剪贴板所有者

WM_DRAWCLIPBOARD

0x0308

当剪贴板的内容变化时发送此消息给剪贴板观察链中的第一个窗口,它允许用剪贴板观察窗口来显示剪贴板的新内容

WM_PAINTCLIPBOARD

0x0309

当剪贴板包含CF_OWNERDIPLAY格式的数据且剪贴板观察窗口的客户区需要重绘时,发送此消息给剪贴板所有者

WM_VSCROLLCLIPBOARD

0x030A

当剪贴板包含CF_OWNERDIPLAY格式的数据且剪贴板观察窗口发生垂直滚动条事件时,剪贴板观察窗口发送此消息给剪贴板所有者

WM_SIZECLIPBOARD

0x030B

当剪贴板包含CF_OWNERDIPLAY格式的数据且剪贴板观察窗口的客户区域的大小已经改变时,剪贴板观察窗口发送此消息给剪贴板的所有者

WM_ASKCBFORMATNAME

0x030C

剪贴板观察窗口发送此消息给剪贴板所有者以获得CF_OWNERDISPLAY剪贴板格式的名字

WM_CHANGECBCHAIN

0x030D

当一个窗口从剪贴板观察链中移去时发送此消息给剪贴板观察链中的第一个窗口

WM_HSCROLLCLIPBOARD

0x030E

当剪贴板包含CF_OWNERDIPLAY格式的数据且剪贴板观察窗口发生水平滚动条事件时,剪贴板观察窗口发送此消息给剪贴板所有者

WM_QUERYNEWPALETTE

0x030F

发送此消息给将要获得键盘焦点的窗口,此消息使窗口在获得焦点时同时有机会实现它的逻辑调色板

WM_PALETTEISCHANGING

0x0310

应用程序将要实现它的逻辑调色板时发送此消息通知所有应用程序

WM_PALETTECHANGED

0x0311

获得焦点的窗口实现它的逻辑调色板后发送此消息给所有顶级并重叠的窗口,以此

来改变系统调色板

WM_HOTKEY

0x0312

当用户按下由RegisterHotKey()注册的热键时产生此消息

WM_PRINT

0x0317

应用程序发送此消息给窗口,要求窗口在指定设备环境中绘制自己,一般情况下是打印机设备环境

WM_PRINTCLIENT

0x0318

应用程序发送此消息给窗口,要求窗口在指定设备环境中绘制窗口客户区,一般情况下是打印机设备环境

WM_APP

0x8000

帮助用户自定义消息,自定义消息可以为WM_APP+X,X为正整数

WM_USER

0x0400

帮助用户自定义消息,自定义消息可以为WM_USER+X,X为正整数

0x000A

3.3 总结

  表A-3  通知消息-按钮

应用程序Enable状态改变时产生

1> ShellExecuteEx打开test.iqy的时先创建excel进程

消息名称

说 明

BN_CLICKED

单击按钮

BN_DISABLE

按钮被禁止

BN_DOUBLECLICKED

双击按钮

BN_HILITE

加亮按钮

BN_PAINT

按钮应当重画

BN_UNHILITE

加亮应当去掉

WM_SETREDRAW

2> 然后创建一个”WorkerW”的窗口用于DDE通信

表A-4  通知消息-组合框

0x000B

3> Post WM_DDE_EXECUTE给excel,告知打开test.iqy的命令

消息名称

说 明

CBN_CLOSEUP

组合框的列表框被关闭

CBN_DBLCLK

用户双击了一个字符串

CBN_DROPDOWN

组合框的列表框被拉下

CBN_EDITCHANGE

用户修改了组合框中的文本

CBN_EDITUPDATE

组合框内的文本即将更新

CBN_ERRSPACE

组合框内存不足

CBN_KILLFOCUS

组合框失去输入焦点

CBN_SELCHANGE

在组合框中选择了一项

CBN_SELENDCANCEL

用户的选择将被忽略

CBN_SELENDOK

用户的选择将被执行

CBN_SETFOCUS

组合框获得输入焦点

设置窗口是否能重画

4> ShellExecuteEx执行结束,但并不destroy “WorkerW”窗口

表A-5  通知消息-编辑框

WM_SETTEXT

5>
excel收到WM_DDE_EXECUTE消息后会广播WM_DDE_INITIATE消息,”WorkerW”窗口所在的console进程由于没有定义消息处理函数,ShellExecuteEx定义的”WorkerW”窗口消息处理函数得不到CPU执行机会,导致不会response该消息,从而导致excel
hang住

消息名称

说 明

EN_CHANGE

编辑框中的文本己更新

EN_ERRSPACE

编辑框内存不足

EN_HSCROLL

用户点击了水平滚动条

EN_KILLFOCUS

编辑框失去输入焦点

EN_MAXTEXT

插入的内容被截断

EN_SETFOCUS

编辑框获得输入焦点

EN_UPDATE

编辑框中的文本将要更新

EN_VSCROLL

用户点击了垂直滚动条

0x000C

类似,我们可以创建一个带窗口的程序,启动后将其挂起,这时,即使直接双击打开test.iqy也会hang住。

表A-6  通知消息-列表框

应用程序发送此消息来设置一个窗口的文本

 

消息名称

说 明

LBN_DBLCLK

用户双击了一项

LBN_ERRSPACE

列表框内存不足

LBN_KILLFOCUS

列表框正在失去输入焦点

LBN_SELCANCEL

用户选择被取消

LBN_SELCHANGE

用户选择将改变

LBN_SETFOCUS

列表框获得输入焦点

WM_GETTEXT

4. 为什么双击打开excel不会hang住

Windows消息大全

0x000D

因为双击打开实际是用explorer.exe打开,而explorer.exe是有窗口的,能够正常的接收处理WM_DDE_INITIATE消息

  Windows是一消息(Message)驱动式系统,Windows消息提供了应用程序与应用程序之间、应用程序与Windows系统之间进行通讯的手段。应用程序要实现的功能由消息来触发,并靠对消息的响应和处理来完成。Windows系统中有两种消息队列,一种是系统消息队列,另一种是应用程序消息队列。计算机的所有输入设备由
Windows监控,当一个事件发生时,Windows先将输入的消息放入系统消息队列中,然后再将输入的消息拷贝到相应的应用程序队列中,应用程序中的消息循环从它的消息队列中检索每一个消息并发送给相应的窗口函数中。一个事件的发生,到达处理它的窗口函数必须经历上述过程。值得注意的是消息的非抢先性,即不论事件的急与缓,总是按到达的先后排队(一些系统消息除外),这就使得一些外部实时事件可能得不到及时的处理。

应用程序发送此消息来复制对应窗口的文本到缓冲区

 

  由于Windows本身是由消息驱动的,举一个例子来说明这个问题。打开记事本程序,该程序有一个File菜单,那么,在运行该应用程序的时候,如果用户单击了File菜单里New命令时,这个动作将被Windows
(而不是应用程序本身!)所捕获,Windows经过分析得知这个动作应该由上面所说的那个应用程序去处理,既然是这样,Windows就发送了个叫做WM_COMMAND的消息给应用程序,该消息所包含信息告诉应用程序:”用户单击了New菜单”,应用程序得知这一消息之后,采取相应的动作来响应它,这个过程称为消息处理。Windows为每一个应用程序(确切地说是每一个线程)维护了相应的消息队列,应用程序的任务就是不停的从它的消息队列中获取消息,分析消息和处理消息,直到一条接到叫做WM_QUIT消息为止,这个过程通常是由一种叫做消息循环的程序结构来实现的。

WM_GETTEXTLENGTH

5. 为什么win7上不会有这样的问题

  消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做T
M s g,它在Wi n d o w s单元中是这样声明的:

0x000E

5.1 在API Monitor中看下PostMessageW

type

得到与一个窗口有关的文本的长度(不包含空字符)

2757com 115

TMsg = packedrecord

WM_PAINT

注意到win7下PostMessageW是用的线程2调用的,搜一下线程创建API
CreateThread

hwnd: HWND / /窗口句柄

0x000F

2757com 116

message: UINT / /消息常量标识符

要求一个窗口重绘自己

可知是ShellExecuteEx内部创建的线程,所以win7上ShellExecuteEx创建了一个线程专门用来处理和excel的DDE消息通信,这样就能正常的接收处理excel发过来的WM_DDE_INITIATE消息了

wParam: WPA R AM  // 32位消息的特定附加信息

WM_CLOSE

lParam: LPA R AM  // 32位消息的特定附加信息

0x0010

time: DWORD / /消息创建时的时间

当一个窗口或应用程序要关闭时发送一个信号

pt: TPoint / /消息创建时的鼠标位置

WM_QUERYENDSESSION

end

0x0011

 

用户选择结束对话框或应用程序自己调用ExitWindows()函数

消息中有什么?

WM_QUIT

是否觉得一个消息记录中的信息像希腊语一样?如果是这样,那么看一看下面的解释:

0x0012

hwnd
32位的窗口句柄。窗口可以是任何类型的屏幕对象,因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。

用来结束程序运行或应用程序调用Postquitmessage()函数来产生此消息

message
用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。

WM_QUERYOPEN

wParam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。

0x0013

lParam
通常是一个指向内存中数据的指针。由于WParm、lParam和Pointer都是32位的,因此,它们之间可以相互转换。

当用户窗口恢复以前的大小位置时,把此消息发送给某个图标

 

WM_ERASEBKGND

WM_NULL = 0

0x0014

WM_CREATE = 1

当窗口背景必须被擦除时(例如在窗口改变大小时)

应用程序创建一个窗口

WM_SYSCOLORCHANGE

WM_DESTROY = 2

0x0015

一个窗口被销毁

当系统颜色改变时,发送此消息给所有顶级窗口

WM_MOVE = 3

WM_ENDSESSION

移动一个窗口

0x0016

WM_SIZE = 5

当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序

改变一个窗口的大小

WM_SHOWWINDOW

WM_ACTIVATE = 6

0x0018

一个窗口被激活或失去激活状态;

当隐藏或显示窗口是发送此消息给这个窗口

WM_SETFOCUS = 7

WM_ACTIVATEAPP

获得焦点后

0x001C

WM_KILLFOCUS = 8

当某个窗口将被激活时,将被激活窗口和当前活动(即将失去激活)窗口会收到此消息,发此消息给应用程序哪个窗口是激活的,哪个是非激活的

失去焦点

WM_FONTCHANGE

WM_ENABLE = 10

0x001D

改变enable状态

当系统的字体资源库变化时发送此消息给所有顶级窗口

WM_SETREDRAW = 11

WM_TIMECHANGE

设置窗口是否能重画

0x001E

WM_SETTEXT = 12

当系统的时间变化时发送此消息给所有顶级窗口

应用程序发送此消息来设置一个窗口的文本

WM_CANCELMODE

WM_GETTEXT = 13

0x001F

应用程序发送此消息来复制对应窗口的文本到缓冲区

发送此消息来取消某种正在进行的操作

WM_GETTEXTLENGTH =14

WM_SETCURSOR

得到与一个窗口有关的文本的长度(不包含空字符)

0x0020

WM_PAINT = 15

如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,发消息给该窗口

要求一个窗口重画自己

WM_MOUSEACTIVATE

WM_CLOSE = 16

0x0021

当一个窗口或应用程序要关闭时发送一个信号

当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给当前窗口

WM_QUERYENDSESSION= 17

WM_CHILDACTIVATE

当用户选择结束对话框或程序自己调用ExitWindows函数

0x0022

WM_QUIT = 18

发送此消息给MDI子窗口当用户点击此窗口的标题栏,或当窗口被激活、移动、改变大小

用来结束程序运行或当程序调用postquitmessage函数

WM_QUEUESYNC

WM_QUERYOPEN = 19

0x0023

当用户窗口恢复以前的大小位置时,把此消息发送给某个图标

此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的Hook程序分离出用户输入消息

WM_ERASEBKGND = 20

WM_GETMINMAXINFO

当窗口背景必须被擦除时(例在窗口改变大小时)

0x0024

WM_SYSCOLORCHANGE =21

当窗口要将要改变大小或位置时,发送此消息给该窗口

当系统颜色改变时,发送此消息给所有顶级窗口

WM_PAINTICON

WM_ENDSESSION = 22

0x0026

当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序,

当窗口图标将要被重绘时,发送此消息给该窗口

通知它对话是否结束

WM_ICONERASEBKGND

WM_SYSTEMERROR = 23

0x0027

WM_SHOWWINDOW = 24

在一个最小化窗口的图标在重绘前,当图标背景必须被重绘时,发送此消息给该窗口

当隐藏或显示窗口是发送此消息给这个窗口

WM_NEXTDLGCTL

WM_ACTIVATEAPP = 28

0x0028

发此消息给应用程序哪个窗口是激活的,哪个是非激活的;

发送此消息给一个对话框程序以更改焦点位置

WM_FONTCHANGE = 29

WM_SPOOLERSTATUS

当系统的字体资源库变化时发送此消息给所有顶级窗口

0x002A

WM_TIMECHANGE = 30

当打印管理列队增加或减少一条作业时发出此消息

当系统的时间变化时发送此消息给所有顶级窗口

WM_DRAWITEM

WM_CANCELMODE = 31

0x002B

发送此消息来取消某种正在进行的摸态(操作)

当Button,ComboBox,Listbox,Menu控件的外观改变时,发送此消息给这些控件的所有者

WM_SETCURSOR = 32

WM_MEASUREITEM

如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,就发消息给某个窗口

0x002C

WM_MOUSEACTIVATE =33

当Button,ComboBox,list box,ListView,Menu
项被创建时,发送此消息给控件的所有者

当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给当前窗口

WM_DELETEITEM

WM_CHILDACTIVATE =34

0x002D

发送此消息给MDI子窗口当用户点击此窗口的标题栏,或当窗口被激活,移动,改变大小

当ListBox 或 ComboBox
被销毁或当某些项通过发送LB_DELETESTRING、LB_RESETCONTENT、
CB_DELETESTRING、CB_RESETCONTENT
消息被删除时,发送此消息给控件的所有者

WM_QUEUESYNC = 35

WM_VKEYTOITEM

此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的hook程序

0x002E

分离出用户输入消息

一个具有LBS_WANTKEYBOARDINPUT风格的ListBox控件发送此消息给它的所有者,以此来响应WM_KEYDOWN消息

WM_GETMINMAXINFO =36

WM_CHARTOITEM

此消息发送给窗口当它将要改变大小或位置;

0x002F

WM_PAINTICON = 38

一个具有LBS_WANTKEYBOARDINPUT风格的ListBox控件发送此消息给它的所有者,以此来响应WM_CHAR消息

发送给最小化窗口当它图标将要被重画

WM_SETFONT

WM_ICONERASEBKGND =39

0x0030

此消息发送给某个最小化窗口,仅当它在画图标前它的背景必须被重画

应用程序绘制控件时,发送此消息得到以何种字体绘制控件中的文本

WM_NEXTDLGCTL = 40

WM_GETFONT

发送此消息给一个对话框程序去更改焦点位置

0x0031

WM_SPOOLERSTATUS =42

应用程序发送此消息得到当前控件绘制文本的字体

每当打印管理列队增加或减少一条作业时发出此消息

WM_SETHOTKEY

WM_DRAWITEM = 43

0x0032

当button,combobox,listbox,menu的可视外观改变时发送

应用程序发送此消息让一个窗口与一个热键相关联

此消息给这些空件的所有者

WM_GETHOTKEY

WM_MEASUREITEM = 44

0x0033

当button, combo box, list box, list view control, or menu item 被创建时

应用程序发送此消息来判断热键与某个窗口是否有关联

发送此消息给控件的所有者

WM_QUERYDRAGICON

WM_DELETEITEM = 45

0x0037

当the list box 或 combo box 被销毁 或 当
某些项被删除通过LB_DELETESTRING,LB_RESETCONTENT, CB_DELETESTRING, or
CB_RESETCONTENT 消息

此消息发送给最小化窗口,当此窗口将要被拖放而它的类中没有定义图标,应用程序就返回一个图标或光标的句柄,当用户拖放图标时系统显示这个图标或光标

WM_VKEYTOITEM = 46

WM_COMPAREITEM

此消息有一个LBS_WANTKEYBOARDINPUT风格的发出给它的所有者来响应WM_KEYDOWN消息

0x0039

WM_CHARTOITEM = 47

发送此消息来判定ComboBox或ListBox新增加的项的相对位置

此消息由一个LBS_WANTKEYBOARDINPUT风格的列表框发送给他的所有者来响应WM_CHAR消息

WM_COMPACTING

WM_SETFONT = 48

0x0041

当绘制文本时程序发送此消息得到控件要用的颜色

显示内存已经很少了

WM_GETFONT = 49

WM_WINDOWPOSCHANGING

应用程序发送此消息得到当前控件绘制文本的字体

0x0046

WM_SETHOTKEY = 50

当调用SetWindowPos()函数改变窗口的大小和位置后,发送此消息给该窗口

应用程序发送此消息让一个窗口与一个热键相关连

WM_POWER

WM_GETHOTKEY = 51

0x0048

应用程序发送此消息来判断热键与某个窗口是否有关联

当系统将进入挂起状态时发送此消息给所有进程

WM_QUERYDRAGICON =55

WM_COPYDATA

此消息发送给最小化窗口,当此窗口将要被拖放而它的类中没有定义图标,应用程序能

0x004A

返回一个图标或光标的句柄,当用户拖放图标时系统显示这个图标或光标

当一个应用程序传递数据给另一个应用程序时发送此消息

WM_COMPAREITEM = 57

WM_CANCELJOURNAL

发送此消息来判定combobox或listbox新增加的项的相对位置

0x004B

WM_GETOBJECT = 61

当某个用户取消程序日志激活状态,发送此消息给应用程序

WM_COMPACTING = 65

WM_NOTIFY

显示内存已经很少了

0x004E

WM_WINDOWPOSCHANGING= 70

当某个控件的某个事件已经发生或这个控件需要得到一些信息时,发送此消息给它的父窗口

发送此消息给那个窗口的大小和位置将要被改变时,来调用setwindowpos函数或其它窗口管理函数

WM_INPUTLANGCHANGEREQUEST

WM_WINDOWPOSCHANGED= 71

0x0050

发送此消息给那个窗口的大小和位置已经被改变时,来调用setwindowpos函数或其它窗口管理函数

当用户选择某种输入语言,或输入语言的热键改变

WM_POWER = 72(适用于16位的windows)

WM_INPUTLANGCHANGE

当系统将要进入暂停状态时发送此消息

0x0051

WM_COPYDATA = 74

当应用程序输入语言改变后发送此消息给受影响的最顶级窗口

当一个应用程序传递数据给另一个应用程序时发送此消息

WM_TCARD

WM_CANCELJOURNAL =75

0x0052

当某个用户取消程序日志激活状态,提交此消息给程序

当应用程序已经初始化Windows帮助例程时发送此消息给应用程序

WM_NOTIFY = 78

WM_HELP

当某个控件的某个事件已经发生或这个控件需要得到一些信息时,发送此消息给它的父窗口

0x0053

WM_INPUTLANGCHANGEREQUEST= 80

当用户按下了F1,如果某个菜单是激活的,就发送此消息给此窗口关联的菜单,否则就发送给有焦点的窗口,如果当前都没有焦点,就把此消息发送给当前激活的窗口

当用户选择某种输入语言,或输入语言的热键改变

WM_USERCHANGED

WM_INPUTLANGCHANGE= 81

0x0054

当平台现场已经被改变后发送此消息给受影响的最顶级窗口

当用户已经登录或退出后发送此消息给所有的窗口,当用户登录或退出时系统更新用户的具体设置信息,在用户更新设置时系统马上发送此消息

WM_TCARD = 82

WM_NOTIFYFORMAT

当程序已经初始化windows帮助例程时发送此消息给应用程序

0x0055

WM_HELP = 83

公用控件和它们的父窗口通过此消息来判断在WM_NOTIFY消息中是使用ANSI还是UNICODE形式的结构,使用此控件能使某个控件与它的父控件进行相互通信

此消息显示用户按下了F1,如果某个菜单是激活的,就发送此消息个此窗口关联的菜单,否则就

WM_CONTEXTMENU

发送给有焦点的窗口,如果当前都没有焦点,就把此消息发送给当前激活的窗口

0x007B

WM_USERCHANGED = 84

当用户在某个窗口中点击右键,则发送此消息给该窗口

当用户已经登入或退出后发送此消息给所有的窗口,当用户登入或退出时系统更新用户的具体

WM_STYLECHANGING

设置信息,在用户更新设置时系统马上发送此消息;

0x007C

WM_NOTIFYFORMAT =85

当将要调用SetWindowLong()函数窗口的一个或多个风格时,发送此消息给该窗口

公用控件,自定义控件和他们的父窗口通过此消息来判断控件是使用ANSI还是UNICODE结构

WM_STYLECHANGED

在WM_NOTIFY消息,使用此控件能使某个控件与它的父控件之间进行相互通信

0x007D

WM_CONTEXTMENU =123

当调用SetWindowLong()函数改变了窗口的一个或多个风格后,发送此消息给该窗口

当用户某个窗口中点击了一下右键就发送此消息给这个窗口

WM_DISPLAYCHANGE

WM_STYLECHANGING =124

0x007E

当调用SETWINDOWLONG函数将要改变一个或多个
窗口的风格时发送此消息给那个窗口

当显示器的分辨率改变后发送此消息给所有的窗口

WM_STYLECHANGED =125

WM_GETICON

当调用SETWINDOWLONG函数一个或多个 窗口的风格后发送此消息给那个窗口

0x007F

WM_DISPLAYCHANGE =126

发送此消息给某个窗口,返回与某个窗口有关联的大图标或小图标的句柄

当显示器的分辨率改变后发送此消息给所有的窗口

WM_SETICON

WM_GETICON = 127

0x0080

此消息发送给某个窗口来返回与某个窗口有关连的大图标或小图标的句柄;

应用程序发送此消息让一个新的大图标或小图标与某个窗口关联

WM_SETICON = 128

WM_NCCREATE

程序发送此消息让一个新的大图标或小图标与某个窗口关联;

0x0081

WM_NCCREATE = 129

当某个窗口第一次被创建时,此消息在WM_CREATE消息被发送前发送

当某个窗口第一次被创建时,此消息在WM_CREATE消息发送前发送;

WM_NCDESTROY

WM_NCDESTROY = 130

0x0082

此消息通知某个窗口,非客户区正在销毁

此消息通知某个窗口,正在销毁非客户区

WM_NCCALCSIZE = 131

WM_NCCALCSIZE

当某个窗口的客户区域必须被核算时发送此消息

0x0083

WM_NCHITTEST =132//移动鼠标,按住或释放鼠标时发生

当计算某个窗口的客户区大小和位置时发送此消息

WM_NCPAINT = 133

WM_NCHITTEST

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章