以前写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 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();

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

MFC DLL 资源模块句柄切换[转]的更多相关文章

  1. MFC DLL资源动态切换

    在MFC使用过程中,遇到DLL资源与主EXE资源冲突问题. 出现这样的Bug,一时无从下手. 报错位置在核心代码中dlgcore.cpp. [cpp] view plaincopy BOOL AFXA ...

  2. Win32 DLL和MFC DLL 中封装对话框

    现在最常看见的关于DLL的问题就是如何在DLL中使用对话框,这是一个很普遍的关于如何在DLL中使用资源的问题.这里我们从Win32   DLL和MFC   DLL两个方面来分析并解决这个问题.     ...

  3. MFC的资源切换AFX_MANAGE_STATE(AfxGetStaticModuleState()

    转载自:http://blog.chinaunix.net/uid-20532101-id-1931929.html 以前写MFC的DLL的时候,总会在自动生成的代码框架里看到提示,需要在每一个输出的 ...

  4. 如何制作带MFC界面的MFC DLL

    最近在做基于组件化MFC界面的开发,需要把界面封装到动态库中. 一:工程创建步骤 1.创建MFC DLL工程,选择 “在共享 DLL 中使用 MFC”. 2.运行时库选择:c/c++-->代码生 ...

  5. MFC DLL 可以封装MFC的窗体 供别的MFC程序使用

    MFC DLL  可以封装MFC的窗体 供别的MFC程序使用 在庞大程序分工里面 非常可取. 可以细分每个窗体就是单独的 模块. [后续不断完善]

  6. MFC获取各类指针句柄

    最近有些人在问MFC编程一些要点,有一些句柄的获取.指针的获取是常见的问题,本文将对这些问题做以解释,参考了前人的笔录(见reference),希望能够帮助大家更方便地进行MFC程序开发. 一般我们使 ...

  7. Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)

    Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...

  8. 【boost】MFC dll中使用boost thread的问题

    项目需要,在MFC dll中使用了boost thread(<boost/thread.hpp>),LoadLibraryEx的时候出现断言错误,去掉thread库引用后断言消失. 百度g ...

  9. Win32之隐藏DLL隐藏模块技术

    Win32之隐藏DLL隐藏模块技术 这一讲涉及到windows底层技术.跟汇编内容. 我们才可以实现模块隐藏(也称为DLL隐藏) 一丶API反汇编勾引兴趣 我们都用过Windows的进程跟线程API  ...

随机推荐

  1. 68. Text Justification

    题目: Given an array of words and a length L, format the text such that each line has exactly L charac ...

  2. java工具类–自动将数据库表生成javabean

    最近和数据库的表打交道挺多的,因为暂时做的是接口活. 在这过程中发现要把表转换成对应的javabean类型,字段少的表还行,如果不小心碰到几十个字段的他妈的写起来就有点麻烦了,万一碰到几百个的呢,那不 ...

  3. ios ableviewcell的动态加载数据,模仿喜马拉雅动态数据加载

    iphone(UITableViewCell)动态加载图片http://developer.apple.com/library/ios/#samplecode/LazyTableImages/Intr ...

  4. POJ 3252 Round Numbers(组合)

    题目链接:http://poj.org/problem?id=3252 题意: 一个数的二进制表示中0的个数大于等于1的个数则称作Round Numbers.求区间[L,R]内的 Round Numb ...

  5. 【HDOJ】5632 Rikka with Array

    1. 题目描述$A[i]$表示二级制表示的$i$的数字之和.求$1 \le i < j \le n$并且$A[i]>A[j]$的$(i,j)$的总对数. 2. 基本思路$n \le 10^ ...

  6. Python错误处理

    中文注释出错,把UTF-8标明放在文件头就行了 # coding: utf-8

  7. POJ 1753 Flip Game (高斯消元 枚举自由变元求最小步数)

    题目链接 题意:4*4的黑白棋,求把棋全变白或者全变黑的最小步数. 分析:以前用状态压缩做过. 和上题差不多,唯一的不同是这个终态是黑棋或者白棋, 但是只需要把给的初态做不同的两次处理就行了. 感觉现 ...

  8. axis : java.lang.NoSuchMethodError

    Hi friends,Iam getting the following error when deploying my app in jboss error.Iam new to axis .can ...

  9. bzoj4154

    一开始读错题,各种不会做,后来发现染色只是染孩子…… 那不就简单了吗……注意这题是允许离线的 染色如果没有距离限制,它就是个dfs序 距离限制怎么做呢?我们考虑扩展一维变成二维的问题,将每个点变为二维 ...

  10. LA 4119 (差分数列 多项式) Always an integer

    题意: 给出一个形如(P)/D的多项式,其中P是n的整系数多项式,D为整数. 问是否对于所有的正整数n,该多项式的值都是整数. 分析: 可以用数学归纳法证明,若P(n)是k次多项式,则P(n+1) - ...