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

MFC 模組狀態管理機制深度解析(二):執行緒局部儲存、擴充 DLL 鏈接、狀態切換與清理流程

     編輯:關於VC++

前言

承接上篇,本文深入剖析 MFC 模組狀態的執行緒局部儲存 (TLS) 實現、擴充 DLL (Extension DLL) 鏈接機制、AFX_MANAGE_STATE 狀態切換、以及程式/DLL 退出時的清理流程。理解這些機制,是解決 MFC 多模組、多執行緒環境下資源載入失敗、CRuntimeClass 找不到、物件跨模組斷言失效等疑難雜症的鑰匙。

一、TLS 架構下的狀態建立流程

核心模板類別

// 執行緒局部儲存管理器
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; // 進程模組狀態

首次存取觸發鏈式建立

  1. _afxThreadState.GetData() → CThreadLocalObject::GetData
    • 檢查 m_nSlot (TLS 索引),為 0 則呼叫 _afxThreadData->AllocSlot() 分配槽位。
    • _afxThreadData 為 CThreadSlotData*,首次存取時在堆上建立。
    • 取得槽位後,_afxThreadData->GetThreadValue(slot) 讀取 TLS 值。
    • 若為 NULL,呼叫建立函式建立 _AFX_THREAD_STATE,再 SetValue 寫入 TLS。
  2. 執行緒狀態建立完成後,存取 m_pModuleState,若為 NULL → 呼叫 _afxBaseModuleState.GetData() 建立進程模組狀態 _AFX_BASE_MODULE_STATE。
  3. 模組狀態內的 m_thread (模組-執行緒狀態) 同樣透過 CThreadLocal 按需建立。

CThreadSlotData 核心演算法

// 配置 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;
}

二、模組狀態切換:AFX_MANAGE_STATE

實現原理:建構/解構時的堆疊切換

#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 內建)—

核心規則:每個進入點函式第一行必須呼叫,確保資源載入、類別查找、物件建立使用正確模組狀態。

三、擴充 DLL (Extension DLL) 初始化與鏈接

_AFX_EXTENSION_MODULE 結構

struct AFX_EXTENSION_MODULE {
    BOOL bInitialized;
    HMODULE hModule;              // DLL 模組控制代碼
    HMODULE hResource;            // 資源控制代碼
    CRuntimeClass* pFirstSharedClass;  // 匯出的 CRuntimeClass 鏈表頭
    COleObjectFactory* pFirstSharedFactory; // 匯出的 OLE Factory 鏈表頭
};

兩階段初始化流程

  1. DllMain 階段:AfxInitExtensionModule(extensionDLL, hInstance)
    • 保存 hModule/hResource。
    • 從當前模組狀態 (應用程式/規則 DLL) 的 m_classList 取出本 DLL 初始化的 AFX_CLASSINIT 註冊類別,移至 pFirstSharedClass。
    • 同理處理 OLE Factory 至 pFirstSharedFactory。
    • 清理當前模組狀態的 m_classInit,避免重複。
  2. 匯出初始化函式:InitMyDll() 中建立 CDynLinkLibrary
    extern "C" void WINAPI InitMyDll() {
        CDynLinkLibrary* pDLL = new CDynLinkLibrary(extensionDLL, TRUE);
        // TRUE = 系統 DLL (MFC 核心 DLL 也用此標記)
    }

CDynLinkLibrary:鏈接節點

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)
                                                                              ...

資源/類別/Factory 查找演算法

// 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 多態序列化

四、核心 MFC DLL 與規則 DLL 的特殊處理

核心 MFC DLL (MFCxx.dll)

  • 本質是特殊擴充 DLL,擁有 coreDLL (AFX_EXTENSION_MODULE)。
  • DllMain 中 AfxInitExtensionModule(coreDLL) + new CDynLinkLibrary(coreDLL) 鏈接到當前模組狀態。
  • _afxBaseModuleState 即為動態連結應用程式的模組狀態。

動態連結規則 DLL (Regular DLL dynamically linked to MFC)

  • 擁有獨立模組狀態 afxModuleState (_AFX_DLL_MODULE_STATE) 與 CWinApp 衍生物件。
  • MFC 內建 DllMain (在 DllModul.cpp):
    1. AfxWinInit 保存模組/資源控制代碼到 afxModuleState。
    2. 建立 CDynLinkLibrary 將 核心 MFC DLL (coreDLL) 鏈接到 afxModuleState。
    3. 呼叫 CWinApp::InitInstance 初始化 DLL 內部類別/資源。
    4. 建立另一個 CDynLinkLibrary 將 本 DLL 自身擴充模組 (controlDLL) 鏈接到 afxModuleState。
  • 呼叫者無需 CWinApp,可為任意 Win32 應用程式。

五、狀態清理與銷毀流程

場景清理進入點關鍵動作
動態連結應用程式結束核心 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。

六、模組-執行緒狀態的映射表管理

CHandleMap:雙層映射

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 同步防止並發修改。
  • 不可跨執行緒傳遞 MFC 物件指標,需傳遞 HANDLE,對端呼叫 FromHandle。

七、總結

MFC 模組狀態機制構建了多模組、多執行緒隔離與協作的基礎設施:

  1. TLS 架構:CThreadLocal/CProcessLocal 實現執行緒/進程隔離的狀態儲存。
  2. 鏈式查找:模組狀態 + CDynLinkLibrary 鏈表,統一資源、類別、Factory 查找邏輯。
  3. RAII 狀態切換:AFX_MANAGE_STATE 確保進入點自動切換/還原,消除人為錯誤。
  4. 分級清理:進程/執行緒/DLL 卸載各有對應流程,確保無洩漏、無殘留映射。
  5. 映射表隔離:CHandleMap 綁定執行緒與模組,從根源防止跨執行緒物件誤用。

掌握此機制,能從容應對「資源找不到」、「類別無法動態建立」、「跨 DLL 物件斷言失敗」等經典 MFC 難題。

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