程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> MFC教程(9)-- MFC的進程和線程(2)

MFC教程(9)-- MFC的進程和線程(2)

編輯:關於VC++

下面以一個動態鏈接到MFC DLL的單模塊應用程序為例,說明這些對象的創建過程。

當第一次訪問狀態信息時,比如使用 AfxGetModuleState得到模塊狀態,導致系列創建過程的開始,如圖9-7所示。

首先分析語句pState=_afxThreadState。如果_afxThreadData、線程狀態和模塊狀態還沒有創建,該語句可以導致這些數據的創建。

pState聲明為CNoTrackObject對象的指針,_afxThreadState聲明為一個模板CThreadLocal的實例,pState=_afxThreadData為什麼可以通過編譯器的檢查呢?因為CThreadLocal模板重載了操作符“”*”和“->”,這兩個運算返回CNoTrackObject類型的對象。回顧3.2節CThreadLocalObject、CThreadLocal的定義,這兩個操作符運算到最後都是調用CThreadLocalObject的成員函數GetData。

創建_afxThreadData所指對象和線程狀態

CThreadLocalObject::GetData用來獲取線程局部變量(這個例子中是線程狀態)的值,其參數用來創建動態的線程局部變量。圖9-7的上面的虛線框表示其流程:

它檢查成員變量m_nSlot是否等於0(線程局部變量是否曾經被分配了MFC線程私有空間槽位),檢查全局變量_afxTheadData指針是否為空。如果_afxThreadData空,則創建一個CThreadSlotData類對象,讓_afxThreadData指向它,這樣本程序的MFC線程局部存儲的管理者被創建。如果m_nSlot等於0,則讓_afxThreadDtata調用AllocSlot分配一個槽位並把槽號保存在m_nSlot中。

得到了線程局部變量(線程狀態)所占用的槽位後,委托_afxThreadData調用GetThreadValue(m_nSlot)得到線程狀態值(指針)。如果結果非空,則返回它;如果結果是NULL,則表明該線程狀態還沒有被創建,於是使用參數創建一個動態的線程狀態,並使用SetValue把其指針保存在槽m_nSlot中,返回該指針。

創建模塊狀態

得到了線程狀態的值後,通過它得到模塊狀態m_pModuleState。如果m_pModuleState為空,表明該線程狀態是才創建的,其許多成員變量還沒有賦值,程序的進程模塊狀態還沒有被創建。於是調用函數_afxBaseModule.GetData,導致進程模塊狀態被創建。

圖9-7的下面一個虛線框表示了CProcessLocalObject::GetData的創建過程:

_afxBaseModule首先檢查成員變量m_pObject是否空,如果非空就返回它,即進程模塊狀態指針;否則,在堆中創建一個動態的_AFX_BASE_MODULE_STATE對象,返回。

從上述兩個GetData的實現可以看出,CThreadLocal模板對象負責線程局部變量的創建和管理(查詢,修改,刪除);CProcessLocal模板對象負責進程局部變量的創建和管理(查詢,修改,刪除)。

模塊-線程狀態的創建

模塊狀態的成員模塊-線程狀態m_thread的創建類似於線程狀態的創建:當第一次訪問m_thread所對應的CThreadLocal模板對象時,給m_thread分配MFC線程局部存儲的私有槽號m_nSlot,並動態地創建_AFX_MODULE_THREAD_STATE對象,保存對象指針在m_nSlot槽中。

創建過程所涉及的幾個重要函數的算法

創建過程所涉及的幾個重要函數的算法描述如下:

AllocSlot

AllocSlot用來分配線程的MFC私有存儲空間的槽號。由於該函數要修改全局變量_afxThreadData,所以必須使用m_sect關鍵段對象來同步多個線程對該函數的調用。

CThreadSlotData::AllocSlot()

{

進入關鍵段代碼(EnterCriticalSection(m_sect);)

搜索m_pSlotData,查找空槽(SLOT)

如果不存在空槽(第一次進入時,肯定不存在)

分配或再分配內存以創建新槽,指針m_pSlotData指向分配的地址。

得到新槽(SLOT)

標志該SLOT為已用

記錄最新可用的SLOT到成員變量m_nRover中。

離開關鍵段代碼(LeaveCriticalSection(m_sect);)

返回槽號

}

GetThreadValue

GetThreadValue用來獲取調用線程的第slot個線程局部變量的值。每一個線程局部變量都占用一個且只一個槽位。

CThreadSlotData::GetThreadValue(int slot)

{

//得到一個CThreadData型的指針pData

//pData指向MFC線程私有存儲空間。

//m_tlsIndex在_afxThreadData創建時由構造函數創建

pData=(CThreadData*)TlsGetValue(m_tlsIndex),。

如果指針空或slot>pData->nCount, 則返回空。

否則,返回pData

}

SetValue

SetValue用來把調用線程的第slot個線程局部變量的值(指針)存放到線程的MFC私有存儲空間的第slot個槽位。

CThreadSlotData::SetValue(int slot, void *pValue)

{

//通過TLS索引得到線程的MFC私有存儲空間

pData = (CThreadData*)TlsGetValue(m_tlsIndex)

//沒有得到值或者pValue非空且當前槽號,即

//線程局部變量的個數

//大於使用當前局部變量的線程個數時

if (pData NULL or slot > pData->nCount && pValue!=NULL)

{

if pData NULL //當前線程第一次訪問該線程局部變量

{

創建一個CThreadData實例;

添加到CThreadSlotData::m_list;

令pData指向它;

}

按目前為止,線程局部變量的個數為pData->pData分配或重分配內存,

用來容納指向真正線程數據的指針

調用TlsSetValue(pData)保存pData

}

//把指向真正線程數據的pValue保存在pData對應的slot中

pData->pData[slot] = pValue

}

管理狀態

在描述了MFC狀態的實現機制之後,現在來討論MFC的狀態管理和相關狀態的作用。

模塊狀態切換

模塊狀態切換就是把當前線程的線程狀態的m_pModuleState指針指向即將運行模塊的模塊狀態。

MFC使用AFX_MANAGE_STATE宏來完成模塊狀態的切換,即進入模塊時使用當前模板的模板狀態,並保存原模板狀態;退出模塊時恢復原來的模塊狀態。這相當於狀態的壓棧和出棧。實現原理如下。

先看MFC關於AFX_MANAGE_STATE的定義:

#ifdef _AFXDLL

struct AFX_MAINTAIN_STATE

{

AFX_MAINTAIN_STATE(AFX_MODULE_STATE* pModuleState);

~AFX_MAINTAIN_STATE();

protected:

AFX_MODULE_STATE* m_pPrevModuleState;

};

//AFX_MANAGE_STATE宏的定義:

#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE _ctlState(p);

#else // _AFXDLL

#define AFX_MANAGE_STATE(p)

#endif //!_AFXDLL

如果使用MFC DLL,MFC提供類AFX_MAINTAIN_STATE來實現狀態的壓棧和出棧,AFX_MANAGE_SATATE宏的作用是定義一個AFX_MAINTAIN_STATE類型的局部變量_ctlState。

AFX_MAINTAIN_STATE的構造函數在其成員變量m_pPrevModuleState中保存當前的模塊狀態對象,並把參數指定的模塊狀態設定為當前模塊狀態。所以該宏作為入口點的第一條語句就切換了模塊狀態。---www.bianceng.cn

在退出模塊時,局部變量_ctlState將自動地銷毀,這導致AFX_MAINTAIN_STATE的析構函數被調用,析構函數把保存在m_pPrevModuleState的狀態設置為當前狀態。

AFX_MANAGE_SATATE的參數在不同場合是不一樣的,例如,

DLL的輸出函數使用

AFX_MANAGE_SATATE(AfxGetStaticModuleState());

其中,AfxGetStaticModuleState返回DLL的模塊狀態afxModuleState。

窗口函數使用

AFX_MANAGE_STATE(_afxBaseModuleState.GetData());

其中,_afxBaseModuleState.GetData()返回的是應用程序的全局模塊狀態。

OLE使用的模塊切換方法有所不同,這裡不作討論。

上面討論了線程執行行不同模塊的代碼時切換模塊狀態的情況。在線程創建時怎麼處理模塊狀態呢?

一個進程(使用MFC的應用程序)的主線程創建線程模塊狀態和進程模塊狀態,前者是_AFX_THREAD_STATE類的實例,後者是_AFX_BASE_MODULE_STATE類的實例。

當進程的新的線程被創建時,它創建自己的線程狀態,繼承父線程的模塊狀態。在線程的入口函數_AfxThreadEntry完成這樣的處理,該函數的描述見8.5.3節。

擴展DLL的模塊狀態

7.3.1節指出擴展DLL的實現必須遵循五條規則,為此,首先在擴展DLL實現文件裡頭,定義AFX_EXTENSION_MODULE類型的靜態擴展模塊變量,然後在DllMain入口函數裡頭使用AfxInitExtension初始化擴展模塊變量,並且實現和輸出一個初始化函數供擴展DLL的使用者調用。

使用者必須具備一個CWinApp對象,通常在它的InitInstance函數中調用擴展DLL提供的初始化函數。

一般用以下的幾段代碼完成上述任務。首先是擴展模塊變量的定義和初始化:

static AFX_EXTENSION_MODULE extensionDLL;

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)

{

if (dwReason == DLL_PROCESS_ATTACH)

{

// Extension DLL one-time initialization

if (!AfxInitExtensionModule(extensionDLL,hInstance))

return 0;

……

}

}

然後是擴展DLL的初始化函數,假定初始化函數命名為InitMyDll,InitMyDll被定義為“C”鏈接的全局函數,並且被輸出。

// wire up this DLL into the resource chain

extern “C” void WINAPI InitMyDll()

{

CDynLinkLibrary* pDLL = new

CDynLinkLibrary(extensionDLL, TRUE);

ASSERT(pDLL != NULL);

...

}

最後是調用者的處理,假定在應用程序對象的InitInstance函數中調用初始化函數:

BOOL CMyApp::InitInstance()

{

InitMyMyDll();

}

上述這些代碼只有在動態鏈接到MFC DLL時才有用。下面,對這些代碼進行分析和解釋

_AFX_EXTENSION_MODULE

在分析代碼之前,先討論描述擴展模塊狀態的_AFX_EXTENSION_MODULE類。_AFX_EXTENSION_MODULE沒有基類,其定義如下:

struct AFX_EXTENSION_MODULE

{

BOOL bInitialized;

HMODULE hModule;

HMODULE hResource;

CRuntimeClass* pFirstSharedClass;

COleObjectFactory* pFirstSharedFactory;

};

其中:

第一個域表示該結構變量是否已經被初始化了;

第二個域用來保存擴展DLL的模塊句柄;

第三個域用來保存擴展DLL的資源句柄;

第四個域用來保存擴展DLL要輸出的CRuntimeClass類;

第五個域用來保存擴展DLL的OLE Factory。

該結構用來描述一個擴展DLL的模塊狀態信息,每一個擴展DLL都要定義一個該類型的靜態變量,例如extensionDLL。

在DllMain中,調用AfxInitExtensionModule函數來初始化本DLL的靜態變量該變量(擴展模塊狀態),如extensionDLL。函數AfxInitExtensionModule原型如下:

BOOL AFXAPI AfxInitExtensionModule(

AFX_EXTENSION_MODULE& state, HMODULE hModule)

其中:

參數1是DllMain傳遞給它的擴展DLL的模塊狀態,如extensionDLL;

參數2是DllMain傳遞給它的模塊句柄。

AfxInitExtensionModule函數主要作以下事情:

(1)把擴展DLL模塊的模塊句柄hModule、資源句柄hModule分別保存到參數state的成員變量hModule、hResource中;

(2)把當前模塊狀態的m_classList列表的頭保存到state的成員變量pFirstSharedClass中,m_classInit的頭設置為模塊狀態的m_pClassInit。在擴展DLL模塊進入DllMain之前,如果該擴展模塊構造了靜態AFX_CLASSINIT對象,則在初始化時把有關CRuntimeClass信息保存在當前模塊狀態(注意不是擴展DLL模塊,而是應用程序模塊)的m_classList列表中。因此,擴展DLL模塊初始化的CRuntimeClass信息從模塊狀態的m_classList中轉存到擴展模塊狀態state的pFirstSharedClass中,模塊狀態的m_classInit恢復被該DLL改變前的狀態。

關於CRuntimeclass信息和AFX_CLASSINIT對象的構造,在3.3.1節曾經討論過。一個擴展DLL在初始化時,如果需要輸出它的CRuntimeClass對象,就可以使用相應的CRuntimeClass對象定義一個靜態的AFX_CLASSINIT對象,而不一定要使用IMPLEMENT_SERIAL宏。當然,可以序列化的類必定導致可以輸出的CRuntimeClass對象。

(3)若支持OLE的話,把當前模塊狀態的m_factoryList的頭保存到state的成員變量pFirstSharedFactory中。m_factoryList的頭設置為模塊狀態的m_m_pFactoryInit。

(4)這樣,經過初始化之後,擴展DLL模塊包含了擴展DLL的模塊句柄、資源句柄、本模塊初始化的CRuntimeClass類等等。

擴展DLL的初始化函數將使用擴展模塊狀態信息。下面,討論初始化函數的作用。

擴展DLL的初始化函數

在初始化函數InitMyDll中,創建了一個動態的CDynLinkLibrary對象,並把對象指針保存在pDLL中。CDynLinkLibrary類從CCmdTarget派生,定義如下:

class CDynLinkLibrary : public CCmdTarget

{

DECLARE_DYNAMIC(CDynLinkLibrary)

public:

// Constructor

CDynLinkLibrary(AFX_EXTENSION_MODULE& state,

BOOL bSystem = FALSE);

// Attributes

HMODULE m_hModule;

HMODULE m_hResource; // for shared resources

CTypedSimpleList<CRuntimeClass*> m_classList;

#ifndef _AFX_NO_OLE_SUPPORT

CTypedSimpleList<COleObjectFactory*> m_factoryList;

#endif

BOOL m_bSystem; // TRUE only for MFC DLLs

// Implementation

public:

CDynLinkLibrary* m_pNextDLL; // simple singly linked list

virtual ~CDynLinkLibrary();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif //_DEBUG

};

CDynLinkLibrary的結構和AFX_EXTENSION_MODULE有一定的相似性,存在對應關系。

CDynLinkLibrary構造函數的第一個參數就是經過AfxInitExtensionModule初始化後的擴展DLL的模塊狀態,如extensionDLL,第二個參數表示該DLL模塊是否是系統模塊。

創建CDynLinkLibrary對象導致CCmdTarget和CDynLinkLibrary類的構造函數被調用。CCmdTarget的構造函數將獲取模塊狀態並且保存在成員變量m_pModuleState中。CDynLinkLibrary的構造函數完成以下動作:

構造列表m_classList和m_factoryList;

把參數state的域hModule、hResource復制到對應的成員變量m_hModule、m_hResource中;

把state的pFirstSharedClass、pFirstSharedFactory分別插入到m_classList列表、m_factoryList列表的表頭;

把參數2的值賦值給成員變量m_bSystem中;

至此,CDynLinkLibrary對象已經構造完畢。之後,CDynLinkLibrary構造函數把CDynLinkLibrary對象自身添加到當前模塊狀態(調用擴展DLL的應用程序模塊或者規則DLL模塊)的CDynLinkLibrary列表m_libraryList的表頭。為了防止多個線程修改模塊狀態的m_libraryList,訪問m_libraryList時使用了同步機制。

這樣,調用模塊執行完擴展模塊的初始化函數之後,就把該擴展DLL的資源、CRuntimeClass類、OLE Factory等鏈接到調用者的模塊狀態中,形成一個鏈表。圖9-8表明了這種關系鏈。

綜合以上分析,可以知道:

擴展DLL的模塊僅僅在該DLL調用DllMain期間和調用初始化函數期間被使用,在這些初始化完畢之後,擴展DLL模塊被鏈接到當前調用模塊的模塊狀態中,因此它所包含的資源信息等也就被鏈接到調用擴展DLL的應用程序或者規則DLL的模塊狀態中了。擴展DLL擴展了調用者的資源等,這是“擴展DLL”得名的原因之一。

也正因為擴展DLL沒有自己的模塊狀態(指AFX_MODULE_STATE對象,擴展DLL模塊狀態不是),而且必須由有模塊狀態的模塊來使用,所以只有動態鏈接到MFC的應用程序或者規則DLL才可以使用擴展DLL模塊的輸出函數或者輸出類。

核心MFC DLL

所謂核心MFC DLL,就是MFC核心類庫形成的DLL,通常說動態鏈接到MFC,就是指核心MFC DLL。

核心MFC DLL實際上也是一種擴展DLL,因為它定義了自己的擴展模塊狀態coreDLL,實現了自己的DllMain函數,使用AfxInitExtensionModule初始化核心DLL的擴展模塊狀態coreDLL,並且DllMain還創建了CDynLinkLibrary,把核心DLL的擴展模塊狀態coreDLL鏈接到當前應用程序的模塊狀態中。所有這些,都符合擴展DLL的處理標准。

但是,核心MFC DLL是一種特殊的擴展DLL,因為它定義和實現了MFC類庫,模塊狀態、線程狀態、進程狀態、狀態管理和使用的機制就是核心MFC DLL定義和實現的。例如核心MFC DLL定義和輸出的模塊狀態變量,即_afxBaseModuleState,就是動態鏈接到MFC的DLL的應用程序的模塊狀態。

但是MFC DLL不作為獨立的模塊表現出來,而是把自己作為一個擴展模塊來處理。當應用程序動態鏈接到MFC DLL時,MFC DLL把自己的擴展模塊狀態coreDLL鏈接到模塊狀態afxBaseModuleState,模塊狀態的成員變量m_hCurrentInstanceHandle指定為應用程序的句柄。當規則DLL動態鏈接到MFC DLL時,由規則DLL的DllMain把核心MFC DLL的擴展模塊狀態coreDLL鏈接到規則DLL的模塊狀態afxModuleState中,模塊狀態afxModuleState的m_hCurrentInstanceHandle指定為規則DLL的句柄。

關於afxModuleState和規則DLL的模塊狀態,見下一節的討論。

動態鏈接的規則DLL的模塊狀態的實現

在本節中,動態鏈接到MFC DLL(定義了_AFXDLL)的規則DLL在下文簡稱為規則DLL。

(1)規則DLL的模塊狀態的定義

規則DLL有自己的模塊狀態_afxModuleState,它是一個靜態變量,定義如下:

static _AFX_DLL_MODULE_STATE afxModuleState;

_AFX_DLL_MODULE_STATE的基類是AFX_MODULE_STATE。

在前面的模塊狀態切換中提到的AfxGetStaticModuleState函數,其定義和實現如下:

_AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState()

{

AFX_MODULE_STATE* pModuleState = &afxModuleState;

return pModuleState;

}

它返回規則DLL的模塊狀態afxModuleState。

規則DLL的內部函數使用afxModuleState作為模塊狀態;輸出函數在被調用的時候首先切換到該模塊狀態,然後進一步處理。

(2)規則DLL的模塊狀態的初始化

從用戶角度來看,動態鏈接到MFC DLL的規則DLL不需要DllMain函數,只要提供CWinApp對象即可。其實,MFC內部是在實現擴展DLL的方法基礎上來實現規則DLL的,它不僅為規則DLL提供了DllMain函數,而且規則DLL也有擴展DLL模塊狀態controlDLL。

順便指出,和擴展DLL相比,規則DLL有一個CWinApp(或其派生類)應用程序對象和一個模塊狀態afxModuleState。應用程序對象是全局對象,所以在進入規則DLL的DllMain之前已經被創建,DllMain可以調用它的初始化函數InitInstance。模塊狀態afxModuleState是靜態全局變量,也在進入DllMain之前被創建,DllMain訪問模塊狀態時得到的就是該變量。擴展DLL是沒有CWinApp對象和模塊狀態的,它只能使用應用程序或者規則DLL的CWinApp對象和模塊狀態。

由於核心MFC DLL的DllMain被調用的時候,訪問的必定是應用程序的模塊狀態,要把核心DLL的擴展模塊狀態鏈接到規則DLL的模塊狀態中,必須通過規則DLL的DllMain來實現。

規則DLL的DllMain(MFC內部實現)把參數1表示的模塊和資源句柄通過AfxWinInit函數保存到規則DLL的模塊狀態中。順便指出,WinMain也通過AfxWinInit函數把資源和模塊句柄保存到應用程序的模塊狀態中。

然後,該DllMain還創建了一個CDynLinkLibrary對象,把核心MFC DLL的擴展模塊 coreDLL鏈接到本DLL的模塊狀態afxModuleState。

接著,DllMain得到自己的應用程序對象並調用InitInstance初始化。

之後,DllMain創建另一個CDynLinkLibrary對象,把本DLL的擴展模塊controlDLL鏈接到本DLL的模塊狀態afxModuleState。

(3)使用規則DLL的應用程序可不需要CwinApp對象

規則DLL的資源等是由DLL內部使用的,不存在資源或者CRuntimeClass類輸出的問題,這樣調用規則DLL的程序不必具有模塊狀態,不必關心規則DLL的內部實現,不一定需要CwinApp對象,所以可以是任意Win32應用程序,

還有一點需要指出,DllMain也是規則DLL的入口點,在它之前,調用DllMain的RawDllMain已經切換了模塊狀態,RawDllMain是靜態鏈接的,所以不必考慮狀態切換。

狀態信息的作用

在分析了MFC模塊狀態的實現基礎和管理機制之後,現在對狀態信息的作用進行專門的討論。

模塊信息的保存和管理

傳統上,線程狀態、模塊狀態等包含的信息是全局變量,但是為了支持Win32s、多線程、DLL等,這些變量必須是限於進程或者線程范圍內有效,或者限於某個模塊內有效。也就是,不再可能把它們作為全局變量處理。因此,MFC引入模塊、線程、模塊-線程狀態等來保存和管理一些重要的信息。

例如:一個模塊注冊了一個“窗口類”之後,應用程序要保存“窗口類”的名字,以便在模塊退出時取消注冊的“窗口類”。因此,模塊狀態使用成員變量m_szUnregisterList在注冊成功之後保存的“窗口類”名字。窗口注冊見2.2.1節。

又如:Tooltip窗口是線程相關的,每個線程一個,所以線程狀態用成員變量m_pToolTip來保存本線程的MFC Tooltip窗口對象。Tooltip窗口見13.2.4.4節。

還有,MFC對象是線程和模塊相關的,所以模塊線程中有一組變量用來管理本線程的MFC對象到Windows對象的映射關系。關於MFC對象和Windows對象的映射,見稍後的討論。

模塊狀態、線程狀態、模塊線程狀態的每個成員變量都有自己存在的必要和作用,這裡就不一一論述了,在此,只是強調模塊狀態自動地實現對模塊句柄和資源句柄等信息的保存和管理,這對MFC應用程序是非常重要的。

SDK 下的應用程序或者DLL,通常使用一個全局變量來保存模塊/資源句柄。有了模塊狀態之後,程序員就不必這麼作了。規則DLL或者應用程序的模塊和資源句柄在調用DllMain或WinMain時被保存到了當前模塊的模塊狀態中。如果是擴展DLL,則其句柄被保存到擴展模塊狀態中,並通過CDynLinkLibrary對象鏈接到主模塊的模塊狀態。

圖9-8示意了MFC模塊狀態對資源、CRuntimeClass對象、OLE工廠等模塊信息的管理。

圖9-8的說明:

左邊的主模塊狀態表示動態鏈接到MFC DLL的應用程序或者規則DLL的模塊狀態,其資源句柄和模塊句柄用來查找和獲取資源,資源句柄一般是應用程序的模塊句柄;CRuntimeClass對象列表和COleObjectFactory對象列表分別表示該模塊初始化了的CRuntimeClass對象和該模塊的OLE工廠對象;CDynLinkLibrary列表包含了它引用的系列擴展DLL的擴展模塊狀態(包括核心MFC DLL的狀態),鏈表中的每一個CDynLinkLibrary對象對應一個擴展模塊狀態,代表了創建該對象的擴展DLL的有關資源、信息。

MFC查找資源、CRuntimeClass類、OLE工廠時,首先查找模塊狀態,然後,遍歷CDynLinkLibrary表搜索相應的對象。下面兩節舉例說明。

MFC資源、運行類信息的查找

MFC內部使用的資源查找函數是:

HINSTANCE AfxFindResourceHandle(LPCTSTR lpszName, LPCTSTR lpszType):

其中:

參數1是要查找的資源名稱,參數2是要查找的資源類型。

返回包含指定資源的模塊的句柄。

上述函數的查找算法如下:

如果進程模塊狀態(主模塊)不是系統模塊,則使用::FindResource(下同)搜索它,成功則返回;

如果沒有找到,則遍歷CDynLinkLibrary對象列表,搜索所有的非系統模塊,成功則返回;

如果沒有找到,則檢查主模塊的語言資源,成功則返回;

如果沒有找到,並且主模塊是系統模塊,則搜索它,成功則返回;

如果沒有找到,則遍歷CDynLinkLibrary對象列表,搜索所有的系統模塊,成功則返回;

如果沒有找到,則使用AfxGetResourceHanlde返回應用程序的資源。

需要指出的是,遍歷CDynLinkLibrary對象列表時,必須采取同步措施,防止其他線程改變鏈表。MFC是通過鎖定全局變量CRIT_DYNLINKLIST來實現的,類似的全局變量MFC定義了多個。

運行時類信息的查找算法類似。

3.3.4節指出,對象進行“<<”序列化操作時,首先需要搜索到指定類的運行時信息,方法如下:

CRuntimeClass* PASCAL CRuntimeClass::Load(

CArchive& ar, UINT* pwSchemaNum)

遍歷主模塊的CRuntimeClass對象列表m_classList,搜索主模塊是否實現了指定的CRuntimeClass類;

遍歷CDynLinkLibrary對象列表m_libraryList;對每一個CDynLinkLibrary對象,遍歷它的CRuntimeClass對象列表m_classList。這樣,所有的擴展DLL模塊的CRuntimeClass對象都會被搜索到。

模塊信息的顯示

遍歷模塊狀態和CDynLinkLibrary列表,可以顯示模塊狀態及其擴展模塊狀態的有關信息。下面,給出一個實現,它顯示程序的當前模塊名稱、句柄和初始化的CRuntimeClass類,然後顯示所有擴展模塊的名稱名稱、句柄和初始化的CRuntimeClass類。

#ifdef _DEBUG

AFX_MODULE_STATE* pState = AfxGetModuleState();

//顯示應用程序的名稱和句柄

TRACE("APP %s HANDLE %x ", pState->m_lpszCurrentAppName,

pState->m_hCurrentInstanceHandle);

TCHAR szT[256];

int nClasses;

nClasses=0;

//顯示CRuntimeClass類信息

AfxLockGlobals(CRIT_RUNTIMECLASSLIST);

for (CRuntimeClass* pClass = pModuleState->m_classList;

pClass != NULL;pClass = pClass->m_pNextClass)

{

nClasses++;

TRACE("CRuntimeClass: %s ",pClass->m_lpszClassName, );

}

AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);

TRACE("all %d classes ", nClasses);

//遍歷CDynLinkLibrary列表

AfxLockGlobals(CRIT_DYNLINKLIST);

for (CDynLinkLibrary* pDLL = pState->m_libraryList; pDLL != NULL;

pDLL = pDLL->m_pNextDLL)

{

// 得到模塊名並且顯示

TCHAR szName[64];

GetModuleFileName(pDLL->m_hModule, szName, sizeof(szName));

TRACE("MODULE %s HANDLE IS %x ", szName, pDLL->m_hModule);

//得到CRuntimeClass信息並顯示

nClasses = 0;

for (CRuntimeClass* pClass = pDLL->m_classList;

pClass != NULL; pClass = pClass->m_pNextClass)

{

nClasses++;

TRACE("CRuntimeClass: %s ",pClass->m_lpszClassName, );

}

wsprintf(szT, _T(" Module %s has %d classes"),szName, nClasses);

}

AfxUnlockGlobals(CRIT_DYNLINKLIST);

#endif

使用MFC提供的調試函數AfxDoForAllClasses可以得到DLL模塊的輸出CRuntimeClass類的信息。上述實現類似於AfxDoForAllClasses函數的處理,只不過增加了模塊名和模塊句柄信息。

模塊-線程狀態的作用

由模塊-線程狀態類的定義可知,一個模塊-線程狀態包含了幾類Windows對象—MFC對象的映射。下面討論它們的作用。

只能訪問本線程MFC對象的原因

MFC規定:

不能從一個非MFC線程創建和訪問MFC對象

如果一個線程被創建時沒有用到CWinThread對象,比如,直接使用“C”的_beginthread或者_beginthreadex創建的線程,則該線程不能訪問MFC對象;換句話說,只有通過CWinThread創建MFC線程對象和Win32線程,才可能在創建的線程中使用MFC對象。

一個線程僅僅能訪問它所創建的MFC對象

這兩個規定的原因是:

為了防止多個線程並發地訪問同一個MFC對象,MFC對象和Windows對象之間有一個一一對應的關系,這種關系以映射的形式保存在創建線程的當前模塊的模塊-線程狀態信息中。當一個線程使用某個MFC對象指針P時,ASSERT_VALID(P)將驗證當前線程的當前模塊是否有Windows句柄和P對應,即是否創建了P所指的Windows對象,驗證失敗導致ASSERT斷言中斷程序的執行。如果一個線程要使用其他線程的Windows對象,則必須傳遞Windows對象句柄,不能傳遞MFC對象指針。

當然一般來說,MFC應用程序僅僅在Debug版本下才檢查這種映射關系,所以訪問其他線程的MFC對象的程序在Realease版本下表面上不會有問題,但是MFC對象被並發訪問的後果是不可預見的。

實現MFC對象和Windows對象之間的映射

MFC提供了幾個函數完成MFC對象和Windows對象之間的映射或者解除這種映射關系,以及從MFC對象得到Windows對象或者從Windows對象得到或創建相應的MFC對象。

每一個MFC對象類都有成員函數Attach和Detach,FromHandle和FromHandlePermanent,AssertValid。這些成員函數的形式如下:

Attach(HANDLE Windows_Object_Handle)

例如:CWnd類的是Attach(HANLDE hWnd),CDC類的是Attach(HDC hDc)。

Attach用來把一個句柄永久性(Perment)地映射到一個MFC對象上:它把一個Windows對象捆綁(Attach)到一個MFC對象上,MFC對象的句柄成員變量賦值為Windows對象句柄,該MFC對象應該已經存在,但是句柄成員變量為空。

Detach()

Detach用來取消Windows對象到MFC對象的永久性映射。如果該Windows對象有一個臨時的映射存在,則Detach不理會它。MFC讓線程的Idle清除臨時映射和臨時MFC對象。

它是一個靜態成員函數。如果該Windows對象沒有映射到一個MFC對象,FromHandle則創建一個臨時的MFC對象,並把Windows對象映射到臨時的MFC對象上,然後返回臨時MFC對象。

它是一個靜態成員函數。如果該Windows對象沒有永久地映射到一個MFC對象上,則返回NULL,否則返回對應的MFC對象。

AssertValid()

它是從CObject類繼承來的虛擬函數。MFC覆蓋該函數,實現了至少一個功能:判斷當前MFC對象的指針this是否映射到一個對應的可靠的Windows對象。

圖 9-9示意了MFC對映射結構的實現層次,對圖9-9解釋如下。

圖中上面的虛線框表示使用映射關系的高層調用,包括上面講述的幾類函數。MFC和應用程序通過它們創建、銷毀、使用映射關系。

圖中中間的虛線框表示MFC使用CHandleMap類實現對映射關系的管理。一個CHandleMap對象可以通過兩個成員變量來管理兩種映射數據:臨時映射和永久映射。模塊-線程狀態給每一類MFC對象分派一個CHandleMap對象來管理其映射數據(見模塊-線程類的定義),例如m_pmapHWND所指對象用來保存CWnd對象(或派生類對象)和Windows window之間的映射。

下面的虛線框表示映射關系的最底層實現,MFC使用通用類CMapPtrToPtr來管理MFC對象指針和Windows句柄之間的映射數據。

對本節總結如下:

MFC的映射數據保存在模塊-線程狀態中,是線程和模塊局部的。每個線程管理自己映射的數據,其他線程不能訪問到本線程的映射數據,也就不允許使用本線程的MFC對象。

每一個MFC對象類(CWnd、CDC等)負責創建或者管理這類線程-模塊狀態的對應CHandleMap類對象。例如,CWnd::Attach創建一個永久性的映射保存在m_pmapHwnd所指對象中,如果m_pmapHand還沒有創建,則使用AfxMapHWND創建相應的CHandleMap對象。

映射分兩類:永久性的或者臨時的。

臨時對象的處理

在2.4節就曾經提到了臨時對象,現在是深入了解它們的時候了。

臨時對象指MFC對象,是MFC或者程序員使用FromHandle或者SelectObject等從一個Windows對象句柄創建的對應的MFC對象。

在模塊-線程狀態中,臨時MFC對象的映射是和永久映射分開保存的。

臨時MFC對象在使用完畢後由MFC框架自動刪除,MFC在線程的Idle處理中刪除本線程的臨時MFC對象,為了防止並發修改,通過線程狀態m_nTempMapLock(等於0,可以修改,大於0,等待)來同步。所以,臨時MFC對象不能保存備用。

狀態對象的刪除和銷毀

至此,本章討論了MFC的線程局部存儲機制,MFC狀態的定義、實現和用途。在程序或者DLL退出之前,模塊狀態被銷毀;在線程退出時,線程狀態被銷毀。狀態對象被銷毀之前,它活動期間所動態創建的對象被銷毀,動態分配的內存被釋放。

先解釋幾個函數:

AfxTermExtensionModule(HANDLE hInstanceOfDll,BOOL bAll);

若bAll為真,則該函數銷毀本模塊(hInstanceOfDll標識的模塊)的模塊狀態的m_libraryList列表中所有動態分配的CDynLinkLibrary對象,否則,該函數清理本DLL動態分配的CDynLinkLibrary對象,並調用AfxTerLocalData釋放本DLL模塊為當前線程的線程局部變量分配的堆空間。

AfxTermLocalData(HANDLE hInstance, BOOL bAll);

若bAll為真,則刪除MFC線程局部存儲的所有槽的指針所指的對象,也就是銷毀當前線程的全部局部變量,釋放為這些線程局部變量分配的內存;否則,僅僅刪除、清理當前線程在hInstance表示的DLL模塊中創建的線程局部變量。

參與清理工作的函數有多種、多個,下面結合具體情況簡要描述它們的作用。

(1)對動態鏈接到MFC DLL的應用程序

動態鏈接到MFC DLL的應用程序退出時,將在DllMain和RawDllMain處理進程分離時清理狀態對象,該DllMain和RawDllMain是核心MFC DLL的入口和出口,在DLLINIT.CPP文件中實現,和進程分離時完成如下動作:

DllMain調用AfxTermExtensionModule(coreDll)清理核心MFC DLL的模塊狀態;調用AfxTermExtensionModule(coreDll, TRUE)清理OLE私有的模塊狀態;調用AfxTermLocalData(NULL, TRUE)釋放本進程或者線程所有的局部變量。

RawDllMain在DllMain之後調用,它調用AfxTlsRealease;AfxTlsRealease減少對_afxThreadData的引用計數,如果引用數為零,則調用對應的CThreadSlotData析構函數清理_afxThreadData所指對象。

(2)對靜態鏈接到MFC DLL的應用程序

如果是靜態鏈接到MFC DLL的應用程序,由於RawDllMain和DllMain不起作用,將由一個靜態變量析構時完成狀態的清除:

有一個AFX_TERM_APP_STATE類型的靜態變量,在程序結束時將被銷毀,導致析構函數被調用,析構函數完成以下動作:

調用AfxTermLocalData(NULL, TRUE)釋放本進程(主線程)的所用局部數據。

(3)對於動態鏈接到MFC DLL的規則DLL

對於動態鏈接到MFC DLL的規則DLL,將在RawDllMain和DllMain中清理狀態對象。這兩個函數在DllModule.cpp中定義,是規則DLL的入口和出口。當和進程分離時,分別有如下動作:

DllMain清除該模塊的模塊-線程狀態中的所有臨時映射,清除臨時MFC對象;調用AfxWinTerm;調用AfxTermExtensionModule(controlDLL, TRUE),釋放本DLL模塊狀態m_libraryList中的所有CDynLinkLibrary對象。

RawDllMain設置線程狀態的模塊狀態指針,使它指向線程狀態的m_PrevModuleState所指狀態。

(4)對於靜態鏈接到MFC DLL的DLL

對於靜態鏈接到MFC DLL的DLL,只有DllMain會被調用,執行以下動作:

清除該模塊的模塊-線程狀態中的所有臨時映射,清除臨時MFC對象;調用AfxWinTerm;調用AfxTermLocalData(hInstance, TRUE)清理本DLL模塊的當前線程的線程局部數據。

另外,它定義一個_AFX_TERM_DLL_STATE類型的靜態變量,在DLL退出時該變量被銷毀,導致其析構函數被調用。析構函數完成如下動作:

調用AfxTermateLocalData(NULL, TRUE);調用AfxCriticlTerm結束關鍵變量;調用AfxTlsRealease。

(5)線程終止時

當使用AFxBeginThread創建的線程終止時,將調用AfxTermThread(HANDLE hInstance)作結束線程的清理工作(參數為NULL):銷毀臨時MFC對象,銷毀本線程的線程局部變量,等等。

另外,當DLL模塊和AfxBeginThread創建的線程分離時,也調用AfxTermThread(hInstance),參數是模塊的句柄,銷毀臨時MFC對象,銷毀本線程在本DLL創建的線程局部變量,等等。所以,AfxTermThread可能被調用兩次。

最後,CThreadLocal和CProcessLocal的實例將被銷毀,析構函數被調用:如果MFC線程局部存儲空間的槽m_nSlot所指的線程局部對象還沒有銷毀,則銷毀它。

_afxThreadData在MFC DLL的RawDllMain或者隨著_AFX_TERM_APP_STATE析構函數的調用,_afxThreadData所指對象被銷毀。_afxThreadData所指對象銷毀之後,所有的狀態相關的內存都被釋放。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved