在 MFC 架構中,「狀態管理」是確保多模組、多線程環境下資源正確隔離與存取的基石。MFC 定義了模組狀態、進程狀態、線程狀態三大核心狀態類別,它們協同工作,支撐著句柄映射、資源載入、OLE 物件管理等關鍵功能。本文將深入剖析這些狀態的資料結構、層級關係,以及 MFC 基於 TLS(Thread Local Storage)實現的線程私有資料機制。
MFC 將運行時環境劃分為三個維度的狀態:
CWinApp/CWinThread 指標、OLE 參考計數、Windows 物件與 MFC 物件的映射表等。這些狀態並非孤立存在。例如,MFC 的句柄映射(HWND 對應 CWnd*)屬於「模組-線程」狀態,即特定模組在特定線程下的私有資料。圖 9-1 展示了單模組應用程式的狀態結構:線程物件持有 m_pModuleState 指針指向當前模組狀態(AFX_MODULE_STATE 結構)。

當執行流進入某個特定模組(如呼叫 DLL 匯出函數、OLE/COM 介面函數、視窗程序)時,必須確保當前線程使用的模組狀態指向該模組。MFC 要求每個模組的所有「入口點」負責執行狀態切換,核心宏為:
AFX_MANAGE_STATE(AfxGetStaticModuleState())
此宏在入口點建立一個臨時物件,在建構時將線程的 m_pModuleState 切換至目標模組狀態,在解構時恢復原狀態。圖 9-2 示意了多模組狀態間的切換過程,m_pModuleState 始終指向當前活躍模組的狀態。

MFC 使用一系列類別封裝狀態資料,其繼承體系如圖 9-3 所示:
CNoTrackObject:根基類,重載 new/delete 使用 LocalAlloc/LocalFree 實現底層記憶體分配,避免記憶體追蹤干擾。AFX_MODULE_STATE:模組狀態核心類,包含模組資訊、資源句柄、執行期類別列表、OLE 工廠列表、視窗類註冊資料、DAO/OLE/OCC 支援資料,以及 線程局部儲存的模組-線程狀態(THREAD_LOCAL(AFX_MODULE_THREAD_STATE, m_thread))。_AFX_BASE_MODULE_STATE:進程狀態類,繼承自 AFX_MODULE_STATE,無新增資料成員,專用於 EXE 模組。_AFX_THREAD_STATE:線程狀態類,繼承自 CNoTrackObject,持有 m_pModuleState、m_pPrevModuleState 指向當前/前一模組狀態,另含視窗建立、命令路由、ToolTip、Socket、OLE 控制項停駐視窗等線程私有資料。AFX_MODULE_THREAD_STATE:模組-線程狀態類,繼承自 CNoTrackObject,存放 CWinThread*、框架視窗列表、臨時映射鎖、句柄映射表(HWND/HMENU/HDC...)、new 處理器、Socket 映射等「模組與線程雙重私有」的資料。圖 9-4 清楚地展示了這些物件的指向關係:每個線程有一個 _AFX_THREAD_STATE,其 m_pModuleState 指向當前模組的 AFX_MODULE_STATE;每個模組狀態內部又包含一個線程局部的 AFX_MODULE_THREAD_STATE 變數 m_thread。

此類別成員涵蓋:
m_hCurrentInstanceHandle、m_hCurrentResourceHandle、m_lpszCurrentAppName、m_bDLL、m_bSystem。m_pCurrentWinApp 指向 CWinApp 物件。m_classList 串聯所有 CRuntimeClass,支援動態建立。m_factoryList(類別工廠)、m_nObjectCount(鎖定計數)、m_bUserCtrl(使用者控制旗標)。m_szUnregisterList 記錄待註銷類別名。m_libraryList 管理 CDynLinkLibrary 物件,m_appLangDLL 指向本地化資源 DLL。THREAD_LOCAL 宏宣告 m_thread,實現每線程一份拷貝。關鍵成員分類:
m_pModuleState、m_pPrevModuleState。m_pWndInit、m_hWndInit、m_bDlgCreate、Hook 句柄等。m_pRoutingView、m_pRoutingFrame、m_lastSentMsg、m_hTrackingWindow/Menu。m_pToolTip、m_pLastHit、m_lastInfo 等。m_pWndPark、m_nCtrlRef、m_bNeedTerm。核心資料包括:
m_pCurrentWinThread:當前 CWinThread 指標。m_frameList:該模組在該線程建立的所有 CFrameWnd 列表(建構時加入,解構時移除)。m_nTempMapLock、m_pmapHWND、m_pmapHMENU...:臨時/永久句柄映射表與鎖計數,防止併發修改。m_pfnNewHandler:線程專屬 new 失敗處理器。m_mapSocketHandle、m_mapDeadSockets、m_listSocketNotifications。MFC 未直接使用 Win32 TlsAlloc/TlsGetValue,而是建構了一層封裝,以支援「模組-線程」雙重私有語意並簡化跨模組管理。
CThreadSlotData 類別管理進程內所有線程的局部儲存空間。全域指標 _afxThreadData 首次使用時建立。其核心欄位:
m_tlsIndex:調用 TlsAlloc 獲得的系統 TLS 索引。m_pSlotData:CSlotData 陣列,每個槽位對應一個線程局部變數,記錄佔用旗標與擁有模組句柄。m_list:CThreadData 串列,每個線程一個節點,節點內 pData 陣列存放該線程所有槽位的資料指標。m_sect:關鍵區,保護全域槽位分配/釋放的執行緒安全。機制流程(圖 9-5、9-6):
_afxThreadData 初始化,TlsAlloc 取得 m_tlsIndex。DllMain(DLL_THREAD_ATTACH) 或線程啟動例程中為該線程配置 CThreadData 結構,掛入 m_list,並將指標存入 TLS 索引對應位置。THREAD_LOCAL 宏)時,MFC 分配一個槽號(AllocSlot),記錄於變數對應的 CThreadLocal 物件的 m_nSlot 中。TlsGetValue(m_tlsIndex) → 取得線程的 CThreadData* → 以槽號為索引讀取 pData[slot] → 獲得資料指標 → 取得實際資料。這是一個「三級間接尋址」過程,雖較原生 TLS 多兩層,但提供了跨模組的統一管理、延遲初始化、型別安全(透過 CThreadLocal 模板)等優勢。

THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState) 宏展開為全域 CThreadLocal<_AFX_THREAD_STATE> _afxThreadState。CThreadLocal 繼承自 CThreadLocalObject,首次存取時(GetData())才分配槽號、在線程局部空間 new 出 _AFX_THREAD_STATE 物件。這實現了「首次使用才建立」,加快 DLL 載入速度。
使用 PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState) 宣告。CProcessLocal 模板在 Win32s 下透過堆積記憶體模擬進程私有資料;在 Win32 下 DLL 本身已有進程私有資料段,故 PROCESS_LOCAL 退化為簡單封裝。_AFX_BASE_MODULE_STATE 建構子傳入 TRUE 標識為 DLL 型模組狀態(實為 EXE 主模組),版本資訊 _MFC_VER、視窗程序 AfxWndProcBase。
全域僅宣告指標/模板物件:
CThreadSlotData* _afxThreadData; // 僅指標
THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState) // 模板實例
PROCESS_LOCAL(_AFX_BASE_MODULE_STATE, _afxBaseModuleState) // 模板實例
真正的 CThreadSlotData、_AFX_THREAD_STATE、_AFX_BASE_MODULE_STATE 及模組內部的 AFX_MODULE_THREAD_STATE 均在**首次被存取時**才動態建立。此設計顯著減少 DLL 載入時的初始化開銷,符合 MFC「按需付費」原則。
下一節將詳細探討 MFC 如何利用上述結構實現模組狀態自動切換(AFX_MANAGE_STATE 內部機制)、擴充 DLL 的初始化流程,以及句柄映射表的具體運作邏輯。