首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

MFC 核心機制深度解析:模組、進程與線程狀態管理(上)

     編輯:關於VC++

在 MFC 架構中,「狀態管理」是確保多模組、多線程環境下資源正確隔離與存取的基石。MFC 定義了模組狀態、進程狀態、線程狀態三大核心狀態類別,它們協同工作,支撐著句柄映射、資源載入、OLE 物件管理等關鍵功能。本文將深入剖析這些狀態的資料結構、層級關係,以及 MFC 基於 TLS(Thread Local Storage)實現的線程私有資料機制。

一、 MFC 三大狀態體系概覽

MFC 將運行時環境劃分為三個維度的狀態:

  • 模組狀態:對應一個可執行檔或使用 MFC 的 DLL(如 OLE 控制項)。包含資源實例句柄、CWinApp/CWinThread 指標、OLE 參考計數、Windows 物件與 MFC 物件的映射表等。
  • 進程狀態:代表一個獨立執行的 MFC 應用程式模組的狀態,本質上是模組狀態的特化。
  • 線程狀態:每個執行緒擁有獨立的狀態資訊,包含指向當前/前一模組狀態的指標、視窗建立輔助變數、命令路由資訊、ToolTip 狀態、OLE 相關變數等。

這些狀態並非孤立存在。例如,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。

四、 核心狀態類別資料結構詳解

1. AFX_MODULE_STATE — 模組全域資料倉庫

此類別成員涵蓋:

  • 模組識別:m_hCurrentInstanceHandle、m_hCurrentResourceHandle、m_lpszCurrentAppName、m_bDLL、m_bSystem。
  • 應用程式主體:m_pCurrentWinApp 指向 CWinApp 物件。
  • 執行期類別資訊:m_classList 串聯所有 CRuntimeClass,支援動態建立。
  • OLE 支援:m_factoryList(類別工廠)、m_nObjectCount(鎖定計數)、m_bUserCtrl(使用者控制旗標)。
  • 視窗類註冊:m_szUnregisterList 記錄待註銷類別名。
  • DLL 資源鏈:m_libraryList 管理 CDynLinkLibrary 物件,m_appLangDLL 指向本地化資源 DLL。
  • 模組-線程狀態:透過 THREAD_LOCAL 宏宣告 m_thread,實現每線程一份拷貝。

2. _AFX_THREAD_STATE — 線程私有控制塊

關鍵成員分類:

  • 模組狀態指標:m_pModuleState、m_pPrevModuleState。
  • 視窗建立輔助:m_pWndInit、m_hWndInit、m_bDlgCreate、Hook 句柄等。
  • 命令/訊息路由:m_pRoutingView、m_pRoutingFrame、m_lastSentMsg、m_hTrackingWindow/Menu。
  • ToolTip 狀態:m_pToolTip、m_pLastHit、m_lastInfo 等。
  • OLE 控制項停駐:m_pWndPark、m_nCtrlRef、m_bNeedTerm。

3. AFX_MODULE_THREAD_STATE — 模組與線程雙重私有資料

核心資料包括:

  • m_pCurrentWinThread:當前 CWinThread 指標。
  • m_frameList:該模組在該線程建立的所有 CFrameWnd 列表(建構時加入,解構時移除)。
  • m_nTempMapLock、m_pmapHWND、m_pmapHMENU...:臨時/永久句柄映射表與鎖計數,防止併發修改。
  • m_pfnNewHandler:線程專屬 new 失敗處理器。
  • Socket 相關映射表:m_mapSocketHandle、m_mapDeadSockets、m_listSocketNotifications。

五、 MFC 線程局部儲存 (TLS) 機制實現

MFC 未直接使用 Win32 TlsAlloc/TlsGetValue,而是建構了一層封裝,以支援「模組-線程」雙重私有語意並簡化跨模組管理。

1. CThreadSlotData 與全域 _afxThreadData

CThreadSlotData 類別管理進程內所有線程的局部儲存空間。全域指標 _afxThreadData 首次使用時建立。其核心欄位:

  • m_tlsIndex:調用 TlsAlloc 獲得的系統 TLS 索引。
  • m_pSlotData:CSlotData 陣列,每個槽位對應一個線程局部變數,記錄佔用旗標與擁有模組句柄。
  • m_list:CThreadData 串列,每個線程一個節點,節點內 pData 陣列存放該線程所有槽位的資料指標。
  • m_sect:關鍵區,保護全域槽位分配/釋放的執行緒安全。

機制流程(圖 9-5、9-6):

  1. 進程啟動,_afxThreadData 初始化,TlsAlloc 取得 m_tlsIndex。
  2. 新線程建立時,系統自動分配 TLS 槽,MFC 在 DllMain(DLL_THREAD_ATTACH) 或線程啟動例程中為該線程配置 CThreadData 結構,掛入 m_list,並將指標存入 TLS 索引對應位置。
  3. 宣告線程局部變數(THREAD_LOCAL 宏)時,MFC 分配一個槽號(AllocSlot),記錄於變數對應的 CThreadLocal 物件的 m_nSlot 中。
  4. 存取變數時:TlsGetValue(m_tlsIndex) → 取得線程的 CThreadData* → 以槽號為索引讀取 pData[slot] → 獲得資料指標 → 取得實際資料。

這是一個「三級間接尋址」過程,雖較原生 TLS 多兩層,但提供了跨模組的統一管理、延遲初始化、型別安全(透過 CThreadLocal 模板)等優勢。

2. 線程狀態 _afxThreadState 的延遲建立

THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState) 宏展開為全域 CThreadLocal<_AFX_THREAD_STATE> _afxThreadState。CThreadLocal 繼承自 CThreadLocalObject,首次存取時(GetData())才分配槽號、在線程局部空間 new 出 _AFX_THREAD_STATE 物件。這實現了「首次使用才建立」,加快 DLL 載入速度。

3. 進程模組狀態 _afxBaseModuleState

使用 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 的初始化流程,以及句柄映射表的具體運作邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved