进入MFC讲坛的前言(三)

简介: MFC中的窗口创建及窗口消息映射 我经常碰到有人问我有关窗口创建的问题,他们经常把用HWND描述的系统窗口对象和用CWnd描述的MFC的窗口对象混淆不清。这两者之间是紧密联系在一起的,但是MFC为了自身的管理,在CWnd中加了一些额外的内容,包括如何从HWND生成CWnd。

MFC中的窗口创建及窗口消息映射

我经常碰到有人问我有关窗口创建的问题,他们经常把用HWND描述的系统窗口对象和用CWnd描述的MFC的窗口对象混淆不清。这两者之间是紧密联系在一起的,但是MFC为了自身的管理,在CWnd中加了一些额外的内容,包括如何从HWND生成CWnd。

  在MFC中,有几种典型的窗口对象,CWnd描述的一般窗口对象,CView描述的视图对象,CFrameWnd描述的SDI框窗对象,CMDIFrameWnd描述的MDI框窗对象等等。在这一章中,主要讨论下述内容:

  MFC中窗口的创建

  MFC的消息映射机制(MESSAGE MAP)

  对于上面两点MFC的设计者们使用了很高的技巧来确保应用程序的代码尽可能小,其中的技巧和隐藏在它们背后的思想值得我们学习。下面对各项内容进行讨论。

  MFC中窗口的创建

  在Window下,创建窗口可以使用两个函数,CreateWindow()和CreateWindowEx(),它们都需要一个参数,这个参数是标识窗口类的字符串。所以,如果要创建窗口,一般的做法是,先使用RegisterClass()或RegisterClassEx()注册一个窗口类,然后使用该窗口类来创建窗口。在前面我也提到过,注册窗口类的最主要目的是为系统提供窗口函数的地址,以便被DispatchMessage()之类的函数回。

  在MFC中,创建窗口的函数是CWnd或其派生类的Create()或CreateEx方法,注册窗口类一般使用AfxRegisterWndClass(),在这个全局函数中,并没有发现窗口函数地址这样的参数,因此脑子里自然就会有这样的问题:窗口函数在哪里?它是如何同窗口关联的?下面我们将对MFC的一些与此有关的代码进行仔细分析,回答上述两个问题。

  窗口函数

  在MFC中,有一个全局的函数AfxWndProc(),正如下面的注释所示,它就是CWnd及所有从它派生的窗口类的窗口函数,它的实现如下:

  // The WndProc for all CWnd's and derived classes

  LRESULT CALLBACK

  AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

  {

   // special message which identifies the window as using AfxWndProc

   if (nMsg == WM_QUERYAFXWNDPROC)

   return 1;

   // all other messages route through message map

   CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

   ASSERT(pWnd != NULL);

   ASSERT(pWnd->m_hWnd == hWnd);

   return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

  }

  AfxCallWndProc()调用pWnd对象的虚拟函数WindowProc(),它的代码如下:

  LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

  {

   // OnWndMsg does most of the work, except for DefWindowProc call

   LRESULT lResult = 0;

   if (!OnWndMsg(message, wParam, lParam, &lResult))

   lResult = DefWindowProc(message, wParam, lParam);

   return lResult;

  }

上面的代码中,OnWndMsg()是用来处理该窗口消息的函数,如果某条消息没有被OnWndMsg()处理,也就是该窗口没有提供处理该消息的函数,它就调用DefWindowProc()进行处理,DefWindowProc()也是一个虚拟函数,看看它的代码:

  LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)

  {

   if (m_pfnSuper != NULL)

   return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);

   WNDPROC pfnWndProc;

   if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)

    return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);

   else

   return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);

  }

DefWindowProc()的策略很简单,调用基类的窗口函数m_pfnSuper来处理该消息。

  通过上面的分析,可以得出这样的结论:与其说AfxWndProc()是MFC的唯一窗口函数,还不如说AfxWndProc()是MFC的窗口消息分发中心。正是由于有了这个消息分发中心,才使得MFC的应用程序能够用有限的几个窗口类,作出各种形形色色的窗口,使得在应用程序中,增加CWnd的派生类,并不增加系统中窗口类的个数,将对系统资源的使用控制在一个稳定的范围之内。

  注册窗口类除了提供窗口函数外,还指定该窗口的一些外观,如是否有标题条,窗口缺省背景等等。在MFC框架中,有框窗、视图和控制条(CControlBar)等,它们除了操作行为不同外,外观等也不相同,所以MFC注册了几种缺省的窗口类。在MFC中,有一个全局函数AfxEndDeferRegisterClass(LONG fToRegister),它用来注册MFC预定义的窗口类,包括同框窗、视图所对应的窗口类。由于它的代码占的篇幅很长,而且实现也很简单,所以就不列出它的代码了,如果你有兴趣,可以在wincore.cpp中找到它的实现代码。


  


  挂接窗口函数

  如果你考察过AfxEndDeferRegisterClass()的实现代码,你会对一行代码感到迷惑,下面列出的是AfxEndDeferRegisterClass()的部分代码,带阴影部分的是那一行令人迷惑的代码:

  BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)

  {

   。。。。。。

   wndcls.lpfnWndProc = DefWindowProc;

   。。。。。。

  }

  MFC将所有预定义的窗口类的窗口函数都设置成DefWindowProc。大家都知道,DefWindowProc是Window下为一般窗口提供消息缺省处理的API,它肯定不是应用程序所需要的窗口函数,所以,MFC肯定在某个地方置换了它,置换DefWindowProc的代码在哪里呢?

  前面说过,在MFC中创建窗口时是用CWnd的两个虚拟函数Create()和CreateEx(),Create()是通过调用CreateEx()实现的,所以最终窗口的创建都要归结到CreateEx()函数上。因此,我们可以推断MFC在CreateEx()中置换了DefWindowProc。为了证实这一点,看看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;

  }

从上面的代码看不出任何显式的置换DefWindowProc的代码,其实,它隐藏在AfxHookWindowCreate(this)之中,顺藤摸瓜,再看看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());

    if (pThreadState->m_hHookOldCbtFilter == NULL)

    AfxThrowMemoryException();

    }

   ASSERT(pThreadState->m_hHookOldCbtFilter != NULL);

   ASSERT(pWnd != NULL);

   ASSERT(pWnd->m_hWnd == NULL); // only do once

   ASSERT(pThreadState->m_pWndInit == NULL); // hook not already in progress

   pThreadState->m_pWndInit = pWnd;

  }

AfxHookWindowCreate()设置了一个线程级的CBT Hook,该Hook的入口地址为_AfxCbtFilterHook,_AfxCbtFilterHook是一个全局的MFC函数,_AfxCbtFilterHook通过调用SetWindowLong()用AfxWndProc()的入口地址置换掉DefWindowProc。

  小结

  1、在MFC中,创建一个窗口的过程是:

    1、生成一个对应窗口类的对象

    2、调用该对象的Create()或CreateEx()方法

    3、在CreateEx()中(Create()是调用CreateEx()实现的),先调用虚拟函数PreCreateWindow(),让应用程序有一个改变窗口行为的机会,同时,在PreCreateWindow中,还作了一件很重要的事情,就是如果指向窗口类的字符串指针为NULL,就调用AfxDeferRegisterClass()注册一个合适的窗口类,将该注册的窗口类作为创建窗口的类型参数。AfxDeferRegisterClass()就是AfxEndDeferRegisterClass(),后者根据参数注册相应的窗口类,并将DefWindowProc作为该窗口类的临时窗口函数。

    4、CreateEx()在调用CreateWindowEx()创建真正的窗口对象之前,设置一个线程级的CBT Hook,该hook在窗口创建完成后被调用,MFC在hook函数中调用SetWindowLong()将该窗口的窗口函数置换成AfxWndProc。

  2、从Window的角度看,任何一个MFC应用程序都只有一个窗口函数AfxWndProc。

  3、AfxWndProc的作用是截获所有的发送给窗口消息,并将这些消息发送给相应的窗口对象的窗口函数WindowProc处理,所以它实质上是一个窗口消息分发器,注意,非窗口消息不被AfxWndProc所分发,它们在AfxWndProc被调用之前就被CWinThread::PreTranslateMessage()处理过了。

  4、同PreTranslateMessage()不同的地方在于,AfxWndProc()能够截获所有的来自于消息队列的和非消息队列的窗口消息(如调用SendMessage()发送的窗口消息)。
目录
相关文章
|
Prometheus 监控 Kubernetes
【云原生】k8s集群资源监控平台搭建—20230227
【云原生】k8s集群资源监控平台搭建—20230227
275 0
|
存储 Docker 容器
docker降级操作,20.10降级到19.03版本
docker降级操作,20.10降级到19.03版本
1123 0
docker降级操作,20.10降级到19.03版本
|
8月前
|
SQL Java 关系型数据库
【📕分布式锁通关指南 01】从解决库存超卖开始加锁的初体验
本文通过电商场景中的库存超卖问题,深入探讨了JVM锁、MySQL悲观锁和乐观锁的实现及其局限性。首先介绍了单次访问下库存扣减逻辑的正常运行,但在高并发场景下出现了超卖问题。接着分析了JVM锁在多例模式、事务模式和集群模式下的失效情况,并提出了使用数据库锁机制(如悲观锁和乐观锁)来解决并发问题。 悲观锁通过`update`语句或`select for update`实现,能有效防止超卖,但存在锁范围过大、性能差等问题。乐观锁则通过版本号或时间戳实现,适合读多写少的场景,但也面临高并发写操作性能低和ABA问题。 最终,文章强调没有完美的方案,只有根据具体业务场景选择合适的锁机制。
261 12
【📕分布式锁通关指南 01】从解决库存超卖开始加锁的初体验
|
5月前
|
人工智能 关系型数据库 分布式数据库
让数据与AI贴得更近,阿里云瑶池数据库系列产品焕新升级
4月9日阿里云AI势能大会上,阿里云瑶池数据库发布重磅新品及一系列产品能力升级。「推理加速服务」Tair KVCache全新上线,实现KVCache动态分层存储,显著提高内存资源利用率,为大模型推理降本提速。
|
10月前
|
JSON API 数据格式
随机头像图片[API盒子官方资源库]免费API接口教程
API盒子提供的头像资源接口,包含大量网络公开收集的头像,适合非商业用途。支持POST/GET请求,需提供用户ID、KEY及返回格式类型。返回数据包括状态码和消息内容,支持JSON/TXT格式。更多详情见API盒子官网。
|
11月前
|
Kubernetes 应用服务中间件 nginx
k8s基础使用--使用k8s部署nginx服务
本文介绍了Kubernetes中核心概念Deployment、Pod与Service的基本原理及应用。Pod作为最小调度单元,用于管理容器及其共享资源;Deployment则负责控制Pod副本数量,确保其符合预期状态;Service通过标签选择器实现Pod服务的负载均衡与暴露。此外,还提供了具体操作步骤,如通过`kubectl`命令创建Deployment和Service,以及如何验证其功能。实验环境包括一台master节点和两台worker节点,均已部署k8s-1.27。
908 1
|
人工智能 自然语言处理 测试技术
这些VLM竟都是盲人?GPT-4o、Sonnet-3.5相继败于视力测试
【7月更文挑战第28天】新研究表明VLM在简单视觉任务上的局限性。论文《Vision language models are blind》指出, GPT-4o、Claude-3.5 Sonnet等顶级模型在如判断形状重叠或字母识别等基本任务上表现不佳。另一研究在CVPR'24上介绍了一个新框架, 利用TRUMANS数据集生成精细的人物动作, 包括手部运动, 显示出在复杂场景下的强大能力, 尽管仍面临一定的局限。[论文链接](https://arxiv.org/pdf/2407.06581) [TRUMANS](https://arxiv.org/pdf/2403.08629)
211 4
|
算法 Java 编译器
掌握Go的运行时:从编译到执行
掌握Go的运行时:从编译到执行
437 0
|
Java API 开发工具
java与Android开发入门指南
java与Android开发入门指南
624 0
|
存储 缓存 安全
深入理解JVM - Hotspot算法细节
深入理解JVM - Hotspot算法细节
271 1