MFC 窗口创建过程与消息处理
目录
钩子简介
介绍几个钩子函数,因为它们与窗口创建工程有关
安装钩子函数
HHOOK SetWindowsHookExA(
[in] int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);
参数说明:
- idHook:指定要安装的钩子类型,例如鼠标钩子、键盘钩子等。
- lpfn:指向钩子过程(HookProc)的指针,即要安装的钩子处理函数。
- hmod:指定包含钩子过程的DLL模块句柄。如果是本地钩子或全局钩子,则此参数可以为NULL。
- dwThreadId:指定关联的线程ID。对于全局钩子,如果此参数为0,则表示将钩子应用到所有线程。
钩子处理函数
LRESULT CALLBACK CBTProc(
_In_?int ???nCode,
_In_?WPARAM wParam,
_In_?LPARAM lParam
);
参数说明:
- nCode:指示钩子过程收到的通知代码,用于确定如何处理钩子。
- wParam:指定与钩子相关的消息的附加消息信息。
- lParam:指定与钩子相关的消息的附加消息信息。
更改窗口处理函数
LONG_PTR SetWindowLongPtrA(
[in] HWND hWnd,
[in] int nIndex,
[in] LONG_PTR dwNewLong
);
参数说明:
- hWnd:指定要设置额外窗口内存的窗口句柄。
- nIndex:指定要设置的值的偏移量。可以是一个负偏移量,也可以是预定义值之一。
- dwNewLong:指定的一个32位或64位的新值,取决于窗口的32位或64位。
代码编写
还是创建一个空白的Winodws应用程序
- 修改为多字节编码
- 使用静态MFC库,方便调试
#include <afxwin.h>
class CMyFrameWnd : public CFrameWnd {
public:
virtual LRESULT WindowProc(UINT msgID, WPARAM wParam, LPARAM);
};
LRESULT CMyFrameWnd::WindowProc(UINT msgID, WPARAM wParam, LPARAM lParam) {
//此函数内部的this为pFrame
switch (msgID) {
case WM_CREATE:
AfxMessageBox("WM_CREATE消息被处理");
break;
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hdc = ::BeginPaint(this->m_hWnd, &ps);
::TextOut(hdc, 100, 100, "hello", 5);
::EndPaint(m_hWnd, &ps);
}
break;
}
return CFrameWnd::WindowProc(msgID, wParam, lParam);
}
class CMyWinApp : public CWinApp {
public:
virtual BOOL InitInstance();
};
CMyWinApp theApp;//爆破点
BOOL CMyWinApp::InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL, "MFCCreate");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
遇见报错,是程序类型的问题
窗口创建过程分析
- 加载菜单
- 调用cWnd::CreateEx函数创建窗口
- 调用PreCreateWindow函数设计和注册窗口类调用AfxDeferRegisterClass函数,在这个函数中设计窗口类∶
- WNDCLASS wndcls;//设计窗口类
- 定义窗口的处理函数为DefWindowProcwndcls.lpfnWndProc = DefWindowProc;调用_AfxRegisterWithlcon函数
- 在函数内部,加载图标,并调用AfxRegisterClass函数,在函数内部,调用::RegisterClass win32 ApI函数注册窗口类
- 调用AfxHookWindowCreate 函数。
- 在函数内部,调用SetWindowsHookEx创建WH_CBT类型的钩子,钩子的处理函数是_AfxCbtFilterHook。
- 将框架类对象地址(pFrame)保存到当前程序线程信息中
- 调用CreateWindowEx函数创建窗口,马上调用钩子处理函数
- 钩子处理函数_AfxCbtFilterHook
- 将窗口句柄和框架类对象地址建立一对一的绑定关系。
- 使用SetWindowLong函数,将窗口处理的函数设置AfxWndProc
- 调用PreCreateWindow函数设计和注册窗口类调用AfxDeferRegisterClass函数,在这个函数中设计窗口类∶
下断点,分析 Create() 函数,F11进入分析
第一个参数为 NULL,第二个参数是一个字符串 MFCCreate,进入 Create 函数内部
前面是针对 第一个参数不为空的处理
if (lpszMenuName != NULL)
{
// load in a menu that will get destroyed when window gets destroyed
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
{
TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
PostNcDestroy(); // perhaps delete the C++ object
return FALSE;
}
}
参数为空的处理 ,进入CreateEx函数,NULL 作为第二个参数
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
CREATESTRUCT结构体在窗口创建过程中提供了创建窗口所需的各种信息,这个结构体会作为函数 CreateWindowEx 的参数创建窗口
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle(); // 获取当前程序实例句柄
cs.lpCreateParams = lpParam;
进入PreCtreateWinodow函数,创建窗口之前的处理函数
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
再进入AfxEndDeferRegisterClass函数,AfxGetModuleState() 是类库的全局函数,为全局变量 当前程序模块信息类 服务
通过定义WNDCLASS结构体并填充相应成员的值,开发人员可以注册一个新的窗口类,并使用该类创建窗口。可以通过 AfxGetInstanceHandle() 这个全局函数获取当前应用程序实例句柄。
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc; // 窗口处理函数
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
一直按F11,程序执行流程走到这个函数中
if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
{
// SDI Frame or MDI Child windows or views - normal colors
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
进入函数
_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME)
为窗口类 lpszClassName 赋值
之后调用??AfxRegisterClass(pWndCls)? ,这个函数就是注册窗口了,之后没有必要再跟下去了
AFX_STATIC BOOL AFXAPI _AfxRegisterWithIcon(WNDCLASS* pWndCls,
LPCTSTR lpszClassName, UINT nIDIcon)
{
pWndCls->lpszClassName = lpszClassName;
HINSTANCE hInst = AfxFindResourceHandle(
ATL_MAKEINTRESOURCE(nIDIcon), ATL_RT_GROUP_ICON);
if ((pWndCls->hIcon = ::LoadIconW(hInst, ATL_MAKEINTRESOURCEW(nIDIcon))) == NULL)
{
// use default icon
pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
}
return AfxRegisterClass(pWndCls);
}
完成了窗口注册,程序流程一路返回
跟进到 AfxHookWindowCreate(this)?
获取当前程序线程信息
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
利用Win32的API函数,埋下一个类型为WH_CBT的钩子,钩子处理函数是 _AfxCbtFilterHook
WH_CBT钩子是一种全局的系统事件钩子,它允许拦截一系列与计算机、窗口、任务和其他系统相关的事件。这些事件包括窗口的创建、激活、移动、销毁等,通过使用WH_CBT钩子,应用程序可以介入并对这些系统事件做出响应或修改。
::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
将自己new的框架类对象pFrame保存到pThreadState->m_pWndInit
pThreadState->m_pWndInit = pFrame;
之后开始创建窗口,当窗口创建成功后,钩子就会钩到WM_CREATE消息,之后调用钩子处理函数,函数第二个参数 wParam 是窗口句柄
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); // 程序线程信息
CWnd* pWndInit = pThreadState->m_pWndInit; // 框架窗口对象赋值
之后调用,attch 函数的this是框架窗口对象
pWndInit->Attach(hWnd);
进入到 afxMapHWND 函数
CHandleMap* pMap = afxMapHWND(TRUE);
函数内部,创建了一个CHandleMap返回给?CHandleMap* pMap
pState->m_pmapHWND = new CHandleMap(RUNTIME_CLASS(CWnd)
调用SetPermanent,第一个参数是窗口句柄,另一个是框架窗口对象
pMap->SetPermanent(m_hWnd = hWndNew, this);
?m_permanentMap是一个数组,根据下标窗口句柄,就能拿到窗口框架对象,也就是说建立了一个窗口句柄到框架窗口对象的映射
void CHandleMap::SetPermanent(HANDLE h, CObject* permOb)
{
BOOL bEnable = AfxEnableMemoryTracking(FALSE);
m_permanentMap[(LPVOID)h] = permOb;
AfxEnableMemoryTracking(bEnable);
}
将窗口处理函数更改为AfxWndProc(才是真正的窗口处理函数)?
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
接下来就是窗口处理函数来处理消息
消息处理
接下尝试调试WM_CREATE消息
- 当收到消息时,进入AfxWndProc函数。
- AfxWndProc 函数根据消息的窗口句柄,查询对应框架类对象的地址( pFrame ) 。
- 利用框架类对象地址( pFrame)调用框架类成员虚函数WindowProc,完成消息的处理。
重写虚函数,消息处理函数
LRESULT CMyFrameWnd::WindowProc(UINT msgID, WPARAM wParam, LPARAM lParam) {
//此函数内部的this为pFrame
switch (msgID) {
case WM_CREATE:
AfxMessageBox("WM_CREATE消息被处理");
break;
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hdc = ::BeginPaint(this->m_hWnd, &ps);
::TextOut(hdc, 100, 100, "hello", 5);
::EndPaint(m_hWnd, &ps);
}
break;
}
return CFrameWnd::WindowProc(msgID, wParam, lParam);
}
下个断点,看看调用堆栈
在这里下个断点,分析一下执行过程
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
进入FromHandlePermanent函数
CWnd* PASCAL CWnd::FromHandlePermanent(HWND hWnd)
{
CHandleMap* pMap = afxMapHWND();
CWnd* pWnd = NULL;
if (pMap != NULL)
{
// only look in the permanent map - does no allocations
pWnd = (CWnd*)pMap->LookupPermanent(hWnd);
ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);
}
return pWnd;
}
CHandleMap* pMap = afxMapHWND(); 进入看看,返回就是之前保存在程序模块线程信息中的映射类对象地址
根据窗口句柄,拿到框架窗口句柄,这之间关系就好比 洗衣机与洗衣机类,通过类来管理句柄
通过这个函数进一步调用到重写的虚函数
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
lResult = pWnd->WindowProc(nMsg, wParam, lParam);
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!