MFC程序基于消息,而使用事件驱动(Message Based,Event Driven)。也就是说MFC就是一个死循环,里面有很多的条件,每个条件对应一个方法。这些条件就是有消息类定义,当用户触发事件时,将发送消息到响应的窗口。当程序收到消息时进行解析,判断如果符合条件,将运行当前事件的处理方法。
MSG msg; while(GetMessage(&msg,NULL,NULL,NULL)) { TranslateMessage(&msg); DispatchMessage(&msg); }每一个程序都存在上述的循环,而MSG是一个结构,是Windows内设的一种数据格式,可以在WinUser.h中找到,代码如下:
/* * Message structure */ typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; #ifdef _MAC DWORD lPrivate; #endif } MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;接受并处理消息的主角是窗口,每一个窗口都必须要有能够处理消息的方法,称为“窗口函数”(Window Procedure/Function)。当窗口获得消息后,必须判断消息的类别,将消息转换(TranslateMessage(&msg)转换键盘消息),然后将消息传递到(DispatchMessage(&msg))窗口函数去处理。
LRESULT CALLBACK WinProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)其中wParam和lParam的意义因消息的不同而不同,但可以知道的是wParam的位数是随着操作系统的位数而定的,在32位的操作系统中为32位,当然64位的就为64位。知道了这个函数后,如果要将每一个消息对应到响应的处理函数中就需要如switch/case结构来判断,为了让程序更好的模块化,需要了解Message Map(消息映射)的原理。
一、消息映射机制
消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。消息可以由系统或者应用程序产生。系统在发生输入事件时产生消息。举个例子, 当用户敲键, 移动鼠标或者单击控件。系统也产生消息以响应由应用程序带来的变化, 比如应用程序改变系统字体改变窗体大小。应用程序可以产生消息使窗体执行任务,或者与其他应用程序中的窗口通讯。
在菜单中选择View-->Class Wizard,也可以用单击鼠标右键,选择Class Wizard,同样可以激活Class Wizard。选择Message Map标签,从Class name组合框中选取我们想要添加消息的类。在Object IDs列表框中,选取类的名称。此时, Messages列表框显示该类的大多数(若不是全部的话)可重载成员函数和窗口消息。类重载显示在列表的上部,以实际虚构成员函数的大小写字母来表示。其他为窗口消息,以大写字母出现,描述了实际窗口所能响应的消息ID。选中我们想添加的消息,单击Add Function按钮,Class Wizard自动将该消息添加进来。
1、一个MFC消息响应函数在程序中有三处相关信息:函数原型,函数实现,关联消息和消息响应函数的宏。
函数原型——头文件CDrawView——两个AFX_MSG注释宏之间——消息响应函数原型的声明 —— afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
函数实现——源文件CDrawView()——OnLButtonDown(UINT nFlags, CPoint point)
关联消息和消息响应函数的宏——源文件CDrawView()——BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP()之间
2、、 MFC消息映射机制的具体实现方法是:在每个能接收和处理消息的类中,定义一个消息和消息函数静态对照表,即:消息映射表。在消息映射表中,消息与对应消息处理函数指针是成对出现的。某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中。当有消息需要处理时,程序只要搜索该消息静态表,查看表中是否含有该消息,就可知道该类能否处理此消息。如果能处理该消息,则同样依照静态表能很容易找到并调用对应的消息处理函数。
3、 MFC消息映射机制的实际实现过程:MFC在后台维护了一个窗口句柄与对应的C++对象指针的对照表。
具体请参考:http://blog.csdn.net/ocean2006/article/details/5498265
4、MFC建立消息的步骤如下:消息的声明、消息的映射、消息的实现
声明:
//{{AFX_MSG afx_msg void OnTimer(UINT nIDEvent); afx_msg void OnPaint(); //}}AFX_MSG DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(CTestDialog, CDialog) //{{AFX_MSG_MAP(CTestDialog) ON_WM_TIMER() ON_WM_PAINT() //}}AFX_MSG_MAP END_MESSAGE_MAP()实现:
void CTestDialog::OnPaint() { } void CTestDialog::OnTimer(UINT nIDEvent) { CDialog::OnTimer(nIDEvent); }
二、事件驱动机制
EVENT有两种状态:发信号,不发信号。 SetEvent/ResetEvent分别将EVENT置为这两种状态分别是发信号与不发信号。WaitForSingleObject()等待,直到参数所指定的OBJECT成为发信号状态时才返回,OBJECT可以是EVENT,也可以是其它内核对象。当你创建一个线程时,其实那个线程是一个循环,不像上面那样只运行一次的。这样就带来了一个问题,在那个死循环里要找到合适的条件退出那个死循环,那么是怎么样实现它的呢?在Windows里往往是采用事件的方式,当然还可以采用其它的方式。在这里先介绍采用事件的方式来通知从线程运行函数退出来,它的实现原理是这样,在那个死循环里不断地使用 WaitForSingleObject函数来检查事件是否满足,如果满足就退出线程,不满足就继续运行。当在线程里运行阻塞的函数时,就需要在退出线程时,先要把阻塞状态变成非阻塞状态,比如使用一个线程去接收网络数据,同时使用阻塞的SOCKET时,那么要先关闭SOCKET,再发送事件信号,才可以退出线程的。CreateEvent、SetEvent、WaitForSingleObj
事件驱动过程需要使用以下三个函数:
1、CreateEvent
函功能描述:创建或打开一个命名的或无名的事件对象.
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性BOOL bManualReset, // 复位方法BOOL bInitialState, // 初始状态LPCTSTR lpName // 对象名称);
2. WaitForSingleObject
功能描述:用来检测 hHandle事件的信号状态,当函数的执行时间超过dwMilliseconds就返回,但如果参数dwMilliseconds为INFINITE时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到WaitForSingleObject有返回直才执行后面的代码。
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );参数hHandle是一个事件的句柄,第二个参数dwMilliseconds是时间间隔。如果时间是有信号状态返回WAIT_OBJECT_0,如果时间超过dwMilliseconds值但时间事件还是无信号状态则返回WAIT_TIMEOUT。
hHandle可以是下列对象的句柄:
Change notification 、Console input 、Event 、Job 、Memory resource notification、 Mutex 、Process、 Semaphore、 Thread 、Waitable timer等等。
3、SetEvent
功能描述:设置事件的状态为有标记,释放任意等待线程。如果事件是手工的,此事件将保持有标记直到调用ResetEvent。这种情况下将释放多个线程,如果事件是自动的,此事件将保持有标记,直到一个线程被释放,系统将设置事件的状态为无标记。如果没有线程在等待,则此事件将保持有标记,直到一个线程被释放。
4、ResetEvent
功能描述:这个函数把指定的事件对象设置为无信号状态。
BOOL ResetEvent(参数说明:HANDLE hEvent );
5、CloseHandle
功能描述:使用CloseHandle函数关闭句柄。当进程停止时,系统将自动关闭句柄。当最后一个句柄被关闭后,事件对象将被销毁。
6、应用:主要应用是锁定功能,实现PV操作(PV操作与信号量的处理相关,P表示通过的意思,V表示释放的意思,解决进程同步与互斥问题)
在调用的过程中,所有线程都可以在一个等待函数中指定事件对象句柄。当指定的对象的状态被置为有信号状态时,单对象等待函数将返回。
对于多对象等待函数,可以指定为任意或所有指定的对象被置为有信号状态。当等待函数返回时,等待线程将被释放去继续运行。
初始状态在bInitialState参数中进行设置。使用SetEvent函数将事件对象的状态置为有信号状态。使用ResetEvent函数将事件对象的状态置为无信号状态。
当一个手动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至明确调用ResetEvent函数将其置为无符号状态。
当事件的对象被置为有信号状态时,任意数量的等待中线程,以及随后开始等待的线程均会被释放。
当一个自动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至一个等待线程被释放;系统将自动将此函数置为无符号状态。如果没有等待线程正在等待,事件对象的状态将保持有信号状态。
多个进程可持有同一个事件对象的多个句柄,可以通过使用此对象来实现进程间的同步。下面的对象共享机制是可行的:
在CreateEvent函数中,lpEventAttributes参数指定句柄可被继承时,通过CreateProcess函数创建的子进程继承的事件对象句柄。
一个进程可以在DuplicateHandle函数中指定事件对象句柄,从而获得一个复制的句柄,此句柄可以被其它进程使用。
一个进程可以在OpenEvent或CreateEvent函数中指定一个名字,从而获得一个有名的事件对象句柄。
使用CloseHandle函数关闭句柄。当进程停止时,系统将自动关闭句柄。当最后一个句柄被关闭后,事件对象将被销毁。
7、以下是简单的事件驱动实例:
#include <WINDOWS.H>//需要访问windows API 函数,因此许加入此头文件 #include <IOSTREAM.H> DWORD WINAPI Fun1Pro(LPVOID lpParameter); DWORD WINAPI Fun2Pro(LPVOID lpParameter); int nTickets = 100; HANDLE g_hEvent; void main()//主线程 { HANDLE hThread1; HANDLE hThread2; //创建人工重置事件对象 // g_hEvent = CreateEvent(NULL,FALSE,TRUE,NULL); // SetEvent(g_hEvent); g_hEvent = CreateEvent(NULL,FALSE,TRUE,"Tickets");//创建命名事件对象 if (g_hEvent) { if (ERROR_ALREADY_EXISTS == GetLastError()) { cout<<"Only one thread can run..."<<endl; return; } } hThread1 = CreateThread(NULL,0,Fun1Pro,NULL,0,NULL); hThread2 = CreateThread(NULL,0,Fun2Pro,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); CloseHandle(g_hEvent); } DWORD WINAPI Fun1Pro(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent,INFINITE); // ResetEvent(g_hEvent); if (nTickets>0) { Sleep(1); cout<<"Thread1 sells tickets : "<<nTickets--<<endl; SetEvent(g_hEvent); } else { SetEvent(g_hEvent); break; } } return 0; } DWORD WINAPI Fun2Pro(LPVOID lpParameter) { while (TRUE) { WaitForSingleObject(g_hEvent,INFINITE); // ResetEvent(g_hEvent); if (nTickets>0) { Sleep(1); cout<<"Thread2 sells tickets : "<<nTickets--<<endl; SetEvent(g_hEvent); } else { SetEvent(g_hEvent); break; } } return 0; }