WTL atlApp.h

志不强者智不达,言不信者行不果。这篇文章主要讲述WTL atlApp.h相关的知识,希望能为你提供帮助。
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 dont 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 dont 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, dont 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中的几个比较重要的类的分析,还有其他几个类的分析我将放在以后的文章中
【WTL atlApp.h】 (待续。。。)

    推荐阅读