WTL源码剖析 --- ATLAPP.H

2024-04-11 15:58
文章标签 源码 剖析 wtl atlapp

本文主要是介绍WTL源码剖析 --- ATLAPP.H,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者:姜江
QQ:457283
E-mail:jznsmail@163.net

ATLAPP.H包含了消息循环类、接口类、和产生应用程序所必需的一些基础类定义。
类定义如下:
CmessageFilter类---用于消息过滤的
CidleHandler 类---用于空闲消息处理的
CmessageLoop类---用于消息循环的
CappModule 类---应用程序基础类
CserverAppModule类---用于Com服务构架的应用程序类
另外还有3个全局函数:
AtlGetDefaultGuiFont()获得默认的显示字体
AtlCreateBoldFont()   产生一个粗体字体
AtlInitCommonControls()初始化一些控件所需共同的DLL
      WTL 程序的结构
       一个窗口程序的创建到销毁过程主要经过如下几个阶段
1.  注册窗口类
2.  创建窗口
3.  进入消息循环
如果用 C 写过 Win32 窗口程序的人一定会记得如下的结构:
// 窗口过程处理函数
LRESULT CALLBACK WndProc(HWND hwnd,UINT Message,WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR szCmdLine,int iCmdShow)
{
          HWND hwnd = NULL;
          MSG msg;
         
          WNDCLASS wndclass;
          wndclass.style       = CS_HREDRAW | CS_VREDRAW;
          wndclass.lpfnWndProc = WndProc;
    
     //注册窗口
     if(!RegisterClass(&wndclass))
     {
          MessageBox(NULL,TEXT("Porgram requires Windows NT!"),szAppName,MB_ICONERROR);
          return 0;
          }
     //创建窗口
     hwnd = CreateWindow(szAppName,TEXT("My Application"),
     WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
     CW_USEDEFAULT,CW_USEDEFAULT,
     CW_USEDEFAULT,CW_USEDEFAULT,
     NULL,NULL,hInstance,NULL);
          ShowWindow(hwnd,iCmdShow);
          UpdateWindow(hwnd);
        
         //进入消息循环
          while(GetMessage(&msg,NULL,0,0))
          {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
     }
     return msg.wParam;
}
那么你可能会问 WTL WinMain 函数再哪里?如果你通过 WTL/ATL 导向生成一个应用程序,那么你会在跟工程名字同名的 .cpp 文件中发现如下的代码:
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
     HRESULT hRes = ::CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following call instead to
// make the EXE free threaded. This means that calls come in on a random RPC thread.
//     HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
          ATLASSERT(SUCCEEDED(hRes));
// this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used
          ::DefWindowProc(NULL, 0, 0, 0L);
        AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); // add flags to support other controls
     hRes = _Module.Init(NULL, hInstance); // 等下分析它的实现      ATLASSERT(SUCCEEDED(hRes));
     int nRet = Run(lpstrCmdLine, nCmdShow);//程序的关键分
     _Module.Term();
     ::CoUninitialize();
     return nRet;
}   
从这个_tWinMain函数的定义,你可以发现程序的关键部分是我紫色标记出来的Run()函数。这个函数是一个自定义的函数,不过如果通过ATL/WTL导向程序,那么会自动生成这样一个Run()函数的,下面我们先分析一下这个自动生成的Run函数。
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)
{
     CMessageLoop theLoop;                  //定义消息循环
     _Module.AddMessageLoop(&theLoop);   //将消息添加到消息循环
     CMainFrame wndMain;                //应用程序框架类
     //生成框架
     if(wndMain.CreateEx() == NULL)
     {
          ATLTRACE(_T("Main window creation failed!/n"));
         return 0;
     }
     //显示框架
     wndMain.ShowWindow(nCmdShow);
     //运行消息循环
     int nRet = theLoop.Run();
     //清除消息
     _Module.RemoveMessageLoop();
     return nRet;
}
通过这个Run函数我们可以看到在函数中完成了如下几个过程:
1. 生成一个消息循环对象(theLoop)
2. 在全局的_Module中加入这个消息循环
3. 生成一个应用程序框架对象
4. 显示应用程序框架
5. 开始消息循环
6. 结束消息循环
7. 返回WinMain函数,结束程序
实现分析
在这篇文章我不想过多的分析应用程序框架和窗口的细节,这些内容将放在以后的几篇文章中详细分析,本文主要对ATLAPP.H头文件中实现的一些过程进行详细分析。
首先从全局变量_Module开始。
_Module 维持着生成应用程序的主线程,控制着程序的消息循环队列,是一个 CAppModule的对象。该CAppModule从ATL::CcomModule继承。
在WTL::CappModule中定义了8个公有成员函数,分别为:
AddMessageLoop()添加一个消息循环,进入消息循环队列里。
RemoveMessageLoop()移除消息循环队列。
GetMessageLoop()获得消息循环。
InitSettingChangeNotify()初始化环境
AddSettingChangeNotify()添加一个窗口句柄。
RemoveSettingChangeNotify()清理环境
除了8个公有成员函数外,该类还定义了3个公有成员变量
m_dwMainThreadID负责保存该应用程序的主线程ID
m_pMsgLoopMap负责存储消息循环
m_pSettingChangeNotify负责存放窗口句柄
下面分别来分析几个主要成员函数的实现:
BOOL AddMessageLoop(CMessageLoop* pMsgLoop)
{
     CStaticDataInitCriticalSectionLock lock;
     //锁住关键片断,由于进程同步的关系!!!
     if(FAILED(lock.Lock()))
     {
          ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::AddMessageLoop./n"));
          ATLASSERT(FALSE);
     return FALSE;
     }
        
     ATLASSERT(pMsgLoop != NULL);
     ATLASSERT(m_pMsgLoopMap->Lookup(::GetCurrentThreadId()) == NULL);   // not in map yet
     BOOL bRet = m_pMsgLoopMap->Add(::GetCurrentThreadId(), pMsgLoop);
     lock.Unlock();
     return bRet;
}
     关键部分我用红色的字体标记出来了,意思是什么?通过当前线程的Id来标示一个消息循环,存储在m_pMsgLoopMap中。
BOOL RemoveMessageLoop()
     {
          CStaticDataInitCriticalSectionLock lock;
          if(FAILED(lock.Lock()))
         {
              ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::RemoveMessageLoop./n"));
              ATLASSERT(FALSE);
              return FALSE;
         }
         BOOL bRet = m_pMsgLoopMap->Remove(::GetCurrentThreadId());
          lock.Unlock();
         return bRet;
     }
       关键部分同样通过红色字体标记出来,嗯,没错正如 AddMessageLoop 函数一样,该函数也是通过线程 Id 来寻找消息循环移除对象的。
CMessageLoop* GetMessageLoop(DWORD dwThreadID = ::GetCurrentThreadId()) const
     {
          CStaticDataInitCriticalSectionLock lock;
          if(FAILED(lock.Lock()))
         {
              ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::GetMessageLoop./n"));
              ATLASSERT(FALSE);
              return NULL;
         }
          CMessageLoop* pLoop = m_pMsgLoopMap->Lookup(dwThreadID);
          lock.Unlock();
         return pLoop;
     }
该函数通过线程Id在m_pMsgLoopMap消息队列中寻找对应的消息循环,找到后返回。
     BOOL InitSettingChangeNotify(DLGPROC pfnDlgProc = _SettingChangeDlgProc)
     {
          CStaticDataInitCriticalSectionLock lock;
          if(FAILED(lock.Lock()))
         {
              ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::InitSettingChangeNotify./n"));
              ATLASSERT(FALSE);
              return FALSE;
         }
          if(m_pSettingChangeNotify == NULL)
         {
              typedef ATL::CSimpleArray<HWND>   _notifyClass;
              ATLTRY(m_pSettingChangeNotify = new _notifyClass);
              ATLASSERT(m_pSettingChangeNotify != NULL);
         }
         BOOL bRet = (m_pSettingChangeNotify != NULL);
          if(bRet && m_pSettingChangeNotify->GetSize() == 0)
         {
              // init everything
              _ATL_EMPTY_DLGTEMPLATE templ;
              //增加一个无模式对话框
              HWND hNtfWnd = ::CreateDialogIndirect(GetModuleInstance(), &templ, NULL, pfnDlgProc);
              ATLASSERT(::IsWindow(hNtfWnd));
              if(::IsWindow(hNtfWnd))
              {
// need conditional code because types don't match in winuser.h
#ifdef _WIN64
                   ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, (LONG_PTR)this);
#else
                   ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, PtrToLong(this));
#endif
                   //加入该窗口句柄
                   bRet = m_pSettingChangeNotify->Add(hNtfWnd);
              }
              else
              {
                   bRet = FALSE;
              }
         }
          lock.Unlock();
         return bRet;
     }
该函数用来初始化一个存放窗口句柄的对象
     BOOL InitSettingChangeNotify(DLGPROC pfnDlgProc = _SettingChangeDlgProc)
     {
          CStaticDataInitCriticalSectionLock lock;
          if(FAILED(lock.Lock()))
         {
              ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::InitSettingChangeNotify./n"));
              ATLASSERT(FALSE);
              return FALSE;
         }
          if(m_pSettingChangeNotify == NULL)
         {
              typedef ATL::CSimpleArray<HWND>   _notifyClass;
              ATLTRY(m_pSettingChangeNotify = new _notifyClass);
              ATLASSERT(m_pSettingChangeNotify != NULL);
         }
         BOOL bRet = (m_pSettingChangeNotify != NULL);
          if(bRet && m_pSettingChangeNotify->GetSize() == 0)
         {
              // init everything
              //??空的ATL Dialog Template吗?
              _ATL_EMPTY_DLGTEMPLATE templ;
              //增加一个无模式对话框
              HWND hNtfWnd = ::CreateDialogIndirect(GetModuleInstance(), &templ, NULL, pfnDlgProc);
              ATLASSERT(::IsWindow(hNtfWnd));
              if(::IsWindow(hNtfWnd))
              {
// need conditional code because types don't match in winuser.h
#ifdef _WIN64
                   ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, (LONG_PTR)this);
#else
                   ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, PtrToLong(this));
#endif
                   bRet = m_pSettingChangeNotify->Add(hNtfWnd);
              }
              else
              {
                   bRet = FALSE;
              }
         }
          lock.Unlock();
         return bRet;
     }
     //清理消息
     void TermSettingChangeNotify()
     {
          CStaticDataInitCriticalSectionLock lock;
          if(FAILED(lock.Lock()))
         {
              ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::TermSettingChangeNotify./n"));
              ATLASSERT(FALSE);
              return;
         }
          if(m_pSettingChangeNotify != NULL && m_pSettingChangeNotify->GetSize() > 0)
              //销毁窗口
              ::DestroyWindow((*m_pSettingChangeNotify)[0]);
         delete m_pSettingChangeNotify;
          m_pSettingChangeNotify = NULL;
          lock.Unlock();
     }
BOOL AddSettingChangeNotify(HWND hWnd)
     {
          CStaticDataInitCriticalSectionLock lock;
          if(FAILED(lock.Lock()))
         {
              ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::AddSettingChangeNotify./n"));
              ATLASSERT(FALSE);
              return FALSE;
         }
          ATLASSERT(::IsWindow(hWnd));
         BOOL bRet = FALSE;
          if(InitSettingChangeNotify() != FALSE)
              bRet = m_pSettingChangeNotify->Add(hWnd);
          lock.Unlock();
         return bRet;
     }
BOOL RemoveSettingChangeNotify(HWND hWnd)
     {
          CStaticDataInitCriticalSectionLock lock;
          if(FAILED(lock.Lock()))
         {
              ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CAppModule::RemoveSettingChangeNotify./n"));
              ATLASSERT(FALSE);
              return FALSE;
         }
         BOOL bRet = FALSE;
          if(m_pSettingChangeNotify != NULL)
              bRet = m_pSettingChangeNotify->Remove(hWnd);
          lock.Unlock();
         return bRet;
     }
     现在回到刚才提到的Run()函数,里面最开始就定义了一个CmessageLoop循环对象,然后通过_Module对象的AddMessageLoop成员函数加入到循环队列里面,直到_Module调用了RemoveMessageLoop移除循环队列,程序才结束循环,返回到WinMain函数。
     在这里还有一个比较重要的类,那就是CMessageLoop,是他维持了系统的消息,维持了程序的生命周期。那么下面我们来看看这个类的定义和具体的实现方法。
CmessageLoop 包含了如下一些成员函数和成员变量
成员变量
//处理消息
     ATL::CSimpleArray<CMessageFilter*> m_aMsgFilter;
     //处理空闲句柄
     ATL::CSimpleArray<CIdleHandler*> m_aIdleHandler;
     //Win32API消息结构
     MSG m_msg;
     成员函数(用红色标记的函数是虚函数)
AddMessageFilter         加入一条消息过滤
RemoveMessageFilter      移除一条消息过滤
AddIdleHandler       加入一个空闲句柄
RemoveIdleHandler         移出一个空闲句柄
AddUpdateUI              为了兼容老的ATL而设计的
RemoveUpdateUI           为了兼容老的ATL而设计的
IsIdleMessage            过滤一些比如WM_MOUSEMOVE之类的消息
Run                      消息循环。关键部分!!!
PreTranslateMessage      消息过滤
OnIdle                   空闲处理
再这里我不准备对每个函数都进行详细的分析,主要分析核心的函数Run,CmessageLoop由它来维持着系统的消息循环。
函数如下:
int Run()
     {
         //空闲?
         BOOL bDoIdle = TRUE;
         //空闲计数器
         int nIdleCount = 0;
         //返回标志
         BOOL bRet;
         //开始消息循环了哦!!!
          for(;;)
         {
              //当bDoIdle为TRUE,并且不能从消息队列里面取出消息了,那么开始空闲操作了!
              //PM_NOREMOVE:再PeekMessage函数处理后不将消息从队列里移除
              while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE))
              {
                   if(!OnIdle(nIdleCount++))
                        bDoIdle = FALSE;
              }
             
              //从当前线程获取一个消息
              //返回-1表示出现一个错误
              //返回 0表示提交了一个WM_QUIT,程序将要退出
              //成功获得一个消息,返回不等于0的值
              bRet = ::GetMessage(&m_msg, NULL, 0, 0);
              if(bRet == -1)
              {
                   ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)/n"));
                   continue;   // error, don't process
              }
              else if(!bRet)
              {
                   ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting/n"));
                   break;   // WM_QUIT, exit message loop
              }
              //如果熟悉使用c语言来写Win32的程序员会发现,原来WinMain中的哪个处理消息循环的语句放到这里来了!!!
              if(!PreTranslateMessage(&m_msg))
              {
                   //translates virtual-key messages into character messages.
                   ::TranslateMessage(&m_msg);
                   //dispatches a message to a window procedure
                   ::DispatchMessage(&m_msg);
              }
             
              //判断是否为空闲消息?
              //排除WM_MOUSEMOVE WM_NCMOUSEMOVE WM_SYSTIMER消息
              if(IsIdleMessage(&m_msg))
              {
                   bDoIdle = TRUE;
                   nIdleCount = 0;
              }
         }
         return (int)m_msg.wParam;
     }
以上就是对ATLAPP.H中的几个比较重要的类的分析,还有其他几个类的分析我将放在以后的文章中
(待续。。。)


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=201806
 

这篇关于WTL源码剖析 --- ATLAPP.H的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/894528

相关文章

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除

red5-server源码

red5-server源码:https://github.com/Red5/red5-server

TL-Tomcat中长连接的底层源码原理实现

长连接:浏览器告诉tomcat不要将请求关掉。  如果不是长连接,tomcat响应后会告诉浏览器把这个连接关掉。    tomcat中有一个缓冲区  如果发送大批量数据后 又不处理  那么会堆积缓冲区 后面的请求会越来越慢。

Windows环境利用VS2022编译 libvpx 源码教程

libvpx libvpx 是一个开源的视频编码库,由 WebM 项目开发和维护,专门用于 VP8 和 VP9 视频编码格式的编解码处理。它支持高质量的视频压缩,广泛应用于视频会议、在线教育、视频直播服务等多种场景中。libvpx 的特点包括跨平台兼容性、硬件加速支持以及灵活的接口设计,使其可以轻松集成到各种应用程序中。 libvpx 的安装和配置过程相对简单,用户可以从官方网站下载源代码