消息处理调试(第46课)

Windows程序和MFC程序是靠消息驱动的,他们对于消息的处理本质上是相同的。只是Windows程序对于消息处理的过程十分清晰明了,MFC程序则掩盖了消息处理的过程,以消息映射的方式呈现在开发者面前,使得开发消息的处理十分简单。当然,在分析消息类程序之前首先对Windows程序的消息机制进行一个简单的描述。

 

一、消息机制说明

 

1、什么是消息?

消息对于Win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序的消息队列中,然后应用程序再从消息队列中取出消息并进行相应的响应。在这个处理的过程中,操作系统也会给应用程序“发送消息”,而所谓的发送消息--实际上就是操作系统调用程序中的一个专门负责处理消息的函数,这个函数称为窗口过程。

 

消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息,在Windows中MSG结构体定义如下:

typedef struct tagMsg

{

HWND    hwnd;       //接受该消息的窗口句柄

UINT    message;     //消息常量标识符,也就是我们通常所说的消息号

WPARAM  wParam;       //32位消息的特定附加信息,确切含义依赖于消息值

LPARAM  lParam;        //32位消息的特定附加信息,确切含义依赖于消息值

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

POINT   pt;          //消息创建时的鼠标/光标在屏幕坐标系中的位置

}MSG;

 

hwnd:窗口句柄,它表示的是消息所属的窗口。我们通常开发的程序都是窗口应用程序,一般一个消息都是和某个窗口相关联的。比如我们在某个活动窗口按下鼠标右键,此时产生的消息就是发送给该活动窗口的。窗口可以是任何类型的屏幕对象,因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。

 

message:一个消息的标识符,用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。在Windows中消息是由一个数值表示的,不同的消息对应不同的数值。但由于当这些消息种类多到足以挑战我们的IQ,所以聪明的程序开发者便想到将这些数值定义为WM_XXX宏的形式。例如,鼠标左键按下的消息--WM_LBUTTONDOWN,键盘按下消息--WM_KEYDOWN,字符消息--WM_CHAR,等等..消息标识符以常量命名的方式指出消息的含义。当窗口过程接收到消息之后,他就会使用消息标识符来决定如何处理消息。例如、WM_PAINT告诉窗口过程窗体客户区被改变了需要重绘。符号常量指定系统消息属于的类别,其前缀指明了处理解释消息的窗体的类型。

 

wParam和lParam- - - 用于指定消息的附加信息。例如,当我们收到一个键盘按下消息的时候,message成员变量的值就是WM_KEYDOWN,但是用户到底按下的是哪一个按键,我们就得拜托这二位,由他们来告知我们具体的信息。

 

time和pt- - -这俩兄弟分别被用来表示消息投递到消息队列中的时间和鼠标当前的位置,一般情况下不怎么使用(但不代表没用)。

 

2、消息队列

在Windows编程中,每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该应用程序所创建的窗口的信息。例如,当我们按下鼠标右键的时候,这时会产生一个WM_RBUTTONDOWN消息,系统会自动将这个消息放进当前窗口所属的应用程序的消息队列中,等待应用程序的结束。Windows将产生的消息以此放进消息队列中,应用程序则通过一个消息循环不断的从该消息队列中读取消息,并做出响应。

 

3、消息标识符

系统保留消息标识符的值在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    应用程序自定义私有消息

 

4、消息的分类

Windows消息大体上可以分为3类:窗口消息,命令消息和空间通知消息。

 

窗口消息是系统中最常见的消息,它是指由操作系统和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都会激发窗口消息,还有我们在上面谈到的单击鼠标所产生的消息也是一种窗口消息。

 

命令消息是一种特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,例如按下一个按钮,他就会向主窗口发送一个命令消息。

 

控件消息:其实它是这样的,当一个窗口内的子控件发生了一些事情,而这些是需要通知父窗口的,此刻它就上场啦。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。

 

例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息--她类似于命令消息,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。

 

5、队列消息和非队列消息

从消息的发送途径来看,Windows程序中的消息可以分成2种:队列消息和非队列消息,也有叫“进队消息”和“不进队消息”。

 

消息队列可以分成系统消息队列和线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列。

 

(1)队列消息送到系统消息队列,然后到线程消息队列;

对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由 Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。

 

一般来讲,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外,同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息, 合并所有的无效区域到一个无效区域。合并WM_PAIN的目的是为了减少刷新窗口的次数。

 

(2)非队列消息直接送给目的窗口过程。

非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程。系统发送非队列消息通知窗口,系统发送消息通知窗口。例如,当用户激活一个窗口系统发送WM_ACTIVATE,WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息。

 

二、消息调试示例

以一个简单的win32程序进行说明(直接使用VS生成win32项目即可)。由于代码量比较大,这里只展现关键代码。

 

注册窗口类:

ATOM MyRegisterClass(HINSTANCE hInstance)

{

WNDCLASSEXW wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style          = CS_HREDRAW | CS_VREDRAW;

wcex.lpfnWndProc    = WndProc;     //关联窗口过程函数

wcex.cbClsExtra     = 0;

wcex.cbWndExtra     = 0;

wcex.hInstance      = hInstance;

wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32PROJECT1));

wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);

wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);

wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32PROJECT1);

wcex.lpszClassName  = szWindowClass;

wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

return RegisterClassExW(&wcex);

}

 

注册完之后创建窗口:

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

{

hInst = hInstance; // 将实例句柄存储在全局变量中

 

HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

 

if (!hWnd)

{

return FALSE;

}

 

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

 

return TRUE;

}

 

消息循环:

while (GetMessage(&msg, nullptr, 0, 0))

{

if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

 

消息处理函数:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (message)

{

case WM_COMMAND:

{

int wmId = LOWORD(wParam);

// 分析菜单选择:

switch (wmId)

{

case IDM_ABOUT:

DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);

break;

case IDM_EXIT:

DestroyWindow(hWnd);

break;

default:

return DefWindowProc(hWnd, message, wParam, lParam);

}

}

break;

case WM_PAINT:

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hWnd, &ps);

// TODO: 在此处添加使用 hdc 的任何绘图代码...

EndPaint(hWnd, &ps);

}

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hWnd, message, wParam, lParam);

}

return 0;

}

 

这个win32程序,我们没有写任何代码,运行之后显示一个窗口,关闭窗口程序结束运行。借助这个程序说一下与消息相关的函数。

 

把一个消息发送到窗口有三种方式:发送,寄送和广播。

 

  • 发送消息的函数有SendMessage、SendMessageCallback、SendNotifyMessage、 SendMessageTimeout。
  • 寄送消息的函数有PostMessage、PostThreadMessage、 PostQuitMessage。
  • 广播消息的函数有:BroadcastSystemMessage、 BroadcastSystemMessageEx。
  • 消息的接收主要由3个函数:GetMessage、PeekMessage、WaitMessage。

 

关于函数的信息可以参考API函数:消息传递或自行网上查阅。

 

消息循环:

while (GetMessage(&msg, nullptr, 0, 0))

{

if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

}

 

在消息循环中,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消息中调用。

 

消息处理函数:

窗口过程是一个用于处理所有发送到这个窗口的消息的函数。任何一个窗口类都有一个窗口过程。同一个类的窗口使用同样的窗口过程来响应消息。系统发送消息给窗口过程将消息作为参数传递给他,消息到来之后,按照消息类型排序进行处理,其中的参数则用来区分不同的消息,窗口过程使用参数产生对应行为。

 

一个窗口过程不经常忽略消息,如果它不处理,他会将消息传回执行到默认的处理。窗口过程通过调用DefWindowProc来做这个处理。窗口过程必须return一个值作为它的消息处理结果。大多数窗口只处理小部分消息和将其他的通过DefWindowProc传递给系统做默认的处理。窗口过程被素有属于同一个类的窗口共享,能为不同点窗口处理消息。

 

知道了消息机制的原理,下面简单说明一下如何使用OD调试。调试Win32程序,重点关注的是消息的处理。而消息过程函数是在注册类的时候初始化的,所以我们需要在RegisterClassEx函数设置断点,它的参数即为窗口类,结构体声明如下:

typedef struct tagWNDCLASSEXW {

UINT        cbSize;

/* Win 3.x */

UINT        style;

WNDPROC     lpfnWndProc;

int         cbClsExtra;

int         cbWndExtra;

HINSTANCE   hInstance;

HICON       hIcon;

HCURSOR     hCursor;

HBRUSH      hbrBackground;

LPCWSTR     lpszMenuName;

LPCWSTR     lpszClassName;

/* Win 4.0 */

HICON       hIconSm;

} WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;

 

第三个参数就是我们需要关注的重点函数。消息调试

 

程序断在RegisterClassEx后,左边为pWndClassEx参数的数据部分,可以看到第三个参数0xEB1352就是消息过程函数,再在这个函数设置断点,运行程序,在有消息处理的时候就会停留在过程处理函数中。然后调试方法就和普通程序的调试方法一样了。

头像
  • ¥ 298.0元
  • 市场价:899.0元
  • ¥ 68.0元
  • 市场价:98.0元
  • ¥ 298.0元
  • 市场价:398.0元
  • ¥ 99.9元
  • 市场价:299元

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: