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