Windows 消息机制

hxboxy 贡献于2012-07-02

作者 liulei  创建于2005-04-04 02:44:00   修改者Liulei  修改于2005-04-04 15:26:00字数5523

文档摘要:进程/线程之间的同步机制实际上就是一种通信方式。 进程/线程之间的通信可以是:事件、信号、信号量、互斥量、消息、共享存储区、管道等。 消息,在一个先进先出的队列中排队,消息的响应可能会延迟,但是在非异常的情况下,所有的消息都将被处理。
关键词:

 Windows消息机制 (培训教材) 目录 前言 3 从Windows程序的基本结构开始 5 从SDK到MFC 8 MFC中的消息架构 9 PostMessage与SendMessage 11 前言 1、有了多线程,才有了对消息的需求。 Windows操作系统,是一个抢占式多线程操作系统。 为什么要通信?进程/线程之间需要信息交流。 进程/线程之间的同步机制实际上就是一种通信方式。 进程/线程之间的通信可以是:事件、信号、信号量、互斥量、消息、共享存储区、管道等。 消息,在一个先进先出的队列中排队,消息的响应可能会延迟,但是在非异常的情况下,所有的消息都将被处理。 2、不要来调用我,我会去调用你。 当一个程序运行的时候,似乎将对这台计算机的控制权从操作系统转移到了应用程序的手里。 但是当我们在写Windows程序的的时候,应用程序好像不能彻底的控制计算机。在一个窗口程序中,我们写的最多的是“响应”,而不是“调用”。 这就是架构的力量,隐藏更多的细节,让我们的目光仅仅需要关注于“应用”。 3、必要的知识——Windows中的线程 Windows中存在两种线程:一种是操作人员线程,一种是用户界面线程。 操作人员线程是用来执行某些辅助处理的线程,它不需要进行任何系统事件或者窗口事件的处理。 用户界面线程是指拥有自己的消息循环并能对用户界面对象进行创建、交互和撤销的线程。 Windows的消息机制与用户界面线程息息相关。用户界面线程一般是继承CWinThread类实现。 一个线程被创建后,一旦调用一个与图形用户界面有关的函数,或者调用了检查消息队列的函数,Windows会分配给这个线程一个THREADINFO结构(详细情况可以参看《Windows核心编程》第26章 窗口消息),同时使这个线程好像在一个独占的环境中运行。在这个结构里保存了一系列的消息队列(登记消息队列、发送消息队列、应答消息队列)、唤醒标志、以及用来描述线程局部输入状态的若干变量。这个结构是窗口消息系统的基础。当线程有了与之相联系的THREADINFO结构时,线程就有了自己的消息队列集合。 THREADINFO 登记消息队列指针 虚拟输入队列指针 发送消息队列指针 应答消息队列指针 nExitCode 唤醒标志 局部输入状态变量 登记的消息 登记的消息 输入MSG 输入MSG 输入MSG 发送MSG 发送MSG 应答MSG 应答MSG 如果没有这个线程的消息时,系统挂起这个线程,不给它分配CPU时间。当有一个消息登记或者发送到这个线程,系统要设置一个唤醒标志。 当调用GetMessage或者PeekMessage函数时,系统必须检查队列状态标志,并确定要处理哪个消息。 那么,到了这里我们会问:THREADINFO结构中消息都是哪里来的呢?首先我们知道系统、用户操作和程序本身都可以产生消息。而系统中也有一个消息队列,它负责管理将系统和用户操作的消息放入线程所属的THREADINFO结构中,亦即线程自己的消息队列中。 从Windows程序的基本结构开始 1、 最小的Windows程序应该包括两个函数:WinMain()和窗口函数 2、 WinMain作为程序入口,应该执行下列功能: a) 定义一个窗口类 b) 注册这个类 c) 创建该类的窗口 d) 显示窗口 e) 开始运行消息循环 3、 窗口函数响应相关消息。 4、 例程,我所见到的最小的Windows程序,请问谁见过更小的? #include char szWinName[] = "MyWin"; LRESULT CALLBACK WindowFunc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hThisInst,HINSTANCE hPrevInst, LPSTR lpszArgs,int nWinMode) { HWND hwnd; MSG msg; WNDCLASSEX wc1; wc1.cbSize = sizeof(WNDCLASSEX); wc1.hInstance = hThisInst; wc1.lpszClassName = szWinName; wc1.lpfnWndProc = WindowFunc; wc1.style = 0; wc1.hIcon = LoadIcon(NULL,IDI_APPLICATION); wc1.hIconSm = LoadIcon(NULL,IDI_WINLOGO); wc1.hCursor = LoadCursor(NULL,IDC_ARROW); wc1.lpszMenuName = NULL; wc1.cbClsExtra = 0; wc1.cbWndExtra = 0; wc1.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wc1)) return 0; hwnd = CreateWindow( szWinName, "Clock", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hThisInst, NULL ); ShowWindow(hwnd,nWinMode); UpdateWindow(hwnd); while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WindowFunc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { switch(message) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd,message,wParam,lParam); } return 0; } 所有的Windows执行程序都具有这样的结构,现在我们通常使用的MFC对这个过程进行了包装。 使用SDK写的Windows程序中管理消息的重点是GetMessage和DispatchMessage两个函数 注:TranslateMessage负责将虚拟键消息转换为字符信息。 GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax); 功能:获取与hWnd指定的窗口相关的且wMsgFilterMin,wMsgFilterMax参数制定的消息值范围内的消息。 当hWnd为NULL时,则GetMessage获取属于调用该函数的的应用程序的任一个窗口的消息。 当wMsgFilterMin,wMsgFilterMax为0时,则返回所有可用的消息。 在这个函数执行的时候,程序将控制权交给Windows,直到Windows的消息队列中出现了程序相关的消息,并且Windows愿意让GetMessage继续运行下去的时候,GetMessage将这个消息带回程序,由DispatchMessage将这个消息分发给窗口函数执行。这里的窗口程序就是前面注册过的那个WNDCLASSEX 结构中WindowFunc。 注意:使用SDK写的Windows程序中的消息响应使用了一个switch语句。 从SDK到MFC 摘自候捷《深入浅出MFC》(繁体第二版,394页)。 MFC中的消息架构 我们在使用MFC写的Windows程序中看不到GetMessage和DispatchMessage函数,他们被藏在了哪里? 从上面的图例中看到了一个MFC写成的程序中第9步执行的是一个Run函数,这个函数是CWinThread类中的成员函数。他封装了GetMessage和DispatchMessage函数。(大家有兴趣可以跟踪进去看看,但是想在Run函数内看见GetMessage和DispatchMessage的人会失望,在这个函数内,大家看不到他们,但是会看到 PeekMessage和PumpMessage。事实上,他们的功能类似,不过后二者可以添加空闲处理。下面为了行文方便,还是使用“GetMessage”和“DispatchMessage”来说明问题。) 那么当一个消息被GetMessage带回程序MFC会如何处理呢? 在参观MFC处理消息的过程之前,我们先看看MFC中消息映射机制。我们前面说过消息的响应不必写成一个switch来处理,MFC使用了一个消息表来替代。 消息表实际上是一个查阅表,它将一个类的消息与一个消息号连接了起来。这个消息表是由消息号与指向处理该消息的函数指针的数组构成的。 那么MFC是在哪里定义这个消息表的呢? 我们可能在以前就发现这样一个现象:在能够处理消息的类的头文件中都有一个macro:DECLARE_MESSAGE_MAP。这个macro的定义如下: #define DECLARE_MESSAGE_MAP() \ private: \ static const AFX_MSGMAP_ENTRY _messageEntries[]; \ protected: \ static AFX_DATA const AFX_MSGMAP messageMap; \ static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \ (摘自微软的AFXWIN.h,第1185行) 在这里定义了两个静态成员,这两个静态成员相结合,组成了消息表。同时它增加了两个方法,一个方法返回基类中的消息表,一个方法返回派生类中的消息表。 在头文件中有了定义,我们就到cpp文件中找实现,我们会在cpp文件中发现这样两个maro: BEGIN_MESSAGE_MAP与END_MESSAGE_MAP。 这两个maro的定义如下: #define BEGIN_MESSAGE_MAP(theClass, baseClass) \ const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \ { return &baseClass::messageMap; } \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return &theClass::messageMap; } \ AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \ { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \ AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \ { \ #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ 结合我们使用这两个macro的实况: BEGIN_MESSAGE_MAP(CMessageTestView, CView) //{{AFX_MSG_MAP(CMessageTestView) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP() 我们基本就可以明白MFC的消息表的模样了。 现在我们回到刚才的问题:消息被GetMessage带回程序MFC会如何处理呢?首先是DispatchMessage将会把消息交个AfxWndProc(最先调用的是AfxWndProcBase函数,在这个函数内调用了AfxWndProc)处理,最终AfxWndProc函数会调用CWnd::OnWndMsg函数。在这这个函数里,我们开始真正的使用消息表来将消息映射到消息响应函数上,并执行之。 当然,如果大家仔细看OnWndMsg函数也会看到一个大大的switch语句。这里处理一些“constant window message”。 系统消息队列 GetMessage AfxWndProc AfxWinMain DispatchMessage OnWndMsg On_XXX() Msg Mapping On_XXX() 线程消息队列 消息循环处理 消息响应处理 PostMessage与SendMessage 当我们使用PostMessage时,是将一个Message复制到“登记消息队列”中,然后并立即返回。之后由GetMessage取回并响应之。 当我们使用SendMessage时,我们都知道,这个消息发送函数必须等到消息响应执行完毕才能返回。而它如何做到这一点的呢?当调用这个SendMessage的线程向这个线程自己创建的窗口发送消息的时候,它只是调用指定窗口的窗口过程,将其作为一个子例程,当窗口过程完成对消息的处理时,返回给SendMessage一个值。当一个线程向其他线程创建的窗口发送消息的时候,SendMessage首先将消息加入接收线程的“发送消息队列”,并为这个线程设置标志。同时发送线程将自己挂起,并在自己的“应答消息队列”中加入一个等待消息。当消息被接收线程处理完毕后,窗口的返回值被登记到发送线程的应答消息队列中。这是发送线程被唤醒,取出包含在应答消息队列中的返回值。 系统消息队列 GetMessage AfxWndProc AfxWinMain DispatchMessage OnWndMsg On_XXX() Msg Mapping On_XXX() 线程消息队列 PostMessage SendMessage

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档