MFC的模組狀態:從AfxGetApp()和AFX_MANAGE_STATE()看MFC的模組狀態
1. Introduction
當我們在用MFC程式設計的時候,我們經常用到AfxGetApp()來獲得當前的CWinApp的Instance。看看MFC的原始碼中AfxGetApp()的實現,你會發現AfxGetApp()的實現並不像一般情況下面那樣直接:
_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp() { return afxCurrentWinApp; } #define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp |
AfxGetApp()呼叫的是AfxGetModuleState(),該函式返回一個AFX_MODULE_STATE的指標,其中的一個成員儲存著當前的CWinApp的指標。可AfxGetModuleState()的作用又是什麼呢?
此外,當我們在開發MFC DLL程式的時候,我們會在每個輸出的DLL函式前面加上一句AFX_MANAGE_STATE:
void SomeMFCDllFunction() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) … |
AFX_MANAGE_STATE又是起什麼作用呢?從字面上看來,它是Manage某種State,而AfxGetStaticModuleState又是獲得State的,那麼State究竟是什麼呢?
在MFC中,States用來儲存某種相關的狀態資訊,分為下面幾類:
1. Process State,和某個單獨的程式繫結起來
2. Thread State,和某個單獨的執行緒繫結
3. Module State,和Module相關
前兩種State和一般的全域性變數十分類似,只是根據需求的不同被繫結於不同的程式/執行緒,如多執行緒支援等。而Module State本身比較特別,Module State根據情況的不同,可以是全域性,執行緒,或者程式相關的State,並且可以根據要求快速切換。
2. Process State
常見的Process State有:
1. _AFX_WIN_STATE
2. _AFX_DB_STATE
3. _AFX_DEBUG_STATE
4. _AFX_SOCK_STATE
5. ……
從字面上面可以很容易猜出這些狀態的用處。
MFC通過下面的巨集來定義Process State:
#define PROCESS_LOCAL(class_name, ident_name) / AFX_COMDAT CProcessLocal<class_name> ident_name; #define EXTERN_PROCESS_LOCAL(class_name, ident_name) / extern CProcessLocal<class_name> ident_name; |
PROCESS_LOCAL用CProcessLocal模板類定義了一個CProcessLocal<class_name>的一個例項作為狀態變數,而EXTERN_PROCESS_LOCAL則使在標頭檔案中宣告此狀態變數。CProcessLocal的定義如下:
class AFX_NOVTABLE CProcessLocalObject { public: // Attributes CNoTrackObject* GetData(CNoTrackObject* (AFXAPI* pfnCreateObject)()); // Implementation CNoTrackObject* volatile m_pObject; ~CProcessLocalObject(); }; template<class TYPE> class CProcessLocal : public CProcessLocalObject { // Attributes public: AFX_INLINE TYPE* GetData() { TYPE* pData = (TYPE*)CProcessLocalObject::GetData(&CreateObject); ENSURE(pData != NULL); return pData; } AFX_INLINE TYPE* GetDataNA() { return (TYPE*)m_pObject; } AFX_INLINE operator TYPE*() { return GetData(); } AFX_INLINE TYPE* operator->() { return GetData(); } // Implementation public: static CNoTrackObject* AFXAPI CreateObject() { return new TYPE; } }; |
CProcessLocal的作用只是一個Wrapper,Hold一個TYPE*的指標,一旦使用者呼叫GetData來獲得這個指標,GetData會首先判斷該指標是否為空,如果為空,則建立一個新的例項儲存起來,否則返回已有的指標。前提條件是,TYPE必須從CNoTrackObject繼承。任何從CNoTrackObject繼承的類都擁有自己的new/delete,這樣此物件便不會被Debug的記憶體分配系統所跟蹤而誤判為Leak。
CNoTrackObject* CProcessLocalObject::GetData( CNoTrackObject* (AFXAPI* pfnCreateObject)()) { if (m_pObject == NULL) { AfxLockGlobals(CRIT_PROCESSLOCAL); TRY { if (m_pObject == NULL) m_pObject = (*pfnCreateObject)(); } CATCH_ALL(e) { AfxUnlockGlobals(CRIT_PROCESSLOCAL); THROW_LAST(); } END_CATCH_ALL AfxUnlockGlobals(CRIT_PROCESSLOCAL); } return m_pObject; } |
3. Thread State
和Process State類似,Thread State和某個執行緒繫結起來,Thread State有:
1. _AFX_THREAD_STATE
2. _AFXCTL_AMBIENT_CACHE
同樣的,Thread State是被THREAD_LOCAL和EXTERN_THREAD_LOCAL定義,也有CThreadLocal和CThreadLocalObject來Hold住Thread State的指標。CThreadLocal和CProcessLocal的實現方式不太一樣,CThreadLocal利用TLS(Thread Local Storage)來儲存指標,而不是用成員變數。簡單來說,Thread Local Storage是Windows支援的功能,可以在任意執行緒中儲存多個DWORD資料,每個這樣的DWORD資料所佔的位置稱之為Slot,分配資料需要分配一個Slot,獲得和修改資料CThreadLocalObject::GetData的實現如下:
CNoTrackObject* CThreadLocalObject::GetData( CNoTrackObject* (AFXAPI* pfnCreateObject)()) { ENSURE(pfnCreateObject); if (m_nSlot == 0) { if (_afxThreadData == NULL) { _afxThreadData = new(__afxThreadData) CThreadSlotData; ENSURE(_afxThreadData != NULL); } m_nSlot = _afxThreadData->AllocSlot(); ENSURE(m_nSlot != 0); } CNoTrackObject* pValue = static_cast<CNoTrackObject*>(_afxThreadData->GetThreadValue(m_nSlot)); if (pValue == NULL) { // allocate zero-init object pValue = (*pfnCreateObject)(); // set tls data to newly created object _afxThreadData->SetValue(m_nSlot, pValue); ASSERT(_afxThreadData->GetThreadValue(m_nSlot) == pValue); } return pValue; } |
CThreadLocalObject::GetData首先判斷m_nSlot,如果m_nSlot == 0,說明該Thread State未曾分配,GetData函式將會使用_afxThreadData->AllocSlot函式分配一個新的TLS的Slot,儲存在m_nSlot之中,然後呼叫GetThreadValue檢查pValue是否為NULL,如果是,則建立一個新的物件然後呼叫SetValue把pValue設定到該Slot之中。_afxThreadData的型別為CThreadSlotData,是對TLS API的一個簡單的封裝。
_AFX_THREAD_STATE是一個很常用的Thread State,每個Thread,都會有自己的一份_AFX_THREAD_STATE。MFC提供了一個函式AfxGetThreadState來獲得當前程式的Thread State,如果當前的執行緒還沒有Thread State,該函式會建立一個新的Thread State。
_AFX_THREAD_STATE* AFXAPI AfxGetThreadState() { _AFX_THREAD_STATE *pState =_afxThreadState.GetData(); ENSURE(pState != NULL); return pState; } |
_AFX_THREAD_STATE中儲存著下列資訊:
1. 當前的m_pModuleState,每個執行緒都知道它當前的Module State,這個資訊被用來獲得當前的Module State,AfxGetModuleState正是這麼做的:
AFX_MODULE_STATE* AFXAPI AfxGetModuleState() { _AFX_THREAD_STATE* pState = _afxThreadState; ENSURE(pState); AFX_MODULE_STATE* pResult; if (pState->m_pModuleState != NULL) { // thread state's module state serves as override pResult = pState->m_pModuleState; } else { // otherwise, use global app state pResult = _afxBaseModuleState.GetData(); } ENSURE(pResult != NULL); return pResult; } |
2. 之前的m_pModuleState,用來儲存之前的Module State,用於Module State切換,可參考AFX_MANAGE_STATE
3. 其他資訊,具體可以參考_AFX_THREAD_STATE的定義
4. Module State
Module State儲存著和Module相關的狀態資訊。Module是Windows的術語,代表任何一個可執行的程式碼檔案, EXE和DLL都是Module的一種。Module State有下面幾種:
1. AFX_MODULE_STATE,儲存MODULE的資訊,是_AFX_BASE_MODULE_STATE和_AFX_DLL_MODULE_STATE的基類
2. _AFX_BASE_MODULE_STATE,儲存MFC Module的狀態資訊,沒有定義其他的成員
3. _AFX_DLL_MODULE_STATE,儲存DLL的狀態資訊,沒有定義其他的成員
4. AFX_MODULE_THREAD_STATE,儲存主執行緒的有關狀態資訊,雖然AFX_MODULE_THREAD_STATE是儲存的執行緒的狀態資訊,但是它只儲存Module的主執行緒的狀態資訊,所以可以看作是Module State的一種。
這些Module State儲存了MFC中的大量重要資訊:
1. CWinApp指標
2. 例項控制程式碼
3. 資源Module的控制程式碼
4. 控制程式碼表
5. OLE相關資訊
6. 視窗過程
7. Activation Context
8. ……
4.1 AFX_MODULE_STATE
AFX_MODULE_STATE的定義如下:
// AFX_MODULE_STATE (global data for a module) class AFX_MODULE_STATE : public CNoTrackObject { public: #ifdef _AFXDLL AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion, BOOL bSystem = FALSE); #else explicit AFX_MODULE_STATE(BOOL bDLL); #endif ~AFX_MODULE_STATE(); CWinApp* m_pCurrentWinApp; HINSTANCE m_hCurrentInstanceHandle; HINSTANCE m_hCurrentResourceHandle; LPCTSTR m_lpszCurrentAppName; // …… 其他成員,從略 }; |
可以看到:
1. AFX_MODULE_STATE從CNoTrackObject繼承。CNoTrackObject定義了自己的new/delete保證自己不會被各種除錯版本的new/delete來Track,以免自己被錯誤的當作Leak。
2. AFX_MODULE_STATE在DLL和非DLL(也就是EXE)的情況下具有不同的建構函式(和成員)
3. AFX_MODULE_STATE在成員中儲存了一些和Module相關的重要資訊
實際上,AFX_MODULE_STATE並沒有被直接使用,而是作為_AFX_BASE_MODULE_STATE和_AFX_DLL_MODULE_STATE的基類:
_AFX_BASE_MODULE_STATE被用於Module,其定義如下:
class _AFX_BASE_MODULE_STATE : public AFX_MODULE_STATE { public: #ifdef _AFXDLL _AFX_BASE_MODULE_STATE() : AFX_MODULE_STATE(TRUE, AfxWndProcBase, _MFC_VER) #else _AFX_BASE_MODULE_STATE() : AFX_MODULE_STATE(TRUE) #endif { } }; PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState) |
_AFX_DLL_MODULE_STATE和_AFX_BASE_MODULE_STATE類似,只是僅用於DLL:
class _AFX_DLL_MODULE_STATE : public AFX_MODULE_STATE { public: _AFX_DLL_MODULE_STATE() : AFX_MODULE_STATE(TRUE, AfxWndProcDllStatic, _MFC_VER) { } }; static _AFX_DLL_MODULE_STATE afxModuleState; |
這兩個class都沒有定義額外的成員,比較簡單,只是傳入到基類AFX_MODULE_STATE的引數不同。此外,他們定義的方式不太一樣,前者使用的是PROCESS_LOCAL巨集,定義了一個變數_afxBaseModuleState。後者只是簡單的定義了一個static變數afxModuleState。
下面這些函式可以用來獲得Module的State:
1. AfxGetModuleState
AfxGetModuleState首先獲得_afxThreadState的m_pModuleState,如果當前的Thread State的m_pModuleState返回NULL,說明當前的Thread State沒有正確的初始化(通常的原因是建立執行緒的時候呼叫的是CreateThread函式而非AfxBeginThread),則使用_afxBaseModuleState。
AFX_MODULE_STATE* AFXAPI AfxGetModuleState() { _AFX_THREAD_STATE* pState = _afxThreadState; ENSURE(pState); AFX_MODULE_STATE* pResult; if (pState->m_pModuleState != NULL) { // thread state's module state serves as override pResult = pState->m_pModuleState; } else { // otherwise, use global app state pResult = _afxBaseModuleState.GetData(); } ENSURE(pResult != NULL); return pResult; } |
_afxBaseModuleState是用PROCESS_LOCAL定義的:
PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState) |
它代表整個MFC Module的State。當你的程式是動態連結到MFC DLL的時候,該State只有一份。如果你的程式是靜態連結到MFC的話,有幾個模組(EXE/DLL)靜態連結到MFC,MFC的程式碼就有幾份,那麼_afxBaseModuleState也就有幾份。
2. AfxGetStaticModuleState
AfxGetStaticModuleState在不同的Project下面有著不同的行為:在DLL專案中,AfxGetSaticModuleState返回afxModuleState,也就是定義好的_AFX_DLL_MODULE_STATE,而在非DLL專案中,AfxGetStaticModuleState直接呼叫AfxGetModuleState。可以看到,在DLL的情況下,必須使用AfxGetStaticModuleState才可以獲得DLL本身的Module State。
#ifdef _AFXDLL static _AFX_DLL_MODULE_STATE afxModuleState; AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState() { AFX_MODULE_STATE* pModuleState = &afxModuleState; return pModuleState; } #else AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState() { AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); return pModuleState; } #endif |
3. AfxGetAppModuleState
AfxGetAppModuleState是最簡單的,直接返回_afxBaseModuleState:
AFX_MODULE_STATE* AFXAPI AfxGetAppModuleState() { return _afxBaseModuleState.GetData(); } |
從上面的討論可以看出,當前處於那個MFC Module的狀態之中,返回的就是那個MFC Module所相關聯的CWinApp物件。如果你有多個Module都是動態連結到MFC DLL的話,那麼AfxGetAppModuleState返回的總是同一個CWinApp。
5. AFX_MANAGE_STATE
AFX_MANAGE_STATE的作用切換到指定的Module State,當出了作用域的時候將Module State恢復到原來的值。是在不同的Module State之中切換,原因有2:
1. 在不同的MFC DLL和MFC EXE的Module State之間切換,保持正確的AFX_MODULE_STATE,最常見的問題是在DLL輸出的函式之中無法獲得DLL本身相關的資源,這就是沒有正確維護Module State的原因造成的,因為當前Resource DLL的控制程式碼就儲存在Module State之中。
2. 切換Activation Context,不同的Module必然有著不同的Activation Context,需要切換。這是屬於Side By Side的內容,以後我會專門寫一篇文章來講述Side By Side和manifest的相關資訊。
一般的用法如下:
void SomeMFCDllFunction() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) … |
注意這裡使用的是AfxGetStaticModuleState,而非AfxGetModuleState。原因是在DLL專案中,AfxGetStaticModuleState返回的是DLL本身的Module State,而AfxGetModuleState則是返回當前執行緒相關的Module State,由於一般DLL輸出的函式是被其他Module呼叫,那麼大部分情況下當前執行緒的Module State都是錯誤的,所以必須得使用DLL本身的Module State。
AFX_MANAGE_STATE只是一個巨集,如下:
struct AFX_MAINTAIN_STATE2 { explicit AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pModuleState) throw(); ~AFX_MAINTAIN_STATE2(); protected: #ifdef _AFXDLL AFX_MODULE_STATE* m_pPrevModuleState; _AFX_THREAD_STATE* m_pThreadState; #endif ULONG_PTR m_ulActCtxCookie; BOOL m_bValidActCtxCookie; }; #define AFX_MANAGE_STATE_NO_INIT_MANAGED(p) AFX_MAINTAIN_STATE2 _ctlState(p); #define AFX_MANAGE_STATE(p) _AfxInitManaged(); AFX_MANAGE_STATE_NO_INIT_MANAGED(p) |
可以看到AFX_MANAGE_STATE宣告瞭一個棧上的區域性變數_ctrlState,型別為AFX_MAINTAIN_STATE2。這是一個很常用的Pattern,AFX_MAINTAIN_STATE2在建構函式的時候會將當前的Module State切換為引數中指定的Module State:
AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pNewState) throw() { #ifdef _AFXDLL m_pThreadState = _afxThreadState.GetData(); ASSERT(m_pThreadState); if(m_pThreadState) { m_pPrevModuleState = m_pThreadState->m_pModuleState; m_pThreadState->m_pModuleState = pNewState; } else { // This is a very bad state; we have no good way to report the error at this moment // since exceptions from here are not expected m_pPrevModuleState=NULL; m_pThreadState=NULL; } #endif if (AfxGetAmbientActCtx() && pNewState->m_hActCtx != INVALID_HANDLE_VALUE) { m_bValidActCtxCookie = AfxActivateActCtx(pNewState->m_hActCtx, &m_ulActCtxCookie); } else { m_bValidActCtxCookie = FALSE; } } |
然後在解構函式的時候將其恢復回來:
// AFX_MAINTAIN_STATE2 functions _AFXWIN_INLINE AFX_MAINTAIN_STATE2::~AFX_MAINTAIN_STATE2() { #ifdef _AFXDLL // Not a good place to report errors here, so just be safe if(m_pThreadState) { m_pThreadState->m_pModuleState = m_pPrevModuleState; } #endif if (m_bValidActCtxCookie) { BOOL bRet; bRet = AfxDeactivateActCtx(0, m_ulActCtxCookie); ASSERT(bRet == TRUE); } } |
可以看到,AFX_MAINTAIN_STATE2將當前_afxThreadState在m_pThreadState中存起來,然後將所指向的Module State儲存在m_pPrevModuleState中。在解構函式中,則使用儲存起來的m_pPrevModuleState恢復到m_pThreadState的Module State。除了儲存恢復Module state之外,AFX_MAINTAIN_STATE2也會在切換Activation Context。這個Activation Context被用來查詢Side By Side Assemblies,我以後會專門寫一篇文章講述Side By Side和Manifest相關的一些資訊。這次就寫到這裡。
相關文章
- saltstack:常用狀態模組
- Vuex 單狀態庫 與 多模組狀態庫Vue
- MFC在狀態列中使用進度條控制元件控制元件
- 有狀態和無狀態的區別
- 從狀態模式看 JavaScript 與 Java模式JavaScript
- 利用Dectorator分模組儲存Vuex狀態(下)Vue
- 利用Dectorator分模組儲存Vuex狀態(上)Vue
- Vuex 模組化實現待辦事項的狀態管理Vue
- Spring Bean Scope 有狀態的Bean和無狀態的BeanSpringBean
- Oracle資料庫的靜默狀態和掛起狀態Oracle資料庫
- 【C++】【MFC】模態和非模態對話方塊C++
- 工作流從無狀態切換到有狀態的好處
- 檢視看防火牆狀態防火牆
- 狀態模式的理解和示例模式
- 更新TableView和CollectionView的狀態View
- 程式的建立和程式的狀態
- 關於有狀態和無狀態會話bean的解釋 (轉)會話Bean
- 從任務中心看狀態機功能元件設計元件
- SAP Fiori和WebClient UI的有狀態和無狀態行為設計原理WebclientUI
- 淺談前端的狀態管理,以及anguar的狀態管理庫前端
- 行為和狀態的關係
- 配置CACTI監控MySQL資料庫狀態(3)配置apache模組MySql資料庫Apache
- AI模組(有限狀態機、行為樹)-應用在cocos中AI
- ⚠️Flutter的 狀態管理⚠️Flutter
- React的狀態管理React
- HTTP的狀態碼HTTP
- 日誌的狀態
- mysql 鎖狀態的一些狀態資訊記錄MySql
- 前端狀態管理與有限狀態機前端
- mysql檢視主從同步狀態的方法MySql主從同步
- [React]屬性和狀態React
- React 狀態管理:狀態與生命週期React
- 狀態模式模式
- 狀態機
- 狀態碼
- 狀態管理
- 5種狀況下的HTTP狀態碼HTTP
- JavaScript 的狀態容器 ReduxJavaScriptRedux