MFC的资源切换AFX_MANAGE_STATE (转载http://www.cnblogs.com/ntrgx/archive/2010/11/14/1877199.html)

来源:互联网 发布:网络信息员岗位职责 编辑:程序博客网 时间:2024/05/29 04:34
MFC的资源切换AFX_MANAGE_STATE (转载) 
如何实现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; }


原来,就是定义一个局部的对象,利用其构造和析构函数在函数的入口和函数的出口进行State状态的切换,我猜AfxGetStaticModuleState()一定是获取当前代码所在DLL的State。


果然,请看


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把很多的数据都堆放在这里,搞得很复杂,结构性非常的差。 
}


afxModuleState是dll的静态成员,自然可以被同样的dll里的代码所访问,但是何时初始化的?


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的入口函数,用该DLL的hInstance初始化了该结构。


到这时候,我们还是不明白,为什么要进行资源切换?前面开始的_afxThreadState到底是什么?好像跟Thread有关系,到底是什么呢?


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


再往下跟踪,发现其实代码越发生涩难懂,但是基本的功能就是访问当前此行代码的线程的私有数据。所谓线程的私有数据,就是说,不同的线程执行同样的一段代码,得到的数据可能是不同的。这才想起来,MFC的很多句柄啦,都是保存在全局的Map里的,而且放在线程的私有数据区里,所以跨线程传递MFC对象是很不安全的。但是,MFC为什么要这么做呢?这个问题,到目前为止,我还是搞不明白。


还是回到开始的代码,资源切换到底是如何进行的?


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的对话框装载资源是通过获取当前线程对应的ModuleState保存的ResourceHandler来装载资源的。所以,DLL里的代码,需要在函数的入口,首先把当前执行线程的ModuleState换成该Dll的State,这样才能装载该dll的资源!这时候,我突然明白过来,为什么需要要依赖线程的私有数据来保存ModuleState,其实确切的说是传递!--这其实是因为CDialog是存放在另一个DLL里的,比如MFC40.dll,如果以共享模式连接MFC库的话。而用户自己编写的CDialog的子类并不放在CDialog同样的Dll里,他们如何来传递这个资源句柄呢?两种解决办法:1,利用参数传递。2,存放在一个公共的地方。前者需要增加参数,显得很麻烦,Win32的API好像就是这样实现的吧?后者,需要确定这个公共地方在何处?这让人想起来,建立一个公共的动态库?由主程序的提供?再多说一句,J2EE里有一个容器的概念(COM+好像也有,不知道.NET是如何的),组件都是生存在容器里,这时候我们就可以设想把该数据存放在容器里。不管怎样,MFC的实现就是放在线程的私有数据区,不需要公共的动态库,也不需要麻烦主程序,它自己就搞定了!它自以为很好的解决方式,很完美,却引发了我们的一系列的问题,特别是不明白就里的人。


关于资源装载,问题似乎已经解决了,但是还有一点点小麻烦就是,我实现的dll不是以普通的输出函数进行输出的,而是输出类,我可不想在每一个类的成员函数里添加AFX_MANAGE_STATE(AfxGetStaticModuleState())。怎么办呢?既然已经知道了资源切换的原理,我们添加两个输出函数,分别对应AFX_MAINTAIN_STATE2的构造和析构函数,在类的使用前后调用,就可以了。或者,分别放在类的构造和析构函数里。又或者,就声明为成员变量。无论怎样,需要保证的一点就是资源的切换要正确嵌套,不可交叉--这种情况在不同的DLL之间交叉调用的时候会发生。


好了,现在DLL里的资源可以正确调用了,但是在当Dialog上包含有IE控件的时候,我们还是失败了,为什么呢?我知道对于ActiveX控件,Dialog需要做一些特殊的处理,AfxEnableControlContainer(),我也知道,要使用COM,需要CoInitialize(),但是我一直没有想过需要两个一起用才能把IE弄出来,但是最后就是这样的。奇怪的是,如果不是在工作线程里,根本不需要CoInitialize(),就能装载IE控件的,这个暂时就先不管了。


PROCESS_LOCAL(COccManager, _afxOccManager)


void AFX_CDECL AfxEnableControlContainer(COccManager* pOccManager) 

    if (pOccManager == NULL) 
        afxOccManager = _afxOccManager.GetData(); 
    else 
        afxOccManager = pOccManager; 
}


#define afxOccManager   AfxGetModuleState()->m_pOccManager


这样看来,这个_afxOccManager应该是属于整个进程的,整个进程只有一个,就在那个定义它的dll里。但是,你需要把该对象(或者创建一个自定义的)传给ModuleState(请注意前面的AFX_MODULE_STATE里就包含了该属性),也就是要AfxEnableControlContainer()一下,这样特定的ModuleState就有了OccManager的信息!但是,请注意,一定要在目标dll里,正确切换了资源之后,才能进行,如下:


AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
CoInitialize(NULL); 
AfxEnableControlContainer();
0 0