以前寫MFC的DLL的時(shí)候,總會(huì)在自動(dòng)生成的代碼框架里看到提示,需要在每一個(gè)輸出的函數(shù)開始添加上AFX_MANAGE_STATE(AfxGetStaticModuleState())。一直不明白這樣做的含義,也一直沒有這樣做,而且代碼也工作得好好的,所以感覺這好像一句廢話。
最近的項(xiàng)目中,需要在DLL里使用MFC生成界面,這才發(fā)現(xiàn)一旦資源放在不同的動(dòng)態(tài)庫里,而且還和多線程攪和在一起的時(shí)候,事情就變得異常的復(fù)雜,以前對(duì)MFC的一知半解已經(jīng)不足與應(yīng)付了。程序莫名的崩潰,莫名的ASSERT,資源怎樣也裝載不起來,為什么呢?每次,總是嘗試著,在每一個(gè)線程的開始,把AFX_MANAGE_STATE(AfxGetStaticModuleState())添加上去,或者在某些地方用AfxSetResourceHandler()一把,然后問題就解決了,但是不是很明白到底是怎么回事,總感覺這種解決辦法讓人很不安心,仿佛在下一秒問題又會(huì)突然冒出來。
前天,這個(gè)問題終于發(fā)揮到了極致,任我花費(fèi)了好幾個(gè)小時(shí),怎樣的嘗試都不能成功,在項(xiàng)目的關(guān)鍵時(shí)候發(fā)生這種事情,讓我暗暗發(fā)誓以后再也不用MFC了。正像很多的電影情節(jié)一樣,事情最后還是得到了解決,這次我決定不能再這么算了,一定要把這個(gè)事情理解得明明白白。
在這里,我遇到的問題就是,如何讓DLL里的界面代碼使用該DLL的資源(Resource),如何在工作線程里加載有IE控件的對(duì)話框?
我問同事,他們是如何實(shí)現(xiàn)DLL資源切換的?AFX_MANAGE_STATE(AfxGetStaticModuleState())這就是他們的答案,一如微軟的推薦,原來就是這么簡單?。∽屛覀儊砜纯?,這句代碼到底做了什么?
#define AFX_MANAGE_STATE(p) AFX_MAINTAIN_STATE2 _ctlState(p);
AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2(AFX_MODULE_STATE* pNewState)
{
m_pThreadState = _afxThreadState;
m_pPrevModuleState = m_pThreadState->m_pModuleState;
m_pThreadState->m_pModuleState = pNewState;
}
_AFXWIN_INLINE AFX_MAINTAIN_STATE2::~AFX_MAINTAIN_STATE2()
{ m_pThreadState->m_pModuleState = m_pPrevModuleState; }
原來,就是定義一個(gè)局部的對(duì)象,利用其構(gòu)造和析構(gòu)函數(shù)在函數(shù)的入口和函數(shù)的出口進(jìn)行State狀態(tài)的切換,我猜AfxGetStaticModuleState()一定是獲取當(dāng)前代碼所在DLL的State。
果然,請(qǐng)看
static _AFX_DLL_MODULE_STATE afxModuleState;
AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState()
{
AFX_MODULE_STATE* pModuleState = &afxModuleState;
return pModuleState;
}
class _AFX_DLL_MODULE_STATE : public AFX_MODULE_STATE
// AFX_MODULE_STATE (global data for a module)
class AFX_MODULE_STATE : public CNoTrackObject
{
...
CWinApp* m_pCurrentWinApp;
HINSTANCE m_hCurrentInstanceHandle;
HINSTANCE m_hCurrentResourceHandle;
LPCTSTR m_lpszCurrentAppName;
BYTE m_bDLL; // TRUE if module is a DLL, FALSE if it is an EXE
...
COccManager* m_pOccManager;
...
這里不得不說,MFC把很多的數(shù)據(jù)都堆放在這里,搞得很復(fù)雜,結(jié)構(gòu)性非常的差。
}
afxModuleState是dll的靜態(tài)成員,自然可以被同樣的dll里的代碼所訪問,但是何時(shí)初始化的?
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
...
AfxWinInit(hInstance, NULL, _T(""), 0);
...
}
BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
// handle critical errors and avoid Windows message boxes
SetErrorMode(SetErrorMode(0) |
SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
// set resource handles
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_hCurrentInstanceHandle = hInstance;
pModuleState->m_hCurrentResourceHandle = hInstance;
...
}
原來在DLL的入口函數(shù),用該DLL的hInstance初始化了該結(jié)構(gòu)。
到這時(shí)候,我們還是不明白,為什么要進(jìn)行資源切換?前面開始的_afxThreadState到底是什么?好像跟Thread有關(guān)系,到底是什么呢?
THREAD_LOCAL(_AFX_THREAD_STATE, _afxThreadState)
#define THREAD_LOCAL(class_name, ident_name) \
AFX_DATADEF CThreadLocal <class_name> ident_name;
template <class TYPE>
class CThreadLocal : public CThreadLocalObject
再往下跟蹤,發(fā)現(xiàn)其實(shí)代碼越發(fā)生澀難懂,但是基本的功能就是訪問當(dāng)前此行代碼的線程的私有數(shù)據(jù)。所謂線程的私有數(shù)據(jù),就是說,不同的線程執(zhí)行同樣的一段代碼,得到的數(shù)據(jù)可能是不同的。這才想起來,MFC的很多句柄啦,都是保存在全局的Map里的,而且放在線程的私有數(shù)據(jù)區(qū)里,所以跨線程傳遞MFC對(duì)象是很不安全的。但是,MFC為什么要這么做呢?這個(gè)問題,到目前為止,我還是搞不明白。
還是回到開始的代碼,資源切換到底是如何進(jìn)行的?
int CDialog::DoModal()
{
...
HINSTANCE hInst = AfxGetResourceHandle();
if (m_lpszTemplateName != NULL)
{
hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);
HRSRC hResource = ::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);
hDialogTemplate = LoadResource(hInst, hResource);
...
}
_AFXWIN_INLINE HINSTANCE AFXAPI AfxGetResourceHandle()
{ ASSERT(afxCurrentResourceHandle != NULL);
return afxCurrentResourceHandle; }
#define afxCurrentResourceHandle AfxGetModuleState()->m_hCurrentResourceHandle
AFX_MODULE_STATE* AFXAPI AfxGetModuleState()
{
_AFX_THREAD_STATE* pState = _afxThreadState;
AFX_MODULE_STATE* pResult;
if (pState->m_pModuleState != NULL)
{
// thread state's module state serves as override
pResult = pState->m_pModuleState;
}
else
{
// otherwise, use global app state
pResult = _afxBaseModuleState.GetData();
}
ASSERT(pResult != NULL);
return pResult;
}
原來MFC的對(duì)話框裝載資源是通過獲取當(dāng)前線程對(duì)應(yīng)的ModuleState保存的ResourceHandler來裝載資源的。所以,DLL里的代碼,需要在函數(shù)的入口,首先把當(dāng)前執(zhí)行線程的ModuleState換成該Dll的State,這樣才能裝載該dll的資源!這時(shí)候,我突然明白過來,為什么需要要依賴線程的私有數(shù)據(jù)來保存ModuleState,其實(shí)確切的說是傳遞!--這其實(shí)是因?yàn)镃Dialog是存放在另一個(gè)DLL里的,比如MFC40.dll,如果以共享模式連接MFC庫的話。而用戶自己編寫的CDialog的子類并不放在CDialog同樣的Dll里,他們?nèi)绾蝸韨鬟f這個(gè)資源句柄呢?兩種解決辦法:,利用參數(shù)傳遞。,存放在一個(gè)公共的地方。前者需要增加參數(shù),顯得很麻煩,Win32的API好像就是這樣實(shí)現(xiàn)的吧?后者,需要確定這個(gè)公共地方在何處?這讓人想起來,建立一個(gè)公共的動(dòng)態(tài)庫?由主程序的提供?再多說一句,J2EE里有一個(gè)容器的概念(COM+好像也有,不知道.NET是如何的),組件都是生存在容器里,這時(shí)候我們就可以設(shè)想把該數(shù)據(jù)存放在容器里。不管怎樣,MFC的實(shí)現(xiàn)就是放在線程的私有數(shù)據(jù)區(qū),不需要公共的動(dòng)態(tài)庫,也不需要麻煩主程序,它自己就搞定了!它自以為很好的解決方式,很完美,卻引發(fā)了我們的一系列的問題,特別是不明白就里的人。
關(guān)于資源裝載,問題似乎已經(jīng)解決了,但是還有一點(diǎn)點(diǎn)小麻煩就是,我實(shí)現(xiàn)的dll不是以普通的輸出函數(shù)進(jìn)行輸出的,而是輸出類,我可不想在每一個(gè)類的成員函數(shù)里添加AFX_MANAGE_STATE(AfxGetStaticModuleState())。怎么辦呢?既然已經(jīng)知道了資源切換的原理,我們添加兩個(gè)輸出函數(shù),分別對(duì)應(yīng)AFX_MAINTAIN_STATE2的構(gòu)造和析構(gòu)函數(shù),在類的使用前后調(diào)用,就可以了。或者,分別放在類的構(gòu)造和析構(gòu)函數(shù)里。又或者,就聲明為成員變量。無論怎樣,需要保證的一點(diǎn)就是資源的切換要正確嵌套,不可交叉--這種情況在不同的DLL之間交叉調(diào)用的時(shí)候會(huì)發(fā)生。
好了,現(xiàn)在DLL里的資源可以正確調(diào)用了,但是在當(dāng)Dialog上包含有IE控件的時(shí)候,我們還是失敗了,為什么呢?我知道對(duì)于ActiveX控件,Dialog需要做一些特殊的處理,AfxEnableControlContainer(),我也知道,要使用COM,需要CoInitialize(),但是我一直沒有想過需要兩個(gè)一起用才能把IE弄出來,但是最后就是這樣的。奇怪的是,如果不是在工作線程里,根本不需要CoInitialize(),就能裝載IE控件的,這個(gè)暫時(shí)就先不管了。
PROCESS_LOCAL(COccManager, _afxOccManager)
void AFX_CDECL AfxEnableControlContainer(COccManager* pOccManager)
{
if (pOccManager == NULL)
afxOccManager = _afxOccManager.GetData();
else
afxOccManager = pOccManager;
}
#define afxOccManager AfxGetModuleState()->m_pOccManager
這樣看來,這個(gè)_afxOccManager應(yīng)該是屬于整個(gè)進(jìn)程的,整個(gè)進(jìn)程只有一個(gè),就在那個(gè)定義它的dll里。但是,你需要把該對(duì)象(或者創(chuàng)建一個(gè)自定義的)傳給ModuleState(請(qǐng)注意前面的AFX_MODULE_STATE里就包含了該屬性),也就是要AfxEnableControlContainer()一下,這樣特定的ModuleState就有了OccManager的信息!但是,請(qǐng)注意,一定要在目標(biāo)dll里,正確切換了資源之后,才能進(jìn)行,如下:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CoInitialize(NULL);
AfxEnableControlContainer();
至此,這個(gè)困擾我很久的問題,終于脈絡(luò)清晰起來了。
本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/teleinfor/archive/2010/01/17/5203816.aspx