关键字 Message map SetWindowLong Hook 代码下载(13.2K) 大家知道,Windows是消息驱动的。写Windows应用程序,我们不可避免要跟窗口消息打交道。有一种情况,需要接收指定的消息,然后由消息来驱动执行某种操作。比如,使用第三方SDK进行视频采集,一种常见的方式就是,每当采集卡采集/准备好一块数据后,SDK就以消息的方式通知指定的窗口;应用程序接收到该消息后,必须及时读出SDK中的数据。这种情况下,我们在应用程序级必须指定给SDK一个窗口,用以响应这个特定的消息。我们称这样的窗口为“消息窗口”。 消息窗口有多种实现方法,笔者下面就列出常见的三种。没有哪一种是最好的,但或许你可以找到一种适合你的。接下去,我们来结合一个基于对话框的应用程序来讲解。 第一种方法:使用消息映射。这是一种最直接、最简单的方法。 a) 依赖主窗口 最简单的方法,我们可以在主窗口中定义消息映射。但有时候我们的主窗口类中已经有很多代码了,显得很凌乱;我们希望把这个特定消息的响应独立出来。这时,我们需要另外创建一个窗口。我们从CWnd类派生了自己的一个类CMsgWnd。 定义消息映射(假设WM_USER_MSG就是那个特定的消息): 在CMsgWnd.cpp中,加入 BEGIN_MESSAGE_MAP(CMsgWnd, CWnd) //{{AFX_MSG_MAP(CMsgWnd) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP ON_MESSAGE(WM_USER_MSG, OnUserMsg) END_MESSAGE_MAP() 以及定义消息响应函数(只是演示接收到了消息): LRESULT CMsgWnd::OnUserMsg(WPARAM wParam, LPARAM lParam) { CString strMsg = "Receive message using message map(CommonWnd): "; CString strParam; strParam.Format("Method %d!", wParam); ::AfxMessageBox(strMsg + strParam); return 0; } 在CMsgWnd.h中,加入函数声明: afx_msg LRESULT OnUserMsg(WPARAM wParam, LPARAM lParam); 演示时,我们在主窗口类(CMsgWindowDlg)中定义一个该类的一个实例,然后如下创建窗口:mMsgWnd.Create(NULL, "Msg", WS_CHILD, CRect(0,0,1,1), this, 0); b) 不依赖其它窗口 上述的方法,我们创建的消息窗口作为主窗口的子窗口。但是,如果我们不想牵扯到其他窗口,我们应该怎么做呢?很简单,修改基类,从CFrameWnd类派生。为此,我们又生成了一个类CMsgFrameWnd。接下去,定义消息映射的过程跟上述方法是相似的。 值得注意的是:我们在演示时,使用new操作在系统的堆(Heap)上创建这个窗口,如下: mMsgFrameWnd = new CMsgFrameWnd(); // creat window in heap mMsgFrameWnd->Create(0, "Msg", WS_POPUPWINDOW, CRect(0,0,1,1), 0, 0); 在销毁这个窗口时,千万不要使用delete,而要使用DestroyWindow,如下: if (mMsgFrameWnd) { ::DestroyWindow(mMsgFrameWnd->GetSafeHwnd()); mMsgFrameWnd = NULL; } 因为CFrameWnd基类实现中会自动调用delete this来释放窗口对象内存。 第二种方法:使用SetWindowLong替换窗口处理函数。这也是一种比较方便的方法。 如果我们不想自定义类,或者不想使用MFC的窗口类,更或者我们在DLL中完成我们的工作,根本不关心也很难利用其它的窗口,那么,就使用Windows API来创建一个消息窗口吧。参考如下: // create an overlapped window with an MFC window class LPCTSTR lpszClass = AfxRegisterWndClass(NULL); HWND hWnd=::CreateWindow(lpszClass, // windows class name "Msg Hub", // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, CW_USEDEFAULT, // position and dimensions CW_USEDEFAULT, CW_USEDEFAULT, NULL, // owner window handle--NULL is Desktop NULL, // for popup and overlapped windows: window menu handle AfxGetInstanceHandle(), // handle to application instance NULL // pointer to window-creation data ); gWindowMethod2.Attach(hWnd); // change the window procdure address gOriginalProc = (WNDPROC) SetWindowLong(hWnd, GWL_WNDPROC, (LONG)NewWndProc); 窗口创建成功后,使用SetWindowLong给这个窗口指定一个新的消息处理函数,定义为: LRESULT WINAPI NewWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_USER_MSG) { CString strMsg = "Receive message using SetWindowLong: "; CString strParam; strParam.Format("Method %d!", wParam); ::AfxMessageBox(strMsg + strParam); return 1; } return CallWindowProc(gOriginalProc,hWnd,uMsg,wParam,lParam); } 注意在不再使用该消息窗口时销毁它: if (gWindowMethod2.GetSafeHwnd()) { HWND hWnd = gWindowMethod2.Detach(); ::DestroyWindow(hWnd); } 第三种方法:使用Hook替换截获消息。这是一种小题大做、“牛刀杀鸡”的方法。 与第二种方法相似的是,我们同样创建一个窗口,如下: // create an overlapped window with an MFC window class LPCTSTR lpszClass = AfxRegisterWndClass(NULL); HWND hWnd=::CreateWindow(lpszClass, // windows class name "Msg Hub", // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, CW_USEDEFAULT, // position and dimensions CW_USEDEFAULT, CW_USEDEFAULT, NULL, // owner window handle--NULL is Desktop NULL, // for popup and overlapped windows: window menu handle AfxGetInstanceHandle(), // handle to application instance NULL // pointer to window-creation data ); gWindowMethod2.Attach(hWnd); // Install hook for the current thread glhHook = SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc,AfxGetInstanceHandle(),GetCurrentThreadId()); 窗口创建成功后,我们就使用SetWindowsHookEx安装一个WH_CALLWNDPROC类型的钩子(Hook)。钩子函数定义如下: LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { CWPSTRUCT * pMsg = (CWPSTRUCT*) lParam; if (pMsg->hwnd == gWindowMethod3.GetSafeHwnd() && pMsg->message == WM_USER_MSG) { CString strMsg = "Receive message using Hook: "; CString strParam; strParam.Format("Method %d!", pMsg->wParam); ::AfxMessageBox(strMsg + strParam); return TRUE; } } return CallNextHookEx(glhHook, nCode, wParam, lParam); } 注意在不再使用该消息窗口时销毁它,并卸载消息钩子: UnhookWindowsHookEx(glhHook); if (gWindowMethod3.GetSafeHwnd()) { HWND hWnd = gWindowMethod3.Detach(); ::DestroyWindow(hWnd); } 演示说明: 主窗口界面如下:
我们使用SendMessage函数来发送WM_USER_MSG消息,消息通过界面上的一系列“Send_Msg”按钮触发。当按下按钮,会弹出相应的消息框,说明当前使用的方法能够正确接收消息。
|