WTL源碼剖析 --- 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.