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 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 中的幾個比較重要的類的分析,還有其他幾個類的分析我將放在以後的文章中

(待續。。。)

發佈了45 篇原創文章 · 獲贊 3 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章