消息窗口使用方法介绍

消息窗口使用方法介绍

作者:陆其明

 

关键字 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”按钮触发。当按下按钮,会弹出相应的消息框,说明当前使用的方法能够正确接收消息。