MFC模块状态(一)
先看一個例子:
1、創(chuàng)建一個動態(tài)鏈接到MFC DLL的規(guī)則DLL,其內(nèi)部包含一個對話框資源。指定該對話框ID如下:
??????????????#define IDD_DLL_DIALOG ?2000
2、創(chuàng)建一個基于對話框的mfc應用程序,它包含兩個對話框資源,IDD_UI_DIALOG和IDD_EXE_DIALOG。并將后者的ID指定如下:
??????????????#define IDD_EXE_DIALOG ?2000
其中前者是這個應用程序的用戶界面,單擊上面的按鈕,將彈出一個對話框。部分代碼如下:
3、單擊按鈕,彈出的不是期望的DLL中的對話框IDD_DLL_DIALOG,而是應用程序中的對話框IDD_EXE_DIALOG。
解釋:
1、應用程序進程本身及其調(diào)用的每個DLL模塊都具有一個全局唯一的HINSTANCE句柄,它們代表了EXE或DLL模塊在進程虛擬空間中的起始地址。(進程本身的模塊句柄一般為0x400000,而DLL模塊的缺省句柄為0x10000000。如果程序同時加載了多個DLL,則每個DLL模塊都會有不同的HINSTANCE。應用程序在加載DLL時對其進行了重定位)。
2、共享MFC DLL(或MFC擴展DLL)的規(guī)則DLL涉及到HINSTANCE句柄問題,HINSTANCE句柄對于加載資源特別重要。EXE和DLL都有其自己的資源,而且這些資源ID可能重復,如果應用程序與規(guī)則DLL共享MFC DLL(或MFC擴展DLL),那么將總是默認使用EXE的資源。
3、因此應用程序需要通過資源模塊的切換來找到正確的資源。如果應用程序需要來自于DLL的資源,就應將資源模塊句柄指定為DLL的模塊句柄;如果需要EXE文件中包含的資源,就應將資源模塊句柄指定為EXE的模塊句柄。
解決辦法:
1、在DLL中改進:
- 方法1
注:AFX_MANAGE_STATE(AfxGetStaticModuleState());一定是作為接口函數(shù)的第一條語句。
???????其功能是在棧上(這意味著其作用域是局部的)創(chuàng)建一個AFX_MODULE_STATE類的實例,并將其指針pModuleState返回。
???????AFX_MODULE_STATE類利用其構造函數(shù)和析構函數(shù)進行存儲模塊狀態(tài)現(xiàn)場及恢復現(xiàn)場的工作。
???????該宏用于將pModuleState設置為當前的有效模塊狀態(tài)。當離開該宏的作用域時(也就離開了pModuleState所指棧上對象的作用域),先前的模塊狀態(tài)將由類AFX_MODULE_STATE的析構函數(shù)恢復。(即自動恢復)
- 方法2
注:AfxGetResourceHandle:獲取當前資源模塊句柄;AfxSetResourceHandle:設置程序目前要使用的資源模塊句柄。
???????同方法1比較,方法2能夠靈活地設置程序的資源模塊句柄,而方法1則只能在DLL接口函數(shù)退出的時候才會恢復模塊句柄。
2、在應用程序中改進:
// in EXE void CEXE::OnButtonClick() {HINSTANCE exe_hInstance = GetModuleHandle(NULL);HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll");AfxSetResourceHandle(dll_hInstance); //切換狀態(tài) ShowDlg();AfxSetResourceHandle(exe_hInstance); //恢復狀態(tài) }注:使用狀態(tài)切換的情況:當DLL導出函數(shù)包含MFC資源、類或者需要創(chuàng)建窗口時。
附加信息1
AFX_MANAGE_STATE(AfxGetStaticModuleState());//用于模塊切換時的狀態(tài)保護,
- AfxGetStaticModuleState()指向當前模塊狀態(tài);
- 當前函數(shù)調(diào)用結束后原模塊的狀態(tài)自動被恢復;
- 用于DLL中所調(diào)用MFC函數(shù)、類、資源時的模塊狀態(tài)切換
摘自MSDN:
By default, MFC uses the resource handle of the main application to load the resource template. If you have an exported function in a DLL, such as one that launches a dialog box in the DLL, this template is actually stored in the DLL module. You need to switch the module state for the correct handle to be used. You can do this by adding the following code to the beginning of the function: AFX_MANAGE_STATE(AfxGetStaticModuleState( )); This swaps the current module state with the state returned from AfxGetStaticModuleState until the end of the current scope.也就是說,並不是每一個dll的輸出函數(shù)前都要調(diào)用它,只有在要輸出對話框等用到資源時要調(diào)用!
dll中資源是共享的用了這個函數(shù)的防止不同的進程修改資源產(chǎn)生錯誤!
缺省情況下MFC使用主應用程序的資源句柄來載入資源模板,而DLL中的資源模板是存在于DLL模板中,因此要使用這一語句切換到由AfxGetStaticModuleState返回的正確的模塊狀態(tài),得到正確的句柄。
----------------------------------------------------------------------------------------------------------------------------------
動態(tài)鏈接到MFC的規(guī)則DLL所有輸出的函數(shù)應該以如下語句開始:
AFX_MANAGE_STATE(AfxGetStaticModuleState( )) //此語句用來正確地切換MFC模塊狀態(tài)。作用在MSDN的解釋:
By default, MFC uses the resource handle of the main application to load the resource template. If you have an exported function in a DLL, such as one that launches a dialog box in the DLL, this template is actually stored in the DLL module. You need to switch the module state for the correct handle to be used. You can do this by adding the following code to the beginning of the function:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
在Dll中創(chuàng)建對話框并調(diào)用(簡單的示例程序)
1、第一步創(chuàng)建一“MFC AppWizard (dll)”工程,接下來選擇“Regular Dll using shared MFC DLL”,點擊“Finish”。
2、添加一對話框資源到工程中,從菜單中選擇Insert->Resource,添加一“Dialog”
選擇“New”,至此對話框已添加到DLL工程中。
3、為對話框添加一新類,如:CTest,基類為CDialog。
4、在MFCDLL.cpp中(因創(chuàng)建的工程為MFCDLL)添加接口函數(shù):
extern "C" __declspec(dllexport) void Show() {AFX_MANAGE_STATE(AfxGetStaticModuleState());CTest test;test.DoModal (); }別忘了在文件中添加: #include "Test.h":),大功告成,編譯吧!
5、用VC新建一對話框工程,在一按鈕點擊事件中添加如下代碼:
typedef void (WINAPI * TESTDLL)();HINSTANCE hmod; hmod = ::LoadLibrary ("mfcdll.dll");if(hmod==NULL) {AfxMessageBox("Fail"); } TESTDLL lpproc; lpproc = (TESTDLL)GetProcAddress (hmod,"Show");if(lpproc!=(TESTDLL)NULL)(*lpproc)(); FreeLibrary(hmod);?
/**********************************************************
/*
/* 2018/9.14補充
/*
**************************************************************
本技術備忘錄介紹MFC “模塊狀態(tài)”結構的實現(xiàn)。充分理解模塊狀態(tài)這個概念對于在DLL中使用MFC的共享動態(tài)庫是十分重要的。
MFC的狀態(tài)信息分為三種:全局模塊狀態(tài)數(shù)據(jù)、進程局部狀態(tài)數(shù)據(jù)和線程局部狀態(tài)數(shù)據(jù)。有時這些數(shù)據(jù)類型之間沒有嚴格界限,例如MFC的句柄表既是全局模塊狀態(tài)數(shù)據(jù)也屬于線程局部狀態(tài)數(shù)據(jù)。
進程局部狀態(tài)數(shù)據(jù)和線程局部狀態(tài)數(shù)據(jù)差不多。早先這些數(shù)據(jù)是全局的,但是為了更好的支持Win32和多線程,現(xiàn)在設計成進程或者線程相關的。模塊狀態(tài)數(shù)據(jù)既可以包含真正的全局狀態(tài)數(shù)據(jù),也可以指向進程或者線程相關的數(shù)據(jù)。
一、什么是模塊狀態(tài)?
模塊狀態(tài)實際上是指可執(zhí)行模塊運行所需的一個數(shù)據(jù)結構。首先要說明,這里的"模塊"指的是一個MFC可執(zhí)行程序,或者使用共享版本MFC動態(tài)庫的DLL或者ActiveX控件。沒有使用MFC的程序或者DLL等不在討論范圍之內(nèi)。
正如下圖"單個模塊的狀態(tài)數(shù)據(jù)"所描述的,使用MFC的每個模塊都有一套狀態(tài)數(shù)據(jù)。這些數(shù)據(jù)包括包括:窗口進程句柄(用于加載資源),指向當前程序的CWinApp和CWinThread對象的指針,OLE模塊引用次數(shù),以及很多關于Windows對象和其對應句柄的映射表等等。
???????????????? 單個模塊(程序)的狀態(tài)數(shù)據(jù)
???????????????
???????????? +-------------MFC程序
???????????? |
??????????? //
?????? +--------------------------------------------+
?????? |??????????????????????????????????????????? |
?????? |??? +--------------------------------+????? |
?????? |??? |??????????????????????????????? |????? |
?????? |??? |?? 線程對象???????????????????? |????? |
?????? |??? |??????????????????????????????? |????? |
?????? |??? +--------------------------------+????? |
?????? |??? |? m_pModuleState??????????????? +---+? |
?????? |??? +--------------------------------+?? |? |
?????? |??????????????????????????????????????? //? |
?????? +--------------------------------------------+
?????? |??? 狀態(tài)數(shù)據(jù)??????????????????????????????? |
?????? +--------------------------------------------+
(注意,因為采用的字符畫圖,如果圖形顯示有問題,請復制到記事本中看)
一個模塊的所有狀態(tài)數(shù)據(jù)包含在一個結構中,這個結構在MFC中被打包成一個類 AFX_MODULE_STATE, 它派生自 CNoTrackObject。關于這個類后面會談到。AFX_MODULE_STATE類的定義位于AfxStat_.H中。內(nèi)容如下所示:
// AFX_MODULE_STATE (模塊的全局數(shù)據(jù)) class AFX_MODULE_STATE : public CNoTrackObject { public: //構造函數(shù) #ifdef _AFXDLLAFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem); #elseAFX_MODULE_STATE(BOOL bDLL); #endif~AFX_MODULE_STATE(); //析構函數(shù) CWinApp* m_pCurrentWinApp; //指向CWinApp對象的指針HINSTANCE m_hCurrentInstanceHandle; //當前進程句柄HINSTANCE m_hCurrentResourceHandle; //當前資源句柄LPCTSTR m_lpszCurrentAppName; //當前程序的文件名BYTE m_bDLL; //TRUE表示模塊是 DLL,否則是EXEBYTE m_bSystem; //TRUE表示模塊是系統(tǒng)模塊BYTE m_bReserved[2]; //字節(jié)對齊 DWORD m_fRegisteredClasses; //窗口類注冊標記 。。。 //很多其它運行態(tài)數(shù)據(jù) };?
二、為什么需要切換模塊狀態(tài)
模塊狀態(tài)數(shù)據(jù)是十分重要的。因為很多MFC函數(shù)都要使用這些狀態(tài)數(shù)據(jù)。如果一個MFC程序使用多模塊,比如一個MFC程序需要調(diào)用多個DLL或者OLE控件的情況,則每個模塊都擁有自己的一套MFC狀態(tài)數(shù)據(jù)。
MFC程序運行過程中,每個線程都包含一個指向“當前”或者“有效”模塊狀態(tài)的指針(自然,這個指針是MFC的線程局部狀態(tài)數(shù)據(jù)的一部分)。當線程執(zhí)行代碼流跨越模塊邊界,轉入一個特定的模塊的時候,就要改變這個指針的值,如下圖所示,m_pModuleState必須設置成指向有效的模塊狀態(tài)數(shù)據(jù)。這一點是非常重要的,否則將導致無法預知的程序錯誤。
多模塊下的狀態(tài)數(shù)據(jù)
?MFC程序?
?? /
??? /???????????????????????????????????????????? +--------------+
?+--------------------------------------+???????? |?? DLL模塊1?? |
?|????????????????????????????????????? |???????? |????????????? |
?|?? +----------------+????? 轉向模塊1? |???????? +--------------+
?|?? |?? 線程對象???? |???? +-----------+-------->|? 狀態(tài)數(shù)據(jù)??? |
?|?? |??????????????? |???? |?????????? |???????? +--------------+
?|?? +----------------+???? |?????????? |
?|?? | m_pModuleState +-----+?????????? |???????? +--------------+
?|?? |??????????????? |????? 轉向模塊2? |???????? |?? DLL模塊2?? |??
?|?? |??????????????? +-----------------+----+??? |????????????? |??
?|?? +----------------+???????????????? |??? |??? +--------------+
?|????????????????????????????????????? |??? +--->|? 狀態(tài)數(shù)據(jù)??? |
?+--------------------------------------+???????? +--------------+
?|?? 狀態(tài)數(shù)據(jù)?????????????????????????? |
?+--------------------------------------+
(注意,因為采用的字符畫圖,如果圖形顯示有問題,請復制到記事本中看)
比如說,如果你在DLL中導出了一個函數(shù),該函數(shù)要創(chuàng)建一個對話框,而這個對話框的模板資源位于DLL中。缺省情況下,MFC是使用主程序中的資源句柄來加載資源的,但現(xiàn)在這個對話框的資源位于DLL中,所以,必須設置m_pModuleState指向DLL模塊的狀態(tài)數(shù)據(jù),否則,就會導致加載資源失敗。
因此,每個模塊要負責在它的所有入口點進行狀態(tài)數(shù)據(jù)的切換。所謂"入口點" 就是任何執(zhí)行代碼流可以進入模塊的地方,包括:?
1、DLL中導出的函數(shù);
2、COM接口函數(shù)
3、窗口過程
首先談dll中的導出函數(shù)。一般來說,如果從一個DLL中導出了一個函數(shù),應該使用AFX_MANAGE_STATE 宏維護正確的全局狀態(tài)。
調(diào)用這個宏的時候,它設置pModuleState指向有效的模塊狀態(tài)數(shù)據(jù),從而該函數(shù)后面的代碼就可以通過該指針得到有效的狀態(tài)數(shù)據(jù)。當函數(shù)執(zhí)行完畢,即將返回時,該宏將自動恢復指針原來的值。
這個自動切換是這樣完成的,在棧空間上創(chuàng)建一個AFX_MODULE_STATE類的實例,并把當前的模塊狀態(tài)指針保存在一個成員變量里面,然后把pModuleState設置成有效的模塊狀態(tài),在這個實例對象的析構函數(shù)中,對象恢復以前保存的指針。
所以,對于上面所說的DLL導出函數(shù),可以在該函數(shù)的開始加入如下預句:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
這個代碼將當前的模塊狀態(tài)設置成AfxGetStaticModuleState返回的值。離開當前作用域之后恢復原來的模塊狀態(tài)。
但是,不是任何DLL中導出的函數(shù)都需要使用AFX_MANAGE_STATE。例如InitInstance函數(shù),MFC在調(diào)用這個函數(shù)的時候是自動切換模塊狀態(tài)的。對于MFC常規(guī)動態(tài)庫中的所有消息處理函數(shù)來說也不需要使用這個宏。因為常規(guī)DLL會鏈接一個特殊的主窗口過程,里面會自動切換模塊狀態(tài)。對于其它導出函數(shù),如果沒有用到模塊狀態(tài)中的數(shù)據(jù),也可以不使用這個宏。
對于COM接口的成員函數(shù)來說,一般使用METHOD_PROLOGUE宏來維護正確的模塊狀態(tài)數(shù)據(jù)。這個宏實際上也使用了AFX_MANAGE_STATE。詳細信息可以參考技術備忘錄38:"MFC/OLE IUnknown的實現(xiàn)"。
對于窗口過程,如果模塊使用了MFC,則該模塊會靜態(tài)鏈接一個特殊的窗口過程實現(xiàn)函數(shù),首先用AFX_MANAGE_STATE宏設置有效的模塊狀態(tài),然后調(diào)用AfxWndProc,這個函數(shù)接著調(diào)用某窗口具體的WindowProc函數(shù)。具體可以參考WINCORE.CPP。
三、模塊狀態(tài)是如何切換的
一般來說,設置當前的模塊狀態(tài)數(shù)據(jù)可以通過函數(shù)AfxSetModuleState。但是大多數(shù)情況下,無需直接使用這個API函數(shù),MFC知道應該如何正確設置模塊狀態(tài)數(shù)據(jù),它會替你調(diào)用它,比如在WinMain函數(shù)、OLE入口、AfxWndProc中等等。這是通過靜態(tài)鏈接一個特殊的WndProc和WinMain (或者DllMain)實現(xiàn)的。可以參考 DLLMODUL.CPP或者APPMODUL.CPP,找到這些實現(xiàn)代碼。
設置當前的模塊狀態(tài),而又不把它設置回去的情況是十分少見的,一般來講,在改變了模塊狀態(tài)后,都要進行恢復。可以通過AFX_MANAGE_STATE宏和AFX_MAINTAIN_STATE類來實現(xiàn)。我們看看這個宏的定義:
#ifdef _AFXDLL?//定義了這個符號表示動態(tài)鏈接MFC
struct AFX_MAINTAIN_STATE
{
?AFX_MAINTAIN_STATE(AFX_MODULE_STATE* pModuleState);//參數(shù)是AFX_MODULE_STATE類對象指針
?~AFX_MAINTAIN_STATE();
protected:
?AFX_MODULE_STATE* m_pPrevModuleState;??//保存在這個私有變量中
};
class _AFX_THREAD_STATE;?//線程局部狀態(tài)數(shù)據(jù),這個類也是派生自CNoTrackObject
struct AFX_MAINTAIN_STATE2????//多線程版本
{
?AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pModuleState);
?~AFX_MAINTAIN_STATE2();
protected:
?AFX_MODULE_STATE* m_pPrevModuleState;??//用來保存模塊狀態(tài)數(shù)據(jù)的指針
?_AFX_THREAD_STATE* m_pThreadState;??//指向線程局部狀態(tài)數(shù)據(jù)的指針
};
#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE2 _ctlState(p);?//定義AFX_MANAGE_STATE宏
#else? // _AFXDLL
#define AFX_MANAGE_STATE(p)?//否則,這個宏沒有意義。
#endif //!_AFXDLL
我們再來看看AFX_MAINTAIN_STATE2的構造函數(shù),很簡單的代碼:
AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pNewState)
{
?m_pThreadState = _afxThreadState;??//首先保存線程局部狀態(tài)數(shù)據(jù)指針
?m_pPrevModuleState = m_pThreadState->m_pModuleState; //保存全局模塊狀態(tài)數(shù)據(jù)指針
?m_pThreadState->m_pModuleState = pNewState;?//設置全局模塊狀態(tài)數(shù)據(jù)指針,指向pNewState。
}
由此可見,線程局部狀態(tài)數(shù)據(jù)里面包含一個指向全局模塊狀態(tài)數(shù)據(jù)的指針。
四、進程局部數(shù)據(jù)
對于Win32 DLL,在每個關聯(lián)它的進程中都有一份獨立的數(shù)據(jù)拷貝。考慮如下代碼:
static CString strGlobal; // at file scope
__declspec(dllexport)?
void SetGlobalString(LPCTSTR lpsz)
{
?? strGlobal = lpsz;
}
__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, int cb)
{
?? lstrcpyn(lpsz, strGlobal, cb);
}
如果上述代碼位于一個DLL中,并且該DLL被兩個進程A和B加載(或者同一個程序的兩個實例),那么將會發(fā)生什么事情呢? A調(diào)用SetGlobalString("Hello from A"),結果,在進程A的上下文中為該CString對象分配內(nèi)存空間,現(xiàn)在B 調(diào)用GetGlobalString(sz, sizeof(sz))。那么B是否可以訪問到A 設置的數(shù)據(jù)呢?
在WIN3.1中是可以的,因為Win32s沒有提供象Win32那樣的進程間的保護措施。顯然這是有問題的,為了解決這個問題。MFC 3.x 是采用線程局部存儲(TLS)技術解決這個問題,和Win32下保存線程局部數(shù)據(jù)的方法類似。但是每個MFC DLL都要在每個進程中使用兩個TLS索引,如果加載過多DLL,會很快消耗完TLS索引(只有64個)。除此以外,還有其它問題。所以在MFC 4.x的版本中,采用了一套模板類,來包裝這些進程相關的數(shù)據(jù)。例如下面的方法:
struct CMyGlobalData : public CNoTrackObject
{
?? CString strGlobal;
};
CProcessLocal<CMyGlobalData> globalData;
__declspec(dllexport)?
void SetGlobalString(LPCTSTR lpsz)
{
?? globalData->strGlobal = lpsz;
}
__declspec(dllexport)
void GetGlobalString(LPCTSTR lpsz, int cb)
{
?? lstrcpyn(lpsz, globalData->strGlobal, cb);
}
MFC采用兩個步驟實現(xiàn)該方法。首先,在Win32 Tls* API (包括TlsAlloc, TlsSetValue, TlsGetValue等)之上實現(xiàn)一個接口層,無論進程加載多少DLL,每個進程僅需使用兩個TLS索引。其次,通過CProcessLocal模板訪問數(shù)據(jù),它重載了->操作符。所有打包進CProcessLocal的對象必須派生自CNoTrackObject。而 CNoTrackObject提供一個底層的內(nèi)存分配函數(shù)(LocalAlloc/LocalFree)以及一個虛析構函數(shù),保證進程終止的時候,MFC可以自動銷毀該進程局部數(shù)據(jù)。這些CNoTrackObject派生類對象可以有自己的析構函數(shù),用于其它必要的清除操作。上面的例子里面沒有,因為編譯器會自動產(chǎn)生一個,并銷毀內(nèi)嵌的 CString 對象。CNoTrackObject類的定義位于Afxtls_.h中,主要是重載new 和 delete操作符,它的實現(xiàn)位于Afxtls.cpp中。
五、線程局部數(shù)據(jù)
和進程局部數(shù)據(jù)類似,線程局部數(shù)據(jù)是指必須和指定線程相關的局部數(shù)據(jù),也就是說,不同線程訪問同一個數(shù)據(jù)的時候,要為每個線程準備一份數(shù)據(jù)的實例。假設有一個CString對象,可以通過把它嵌入 CThreadLocal模板,使它成為線程局部數(shù)據(jù):
struct CMyThreadData : public CNoTrackObject
{
?? CString strThread;
};
CThreadLocal<CMyThreadData> threadData;
void MakeRandomString()
{
?? // 一種洗牌方式,52張牌,效率很低,不實用
?? CString& str = threadData->strThread;
?? str.Empty();
?? while (str.GetLength() != 52)
?? {
????? TCHAR ch = rand() % 52 + 1;
????? if (str.Find(ch) < 0)
???????? str += ch;?
?? }
}
如果從兩個不同的線程調(diào)用 MakeRandomString ,則每個線程都會打亂字符串的順序,而且相互之間沒有影響。這是因為每個線程都有一個strThread實例對象,而不是只有一個全局對象。
上述代碼中使用了一個引用,而不是在循環(huán)中使用 threadData->strThread,避免循環(huán)調(diào)用->操作符,這樣可以提高代碼的效率。
轉載于:https://www.cnblogs.com/tinaluo/p/7865507.html
總結
以上是生活随笔為你收集整理的MFC模块状态(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用RecyclerView打造一个轮播图
- 下一篇: 制作备份的解密密钥