WTL源碼剖析 --- ATLAPP.H

作者:姜江
QQ:457283
E-mail:[email protected]

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;
}
那麼你可能會問WTLWinMain函數再哪裏?如果你通過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
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章