MFC源码实战分析(四)——hWnd与CWnd之千里情缘一线牵

MFC教程 -- Windows界面开发 MFC教程 -- Windows界面开发 Windows消息机制 初步认识MFC 要想熟练掌握 Windows 应用程序的开发, 首先需要理解 Windows 平台下程序运行的内部机制。如果想要更好的学习掌握 MFC,必须要先了解Windows 程序的内部运行机制,为我们扫清学习路途中的第一个障碍,为进一步学习 MFC 程序打下基础。 1.1 基本概念解释 我们在编写标准C程序的时候,经常会调用各种库函数来辅助完成某些功能:初学者使用得最多的C库函数就是printf了,这些库函数是由你所使用的编 阅读详情

深入MFC的消息响应机制

对于Win32下的应用程序框架来说,如何建立起窗口句柄和窗口类间的对应关系是其在封装窗口类时必须考虑的,而这个过程和男女谈恋爱很类似:从CWnd找到对应的hWnd是相对容易的(m_hWnd就是CWnd的成员变量),就好像“女追男,隔层纱”;而要从hWnd找到对应的CWnd就难了,正所谓“男追女,隔座山”。因此,为了建立起hWnd到CWnd的联系,各大框架的设计者也是绞尽脑汁,就像追女孩一样,可谓“八仙过海,各显神通”。正好今天心情不太好,咱们就一起顺着MFC前辈当年的足迹“爬爬山”。

建立起CWnd*与HWND的联系

通过前文的分析,我们知道MFC有一个全局的窗口过程AfxWndProc供User32.dll回调,在其中通过CWnd::FromHandlePermanent(hWnd)函数,由窗口句柄hWnd找到了CWnd类地址。那么CWnd::FromHandlePermanent(HWND)是怎样由hWnd找到CWnd的指针的呢?我们来看看它的源码。
这里写图片描述
首先调用全局函数afxMapHWND获取到了一个CHandleMap类型对象的指针,并调用其LookupPermanent函数,传入hWnd值,由该函数返回CWnd指针。从其名字中的Map我们猜测,这应该是一个map容器。下面继续跟踪其类定义:

class CHandleMap
{
private:    // implementation
    CFixedAllocNoSync m_alloc;
    void (PASCAL* m_pfnConstructObject)(CObject* pObject);
    void (PASCAL* m_pfnDestructObject)(CObject* pObject);
    CMapPtrToPtr m_permanentMap;
    CMapPtrToPtr m_temporaryMap;
    CRuntimeClass* m_pClass;
    size_t m_nOffset;       // offset of handles in the object
    int m_nHandles;         // 1 or 2 (for CDC)

public:
    CHandleMap(CRuntimeClass* pClass, 
        void (PASCAL* pfnConstructObject)(CObject* pObject),
        void (PASCAL* pfnDestructObject)(CObject* pObject),
        size_t nOffset, int nHandles = 1);
    virtual ~CHandleMap() { DeleteTemp(); }

// Operations
public:
    CObject* FromHandle(HANDLE h);
    void DeleteTemp();
    void SetPermanent(HANDLE h, CObject* permOb);
    void RemoveHandle(HANDLE h);
    CObject* LookupPermanent(HANDLE h);
    CObject* LookupTemporary(HANDLE h);
    friend class CWinThread;
};

inline void CHandleMap::SetPermanent(HANDLE h, CObject* permOb)
    { m_permanentMap[(LPVOID)h] = permOb; }

inline void CHandleMap::RemoveHandle(HANDLE h)
{
    m_permanentMap.RemoveKey((LPVOID)h);
}

inline CObject* CHandleMap::LookupPermanent(HANDLE h)
    { return (CObject*)m_permanentMap.GetValueAt((LPVOID)h); }
inline CObject* CHandleMap::LookupTemporary(HANDLE h)
    { return (CObject*)m_temporaryMap.GetValueAt((LPVOID)h); }

从源码分析,这个类基本就是一个adapter,对CMapPtrToPtr进行了包装。关于CMapPtrToPtr,这里不再继续跟踪,否则就偏离我们的主线了,更多详细信息请自行分析源码,或参考MSDN https://docs.microsoft.com/en-us/cpp/mfc/reference/cmapptrtoptr-class

为什么要跟到CHandleMap,却不再关注其中CMapPtrToPtr呢?因为我们的目的已经达到了,从CHandleMap的类定义中,我们找到了与LookupPermanent相对应的函数SetPermanent,下面,看看HWND-CWND*组合是何时被传入的。

跟踪建立过程

我们在CMyWnd::CMyWnd上下断点,调试运行程序。当CMyWnd::CMyWnd被断下时,记住其实例地址。
这里写图片描述
由上图得CMyWnd类实例的地址为0x01404CD8,此时我们再对CHandleMap::SetPermanent下条件断点:
这里写图片描述
继续运行程序,断点被命中,查看调用栈:
这里写图片描述
我的天,我没看错吧?!user32.dll!DispatchHookW发起的调用,居然是在钩子处理函数中调用的!先不管那么多,找到向map容器中存入key-value对的地方:
这里写图片描述
再倒回去看看那个钩子函数:

LRESULT CALLBACK
_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
    _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
    if (code != HCBT_CREATEWND) return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code, wParam, lParam);

    CWnd* pWndInit = pThreadState->m_pWndInit;
    if (pWndInit != NULL || (!(lpcs->style & WS_CHILD)))
    {
        HWND hWnd = (HWND)wParam;
        WNDPROC oldWndProc;
        if (pWndInit != NULL)
        {
            // 将HWND-PCWnd对存入map中
            pWndInit->Attach(hWnd);
            // 在替换消息处理函数前调用此函数,目前为空函数(所谓子类化就是换掉窗口过程)
            pWndInit->PreSubclassWindow(); 
            // 获取AfxWndProc并替换窗口过程
            WNDPROC afxWndProc = AfxGetAfxWndProc(); //填入AfxWndProc
            oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (DWORD_PTR)afxWndProc);
        }
        //省略else部分
    }
    LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,
        wParam, lParam);
    return lResult;
}

可以看出这是一个CBT类型的消息钩子,在这里的主要目的是截获窗口创建消息,并将hWnd-CWnd*值存入map容器,然后将窗口过程替换掉。那就说明原先注册窗口类时,安装的窗口过程函数不是AfxWndProc。下面我们来看看窗口创建时的景象。

初始消息处理函数、设置CBT钩子

我们现在CMyWnd::CMyWnd处下断点,调试运行程序后待断下,然后在RegisterClassA、RegisterClassW、RegisterClassExA、RegisterClassExW处下断点。运行后程序被断下:
此时查看调用堆栈:
这里写图片描述
关键函数是CWnd::CreateEx

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName, DWORD dwStyle,
    int x, int y, int nWidth, int nHeight,
    HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
    // allow modification of several common create parameters
    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;

    if (!PreCreateWindow(cs))
    {
        PostNcDestroy();
        return FALSE;
    }

    AfxHookWindowCreate(this);
    HWND hWnd = CreateWindowEx(cs.dwExStyle, cs.lpszClass,
            cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
            cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

    if (!AfxUnhookWindowCreate())
        PostNcDestroy();        // cleanup if CreateWindowEx fails too soon

    if (hWnd == NULL)
        return FALSE;
    ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
    return TRUE;
}

其中,在PreCreateWindow中用传入的CREATESTRUCT,由其调用AfxEndDeferRegisterClass负责注册窗口类,在该函数中,将窗口类的窗口过程回调函数设为了DefWindowProc。这里PreCreateWindow是虚函数,被CFrameWnd类override了。
接下来就调用AfxHookWindowCreate了,代码如下:

void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
    _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
    if (pThreadState->m_pWndInit == pWnd)
        return;

    if (pThreadState->m_hHookOldCbtFilter == NULL)
    {
        pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
            _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());    //设置本线程的CBT钩子处理函数
        if (pThreadState->m_hHookOldCbtFilter == NULL)
            AfxThrowMemoryException();
    }

    pThreadState->m_pWndInit = pWnd;     //保存窗口类指针
}

可以看出,该CBT钩子的作用范围仅限于本线程内。最后,再看看注册时传入的DefWndProc:
这里写图片描述

这里写图片描述
厉害了,就是Win32SDK编程中缺省的那个。为什么MFC不直接将AfxWndProc作为默认窗口处理函数呢?搜索的答案是为了支持3D控件。

小结

  1. 在CMyWnd::CMyWnd调用虚函数Create时,由于CFrameWnd类override了此函数,所以调到的是CFrameWnd::Create
  2. CFrameWnd::Create中先加载了菜单之类的,然后调用虚函数CreateEx,由于CFrameWnd没有override此函数,因此调到了CWnd::CreateEx
  3. CWnd::CreateEx中,首先构造了一个CREATESTRUCT,填写了大部分信息(回调函数留空),引用传递给了虚函数PreCreateWindow,CFrameWnd重载了此函数,因此调用了CFrameWnd::PreCreateWindow
  4. CFrameWnd::PreCreateWindow中调用了全局函数AfxEndDeferRegisterClass,该函数根据要求设置了CREATESTRUCT中其余字段并将窗口过程地址为DefWindowProc并调用Win32API:RegisterClass注册了窗口类后返回
  5. AfxEndDeferRegisterClass、CFrameWnd::PreCreateWindow先后返回,在CWnd::CreateEx中调用全局函数AfxHookWindowCreate(this)将this存入线程存储空间并设置可线程级的CBT钩子。
  6. CWnd::CreateEx中继续调用Win32API:CreateEx创建窗口
  7. 窗口创建消息被线程级CBT钩子_AfxCbtFilterHook截获,在其中进行了两项重要操作:1.将hWnd-CWnd*存入了map容器中;2.替换窗口过程为AfxWndProc
  8. Win32API CreateEx完成后,CWnd::CreateEx调用AfxUnhookWindowCreate()卸载掉CBT钩子,然后返回。
1.2、基于卷积神经网络(CNN)的车牌自动识别系统(matlab) 基于卷积神经网络(CNN)的车牌自动识别系统(matlab) 阅读详情

相关推荐

微信小程序利用Echarts加载中国地图的步骤

微信小程序如何利用Echarts加载地图?可按照如下四个步骤进行。(阅读时间大概3分钟)。

成都都成 5283

MFC第十五天 HWND与CWnd对象之间的转换、CWnd类简介(补充)、员工管理系统登录界面眼睛捕获(补充)

HWND 与CWND 对象之间的转换,以及CWnd类的简介,对Setcapture捕捉函数以及相关函数进行介绍

Jcrry的博客 1829

ArcGIS Pro2.5深度学习使用注意事项

ArcGIS Pro2.5深度学习使用中的注意事项1、ArcGIS Pro2.5深度学习使用中的注意事项1.1、Label深度学习样本工具参数介绍1.2、准备数据参数介绍1.3、训练模型介绍1.4、ArcGIS Pro2.5深度学习推理工具和模型对应关系介绍1.4.1、Classify Objects Using Deep Learning1.4.2、Classify Pixels Using D...

GIS哼哈哈的博客 7202

深入解析MFC -- 句柄与对象的关系

在Windows体系中,很多对象都是以句柄的形式展示给开发人员的。比如窗口句柄(HWND),绘图设备(HDC)等等。然后大部分的API函数则围绕 这些句柄做文章。比如ShowWindow,SetWindowText,TextOut等等。这些API函数的第一个参数通常就是句柄了。但是在C++ 体系中,这种对于事物细节的访问,往往是有违其封装精神的。因此MFC做了很多的封装类,来隐藏这些细节。应运而生就是CWnd,CDC等类。通过这些类 暴露的方法,可以直接对句柄做操作,而又可以不去关心他。不过,我们今天的主题

zhangxinrun的专栏 1206

Cadence Spectre仿真保姆级教程:从DC/AC到PSS/PV,手把手教你搞定模拟电路分析

作为业界标准的Cadence Spectre仿真器,以其精确的算法和丰富的分析类型,成为每个模拟IC工程师必须掌握的利器。本文将带你从零开始,系统掌握Spectre仿真的核心技能,从最基础的DC工作点分析到复杂的PSS相位噪声仿真,每个步骤都配有详细的操作指导和实用技巧。记得我第一次使用Spectre仿真一个简单的两级运放时,花了整整三天时间才得到正确的AC响应曲线。过程中踩过的坑、浪费的时间,现在想来都是宝贵的学习资源。与传统的AC分析不同,STB分析可以在不破坏环路的情况下评估稳定性。

weixin_30898555的博客 336

【2026年华为暑期实习(AI)-4月8日-第一题- 选择题】(题目+思路+Java&C++&Python解析+在线测试)

答案与解析 1. 相关系数问题 答案:C. 完全负线性相关 解析:相关系数ρ=-1表示X和Y之间存在完全负线性相关关系,所有数据点都严格落在一条斜率为负的直线上。 2. 矩阵秩问题 答案:B. Σ的对角线上非零奇异值的个数 解析:矩阵A的秩等于其奇异值矩阵Σ中非零奇异值的个数,因为U和V都是正交矩阵,不改变矩阵的秩。 3. 条件概率问题 答案:B. 0.5 解析:根据全概率公式和贝叶斯公式计算,P(B)=0.5×0.4+0.3×0.6=0.38,P(A|B)=(0.5×0.4)/0.38≈0.526,最接近

m0_73659489的博客 287

《Windows 程序设计(第3版)》——6.2 窗口句柄映射

本节书摘来自异步社区《Windows 程序设计(第3版)》一书中的第6章,第6.2节,作者:王艳平 , 张铮著,更多章节内容可以访问云栖社区“异步社区”公众号查看 6.2 窗口句柄映射 6.2.1 向CWnd对象分发消息一个线程中可能(很可能)有不止一个窗口,因此也会有多个对应的CWnd对象。每个CWnd对象只响应发送给本窗口的消息,那么,如何将线程接受...

weixin_33705053的博客 302

n8n AI高效工作流自动化 部署使用教程

摘要: n8n是一款开源可视化工作流自动化工具,结合AI能力可高效解决重复性任务。文章详细介绍了从Docker部署到实战应用的完整流程:1)通过Docker Compose自托管n8n,确保数据可控;2)以AI客服日报机器人为例,演示如何串联工单系统、ChatGPT和邮件通知,实现定时自动化;3)进阶搭建交互式AI助手,利用ToolsAgent节点调用业务API;4)整合向量数据库构建RAG知识库问答。关键建议包括:拆分复杂工作流、严格管理凭据、区分开发与生产环境,并建议从高频刚性需求(如日报生成)切入,逐

专注AI大模型,软件混淆,授权 267

MFC 获取窗口句柄

每个窗口类的CWnd *hwnd指针中封装了该窗口的句柄m_hWnd,直接hwnd->m_hWnd即可得到该窗口的句柄。HWND是句柄,CWnd是MFC窗体类,CWnd中包含HWND句柄成员对象是m_hWnd。

weixin_46047677的博客 2326

精密装配机器人系列编程:ABB IRB 120_(20).编程项目设计与实施

在精密装配机器人系列编程中,项目的成功设计与实施涉及多个关键步骤。从项目需求分析到最终调试,每一个环节都需要细致的考虑和专业的技术。选择合适的编程语言、设计良好的程序结构、掌握调试技巧以及解决常见问题,都是确保机器人高效、稳定运行的重要因素。通过不断优化和改进,可以提高生产效率和产品质量,满足不断变化的生产需求。

zhubeibei168的博客 1243

MFC实现简单UDP通信(附完整源码)

MFC实现简单UDP通信(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1113

Windows/MFC_CWnd与Hwnd转换

1:CWnd是MFC的窗口基类。 HWND是Windows窗口句柄。 前者是一个C++对象,后者是一个类似于指针地址的数字型对象。 2:CWnd可以看成是对Windows窗口操作的封装,而封装的核心就是使用Windows窗口句柄(即HWND)来操作窗口.   3:CWnd可以通过CWnd::GetSafeHwnd()或成员变量m_hWnd来获得该窗口对象的HWND窗口句

迎难而上 1094

Pytorch CIFAR10图像分类 EfficientNet v1篇

EfficientNet源自Google Brain的论文EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks. 从标题也可以看出,这篇论文最主要的创新点是. 论文提出了,混合缩放,把网络缩放的三种方式:深度、宽度、分辨率,组合起来按照一定规则缩放,从而提高网络的效果。EfficientNet在网络变大时效果提升明显,把精度上限进一步提升,成为了当前最强网络。

风信子的猫Redamancy的快乐星球 1852

mfc窗口和线程

mfc 窗口 线程

建立虚拟世界 497

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

MFC 窗口创建过程与消息处理

MFC 窗口创建过程分析,消息处理

qq_61553520的博客 1622

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

pDC CDC GetDC hdc HDC hWnd HWND CWnd pWnd ---整理!!

 CDC *pDCpDC = GetDC();void show::draw(CWnd * pWnd)//要是CWnd指针是成员变量就可写成void show::draw() { CDC * pDC = pWnd->GetDC();//get the pWnd s DC //use dc draw your pic ... pWnd->ReleaseDC(pDC); } ---void sh

Parfait的专栏 2041

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

CWND和HWND之间的关系和转换 和获取方法

CWnd与HWND的区别  (2014-04-02 15:08:40) 转载▼ 标签:  cwnd   hwnd   mfc   fromhandle   getsafehandle()   it 分类: StudyNotes 一、区别 HWND是句柄,CWnd是MFC窗体类,CWnd

Alexander_Frank的博客 1万+

CWnd与HWND的区别与联系(相互转换)完全解析

区别            首先,从这两个单词的第一个字母即可知道两者之间的差别。熟悉MFC的人都知道在MFC中,类基本上都是以C开头的,这是一种规则约定,目的就是让人一看就知道它是一个类!           因此,CWnd第一个字母C指的是class的意思,即CWnd是一个类;HWND第一个字母H指的是handle的意思,即HWND是一个句柄。 CWnd:是MFC类库中所有窗口类

allin_allin的恋爱小屋 6219

CWnd与HWND的区别与转换

一、区别HWND是句柄,CWnd是MFC窗体类,CWnd中包含HWND句柄成员对象是m_hWnd.HWND是Windows系统中对所有窗口的一种标识,即窗口句柄。这是一个SDK概念。   CWnd是MFC类库中所有窗口类的基类。微软在MFC中将所有窗口的通用操作都封装到了这个类中,如:ShowWindow等等,同时它也封装了窗口句柄即m_hWnd成员。二、如何得到窗口对象指针CWnd * pCWn...

四维空间 624

MFC中窗口类、句柄的获取

2、主窗体的类对象指针:AfxGetApp()->m_pMainWnd->, //AfxGetApp()->m_pMainWnd就是主窗体的窗口对象指针。注:每个窗口类的CWnd *hwnd指针中封装了该窗口的句柄m_hWnd,直接hwnd->m_hWnd即可得到该窗口的句柄。AfxGetApp()->GetMainWnd()->m_hWnd //返回主窗口的句柄。AfxGetMainWnd() ->m_hWnd //返回主窗口的句柄。AfxGetMainWnd() //返回主窗口的CWnd指针。

taihang135的专栏 2533

[MFC] HWND(获取控件或窗体句柄) [大三TJB_708]

平台:visual studio 2010   1 C++窗体对象和HWND 窗体对象是一个继承于CWnd类的C++类的 对象。咱们可以用构造函数生成一个windows窗体,此windows窗体被一个 "window handle" (HWND)唯一标识,当一个窗体被创建后,此窗口的窗口句柄也响应的被创建。此窗口的窗口句柄被保存在窗体对象的数据成员m_hWnd中。   2. 获取一个控件

记一忘三二 3万+

CWnd指针和HWnd

HWND是Windows系统中对所有窗口的一种标识,即窗口句柄。这是一个SDK概念。   CWnd是MFC类库中所有窗口类的基类。微软在MFC中将所有窗口的通用操作都封装到了这个类中,如:ShowWindow等等,同时它也封装了窗口句柄即m_hWnd成员。 由HWnd得到CWnd*:CWnd wnd;HWnd hWnd;wnd.Attach(hWnd);通常一个窗口资源已经和一个CWnd类的对

wwwcs59的专栏 1017

MFC的句柄概念以及句柄类型

在MFC中,通常使用CDC类来进行绘图操作,而设备上下文句柄可以通过CDC::GetSafeHdc()方法获取,用于在设备上下文中进行绘图操作,例如绘制图形、文本或图像等。在MFC中,通过CMenu类可以创建和管理菜单,而菜单句柄可以通过CMenu::GetSafeHmenu()方法获取,用于操作菜单,例如添加菜单项、显示菜单等。在MFC中,通过CWnd::m_hWnd成员变量可以获取窗口句柄,用于操作和管理窗口,例如改变窗口的大小、位置、显示或隐藏窗口等。

最后一个bug的博客 789

从新手小白入门MFC框架视频精讲

微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类!

MFC获取窗口句柄号的六种方法

CClientDC dc(this); CString fun1; //直接获得 fun1.Format("%ld", AfxGetMainWnd()->m_hWnd); fun1 = "AfxGetMainWnd()->m_hWnd方法获取句柄号 = " + fun1; dc.TextOut(20, 50, fun1); //通过AfxGetApp()间接获得 fun1.Format("%ld", AfxGetApp()->GetMainWnd()->m_hWnd); fun1 .

顺其自然~专栏 3830
上一篇: MFC源码实战分析(三)——消息映射原理与消息路由机制初探
下一篇: MFC源码分析实战(五)——层层封装下的消息循环
LPWSTR
博客等级 码龄10年 73粉丝 47原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值