承接上篇,本文深入剖析 MFC 模組狀態的執行緒局部儲存 (TLS) 實現、擴充 DLL (Extension DLL) 鏈接機制、AFX_MANAGE_STATE 狀態切換、以及程式/DLL 退出時的清理流程。理解這些機制,是解決 MFC 多模組、多執行緒環境下資源載入失敗、CRuntimeClass 找不到、物件跨模組斷言失效等疑難雜症的鑰匙。
// 執行緒局部儲存管理器
template class CThreadLocal : public CThreadLocalObject {
TYPE* GetData(TYPE* (*pfnCreate)() = NULL); // 取得/建立執行緒資料
void SetData(TYPE* pData); // 設定執行緒資料
void DeleteData(); // 刪除執行緒資料
};
// 進程局部儲存管理器
template class CProcessLocal : public CProcessLocalObject {
TYPE* GetData(TYPE* (*pfnCreate)() = NULL); // 取得/建立進程資料
};
CThreadLocal<_AFX_THREAD_STATE> _afxThreadState; // 執行緒狀態 TLS
CProcessLocal<_AFX_BASE_MODULE_STATE> _afxBaseModuleState; // 進程模組狀態
_afxThreadState.GetData() → CThreadLocalObject::GetData
m_nSlot (TLS 索引),為 0 則呼叫 _afxThreadData->AllocSlot() 分配槽位。_afxThreadData 為 CThreadSlotData*,首次存取時在堆上建立。_afxThreadData->GetThreadValue(slot) 讀取 TLS 值。_AFX_THREAD_STATE,再 SetValue 寫入 TLS。m_pModuleState,若為 NULL → 呼叫 _afxBaseModuleState.GetData() 建立進程模組狀態 _AFX_BASE_MODULE_STATE。m_thread (模組-執行緒狀態) 同樣透過 CThreadLocal 按需建立。// 配置 TLS 槽位 (需關鍵區段同步)
int CThreadSlotData::AllocSlot() {
EnterCriticalSection(&m_sect);
// 搜尋 m_pSlotData 陣列中空閒槽,無則重新分配陣列
// 標記已用,更新 m_nRover,LeaveCriticalSection
return slotIndex;
}
// 讀取執行緒區域值
void* CThreadSlotData::GetThreadValue(int slot) {
CThreadData* pData = (CThreadData*)TlsGetValue(m_tlsIndex);
if (!pData || slot > pData->nCount) return NULL;
return pData->pData[slot];
}
// 寫入執行緒區域值
void CThreadSlotData::SetValue(int slot, void* pValue) {
CThreadData* pData = (CThreadData*)TlsGetValue(m_tlsIndex);
if (!pData || (slot > pData->nCount && pValue)) {
if (!pData) { /* 首次:建立 CThreadData,加入 m_list,TlsSetValue */ }
// 重新分配 pData->pData 陣列容納新槽位
TlsSetValue(m_tlsIndex, pData);
}
pData->pData[slot] = pValue;
}
#ifdef _AFXDLL
struct AFX_MAINTAIN_STATE {
AFX_MODULE_STATE* m_pPrevModuleState;
AFX_MAINTAIN_STATE(AFX_MODULE_STATE* pNewState) {
m_pPrevModuleState = AfxGetModuleState(); // 保存舊狀態
AfxSetModuleState(pNewState); // 切換新狀態
}
~AFX_MAINTAIN_STATE() {
AfxSetModuleState(m_pPrevModuleState); // 還原舊狀態
}
};
#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE _ctlState(p)
#else
#define AFX_MANAGE_STATE(p) // 靜態連結無需切換
#endif
| 場景 | 宏呼叫 | 參數來源 |
|---|---|---|
| DLL 匯出函式 | AFX_MANAGE_STATE(AfxGetStaticModuleState()) | DLL 靜態 afxModuleState |
| 視窗程序/回調 | AFX_MANAGE_STATE(_afxBaseModuleState.GetData()) | 應用程式/規則 DLL 進程模組狀態 |
| OLE 介面 | 特殊機制 (COleObjectFactory 內建) | — |
核心規則:每個進入點函式第一行必須呼叫,確保資源載入、類別查找、物件建立使用正確模組狀態。
struct AFX_EXTENSION_MODULE {
BOOL bInitialized;
HMODULE hModule; // DLL 模組控制代碼
HMODULE hResource; // 資源控制代碼
CRuntimeClass* pFirstSharedClass; // 匯出的 CRuntimeClass 鏈表頭
COleObjectFactory* pFirstSharedFactory; // 匯出的 OLE Factory 鏈表頭
};
AfxInitExtensionModule(extensionDLL, hInstance)
hModule/hResource。m_classList 取出本 DLL 初始化的 AFX_CLASSINIT 註冊類別,移至 pFirstSharedClass。pFirstSharedFactory。m_classInit,避免重複。InitMyDll() 中建立 CDynLinkLibrary
extern "C" void WINAPI InitMyDll() {
CDynLinkLibrary* pDLL = new CDynLinkLibrary(extensionDLL, TRUE);
// TRUE = 系統 DLL (MFC 核心 DLL 也用此標記)
}
class CDynLinkLibrary : public CCmdTarget {
HMODULE m_hModule, m_hResource;
CTypedSimpleList m_classList;
CTypedSimpleList m_factoryList;
BOOL m_bSystem; // 系統 DLL 標記
CDynLinkLibrary* m_pNextDLL; // 單向鏈表
CDynLinkLibrary(AFX_EXTENSION_MODULE& state, BOOL bSystem) {
// 複製 hModule/hResource
// 將 state.pFirstSharedClass/Factory 插入自有鏈表頭
// 加入當前模組狀態的 m_libraryList 表頭 (需鎖定 CRIT_DYNLINKLIST)
}
};
應用程式/規則 DLL 模組狀態 (AFX_MODULE_STATE)
│
├─ m_hCurrentInstanceHandle / m_hCurrentResourceHandle
├─ m_classList (本模組類別)
├─ m_factoryList (本模組 Factory)
└─ m_libraryList → CDynLinkLibrary (核心 MFC DLL)
│
├─ m_hModule / m_hResource
├─ m_classList (核心類別: CWnd, CDocument...)
├─ m_factoryList
└─ m_pNextDLL → CDynLinkLibrary (擴充 DLL A)
│
├─ m_classList (DLL A 匯出類別)
└─ m_pNextDLL → CDynLinkLibrary (擴充 DLL B)
...
// AfxFindResourceHandle(lpszName, lpszType)
1. 搜尋主模組 (非系統) → 成功返回
2. 遍歷 m_libraryList 搜尋非系統 CDynLinkLibrary → 成功返回
3. 檢查主模組語言資源 → 成功返回
4. 若主模組為系統 → 搜尋主模組
5. 遍歷 m_libraryList 搜尋系統 CDynLinkLibrary → 成功返回
6. 返回 AfxGetResourceHandle() (預設應用程式資源)
// CRuntimeClass::Load (序列化時)
1. 遍歷主模組 m_classList
2. 遍歷 m_libraryList 每個 CDynLinkLibrary 的 m_classList
// 找到即返回,支援跨 DLL 多態序列化
coreDLL (AFX_EXTENSION_MODULE)。DllMain 中 AfxInitExtensionModule(coreDLL) + new CDynLinkLibrary(coreDLL) 鏈接到當前模組狀態。_afxBaseModuleState 即為動態連結應用程式的模組狀態。afxModuleState (_AFX_DLL_MODULE_STATE) 與 CWinApp 衍生物件。DllMain (在 DllModul.cpp):
AfxWinInit 保存模組/資源控制代碼到 afxModuleState。CDynLinkLibrary 將 核心 MFC DLL (coreDLL) 鏈接到 afxModuleState。CWinApp::InitInstance 初始化 DLL 內部類別/資源。CDynLinkLibrary 將 本 DLL 自身擴充模組 (controlDLL) 鏈接到 afxModuleState。| 場景 | 清理進入點 | 關鍵動作 |
|---|---|---|
| 動態連結應用程式結束 | 核心 MFC DLL 的 DllMain (DLL_PROCESS_DETACH) | AfxTermExtensionModule(coreDLL)、AfxTermLocalData(NULL, TRUE)、AfxTlsRelease |
| 靜態連結應用程式結束 | 靜態物件 AFX_TERM_APP_STATE 解構 | AfxTermLocalData(NULL, TRUE) |
| 動態連結規則 DLL 卸載 | 規則 DLL 的 DllMain/RawDllMain | 清除臨時映射、AfxWinTerm、AfxTermExtensionModule(controlDLL, TRUE)、切換回前一模組狀態 |
| 靜態連結 DLL 卸載 | DllMain + 靜態物件 _AFX_TERM_DLL_STATE 解構 | 清除臨時映射、AfxWinTerm、AfxTermLocalData(hInst, TRUE)、AfxCriticalTerm、AfxTlsRelease |
| 執行緒結束 (AfxBeginThread) | AfxTermThread(hInst) | 銷毀臨時物件、清理執行緒局部資料 (若 hInst=NULL 則全清,否則針對該 DLL) |
AfxTermExtensionModule(state, bAll):bAll=TRUE 清理 m_libraryList 所有 CDynLinkLibrary;FALSE 僅清理本 DLL 節點 + AfxTermLocalData。AfxTermLocalData(hInst, bAll):刪除 TLS 槽位對應的物件,釋放堆記憶體。AfxTlsRelease:遞減 _afxThreadData 參考計數,歸零時解構 CThreadSlotData。class CHandleMap {
CMapPtrToPtr m_permanentMap; // 永久映射: MFC物件* → HANDLE
CMapPtrToPtr m_temporaryMap; // 臨時映射: HANDLE → MFC物件* (FromHandle建立)
};
class _AFX_MODULE_THREAD_STATE {
CHandleMap* m_pmapHWND; // CWnd ↔ HWND
CHandleMap* m_pmapHDC; // CDC ↔ HDC
CHandleMap* m_pmapHMENU; // CMenu ↔ HMENU
CHandleMap* m_pmapHGDIOBJ; // CGdiObject ↔ HGDIOBJ
CHandleMap* m_pmapHIMAGELIST;// CImageList ↔ HIMAGELIST
CHandleMap* m_pmapHSOCKET; // CAsyncSocket ↔ SOCKET
// ...
};
Attach(h):永久映射,MFC 物件擁有 HANDLE 生命週期。Detach():移除永久映射,不刪除 HANDLE。FromHandle(h):查永久 → 無則建臨時映射並返回臨時物件。FromHandlePermanent(h):僅查永久映射。AssertValid():驗證 this 在當前執行緒/模組的永久映射中存在對應 HANDLE。m_temporaryMap,由執行緒 Idle 處理刪除。m_nTempMapLock 同步防止並發修改。FromHandle。MFC 模組狀態機制構建了多模組、多執行緒隔離與協作的基礎設施:
CThreadLocal/CProcessLocal 實現執行緒/進程隔離的狀態儲存。CDynLinkLibrary 鏈表,統一資源、類別、Factory 查找邏輯。AFX_MANAGE_STATE 確保進入點自動切換/還原,消除人為錯誤。CHandleMap 綁定執行緒與模組,從根源防止跨執行緒物件誤用。掌握此機制,能從容應對「資源找不到」、「類別無法動態建立」、「跨 DLL 物件斷言失敗」等經典 MFC 難題。