MFC的资源切换AFX_MANAGE_STATE(AfxGetStaticModuleState()

转载自:http://blog.chinaunix.net/uid-20532101-id-1931929.html

以前写MFC的DLL的时候,总会在自动生成的代码框架里看到提示,需要在每一个输出的函数开始添加上AFX_MANAGE_STATE (AfxGetStaticModuleState())。一直不明白这样做的含义,也一直没有这样做,而且代码也工作得好好的,所以感觉这好像一句废 话。

最近的项目中,需要在DLL里使用MFC生成界面,这才发现一旦资源放在不同的动态库里,而且还和多线程搅和在一起的时候,事情就变 得异常的复杂,以前对MFC的一知半解已经不足与应付了。程序莫名的崩溃,莫名的ASSERT,资源怎样也装载不起来,为什么呢?每次,总是尝试着,在每 一个线程的开始,把AFX_MANAGE_STATE(AfxGetStaticModuleState())添加上去,或者在某些地方用 AfxSetResourceHandler()一把,然后问题就解决了,但是不是很明白到底是怎么回事,总感觉这种解决办法让人很不安心,仿佛在下一秒 问题又会突然冒出来。

前天,这个问题终于发挥到了极致,任我花费了好几个小时,怎样的尝试都不能成功,在项目的关键时候发生这种事情,让我暗暗发誓以后再也不用MFC了。正像很多的电影情节一样,事情最后还是得到了解决,这次我决定不能再这么算了,一定要把这个事情理解得明明白白。

在这里,我遇到的问题就是,如何让DLL里的界面代码使用该DLL的资源(Resource),如何在工作线程里加载有IE控件的对话框?

我问同事,他们是如何实现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 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();

至此,这个困扰我很久的问题,终于脉络清晰起来了。

时间: 2024-10-05 13:51:25

MFC的资源切换AFX_MANAGE_STATE(AfxGetStaticModuleState()的相关文章

在DLL中资源切换的两中方法

方法一: AFX_MANAGE_STATE(AfxGetAppModuleState());// MFC扩展dll 或 AFX_MANAGE_STATE( AfxGetStaticModuleState());//常规Regular dll 方法二: HMODULE hDLL=GetModuleHandle("SourceChange.dll"); HINSTANCE hEXE=AfxGetResourceHandle(); AfxSetResourceHandle((HINSTANC

heartbeat配置资源切换操作笔记(原创)

标题:CentOS6.6安装heartbeat实现高可用 参考资料:http://www.centoscn.com/CentosServer/cluster/2015/0605/5604.html 使用heartbeat来做HA集群,并且把nginx服务作为HA对应的服务.master1为主,另一个是备. 准备: master1: 192.168.42.12 10.1.1.1(心跳) master2: 192.168.42.13 10.1.1.2(心跳) 以下是IP配置: vip: 192.16

MFC菜单资源ID加载到对话框上

方法1: 首先定位到显示菜单栏对话框的OnInitDialog()函数处,在其函数体里添加如下代码: CMenu menu; menu.LoadMenuW(IDR_MENU1);//加载资源菜单栏,IDR_MENU1为资源视图里菜单的ID this->SetMenu(&menu);     //这个函数将当前菜单设为指定的菜单. //它使窗口被重画以反映菜单的变化.参数必须是指针型的. menu.Detach();// 窗口销毁时,局部变量menu同时销毁 方法2: 对话框属性栏中Gener

MFC 的资源文件 就是那个后缀名是 .rc的那个

参考: http://blog.csdn.net/zgrjkflmkyc/article/details/16897881 http://www.oschina.net/question/565065_72669 ××××××××××××××××××××××××××××××××××××××××××××××× 统一管理: 在Win32编程的时候,为了方便项目中的资源统一管理,IDE提供了一个统一的格式资源文件,这是就是后缀为.rc的文件. 在程序编译的时候他会被编译器(具体来说是资源编译器)生成一

MFC从资源加载文本

bool CWizardSheet::GetTextResource(UINT uID, CString& csContent) { HMODULE hModule=GetModuleHandle(NULL); HRSRC hRes = FindResource(hModule,MAKEINTRESOURCE(uID),_T("txt")); if(hRes == NULL) { FreeResource(hRes); } else { HGLOBAL hglobal = Lo

MFC工程资源相对路径

Project->Properties->Configuration Properties->Debuging->Working Directory改为$(ProjectDir) ./ 该目录 ../该目录的父目录

dll的概念、dll导出类

随笔- 64  文章- 0  评论- 37 dll的概念.dll导出类(转) 1. DLL的概念 DLL(Dynamic Linkable Library),动态链接库,可以向程序提供一些函数.变量或类.这些可以直接拿来使用. 静态链接库与动态链接库的区别: (1)静态链接库与动态链接库都是共享代码的方式.静态链接库把最后的指令都包含在最终生成的EXE文件中了:动态链接库不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件. (2)静态链接库中不能再

DLL的概念、dll导出类(转贴)

1. DLL的概念DLL(Dynamic Linkable Library),动态链接库,可以向程序提供一些函数.变量或类.这些可以直接拿来使用.静态链接库与动态链接库的区别:(1)静态链接库与动态链接库都是共享代码的方式.静态链接库把最后的指令都包含在最终生成的EXE文件中了:动态链接库不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件.(2) 静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库.

VC++动态链接库(DLL)编程深入浅出(zz)

1.概论 先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量.函数或类.在仓库的发展史上经历了"无库-静态链接库-动态链接库"的时代. 静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了.但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以"动态"地引用和卸载这个与E