AFX_MANAGE_STATE(AfxGetStaticModuleState()) (转自优快云:何先生)

本文详细解析了MFC中AFX_MANAGE_STATE(AfxGetStaticModuleState())的作用及使用方法,阐述了在DLL环境下如何正确管理和切换资源,确保跨DLL调用时资源句柄的有效性。

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

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


本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/hejianhua/archive/2008/05/30/2495606.aspx

 

总结:

AFX_MANAGE_STATE(AfxGetStaticModuleState())

AFX_MANAGE_STATE(AfxGetStaticModuleState());//用于模块切换时的状态保护,

1.AfxGetStaticModuleState()指向当前模块状态;

2.当前函数调用结束后原模块的状态自动被恢复;

3.用于DLL中所调用MFC函数、类、资源时的模块状态切换

AFX_MANAGE_STATE的作用切换到指定的Module State,当出了作用域的时候将Module State恢复到原来的值。是在不同的Module State之中切换,原因有2:

1.       在不同的MFC DLL和MFC EXE的Module State之间切换,保持正确的AFX_MODULE_STATE,最常见的问题是在DLL输出的函数之中无法获得DLL本身相关的资源,这就是没有正确维护Module State的原因造成的,因为当前Resource DLL的句柄就保存在Module State之中。

2.       切换Activation Context,不同的Module必然有着不同的Activation Context,需要切换。
一般的用法如下:

void SomeMFCDllFunction()

{

AFX_MANAGE_STATE(AfxGetStaticModuleState())


注意这里使用的是AfxGetStaticModuleState,而非AfxGetModuleState。原因是在DLL项目中,AfxGetStaticModuleState返回的是DLL本身的Module State,而AfxGetModuleState则是返回当前线程相关的Module State,由于一般DLL输出的函数是被其他Module调用,那么大部分情况下当前线程的Module State都是错误的,所以必须得使用DLL本身的Module State。

<think>我们正在讨论MFC中的AFX_MANAGE_STATEAfxGetStaticModuleState。根据引用,这些用于模块状态切换,特别是在DLL中。用户要求解析其用途和代码工作原理。首先,AFX_MANAGE_STATE一个宏,用于管理MFC模块的全局状态。MFC使用模块状态来跟踪资源句柄、当前应用程序实例等。当在DLL调用MFC函数时,需要确保使用正确的模块状态,否则可能访问错误的资源(例如,使用应用程序的资源而不是DLL的资源)。AfxGetStaticModuleState函数返回指向当前模块状态的指针。在DLL中,通常使用它来获取DLL的模块状态。AFX_MANAGE_STATE宏接受一个指向AFX_MODULE_STATE的指针,通常通过AfxGetStaticModuleState()获取。这个宏的作用是在进入函数时设置当前模块状态为指定的状态,并在退出函数时恢复之前的模块状态。这样,在函数执行期间,所有MFC操作(如加载资源)都会使用正确的模块状态。根据引用[5],在函数堆栈中,只需要在最外层入口使用一次即可,避免嵌套使用。现在,我们来看代码工作原理:1.AfxGetStaticModuleState()的实现:```cppAFX_MODULE_STATE*AFXAPIAfxGetStaticModuleState(){AFX_MODULE_STATE*pModuleState=&afxModuleState;returnpModuleState;}```这个函数返回一个指向全局变量`afxModuleState`的指针,该变量代表DLL的模块状态。2.AFX_MANAGE_STATE宏的定义:```cpp#defineAFX_MANAGE_STATE(p)\AFX_MAINTAIN_STATE2_ctlState(p)```这个宏实际上声明了一个名为`_ctlState`的AFX_MAINTAIN_STATE2类型的局部变量。该类的构造函数保存当前的模块状态并设置新的状态,析构函数恢复原来的状态。因此,当我们在函数开始时写:```cppAFX_MANAGE_STATE(AfxGetStaticModuleState());```相当于:```cppAFX_MAINTAIN_STATE2_ctlState(AfxGetStaticModuleState());```这样,在函数执行期间,模块状态被设置为DLL的模块状态,函数结束后自动恢复。用途:在DLL导出的函数中,如果该函数会使用MFC资源(如对话框、字符串等)或调用MFC函数,必须使用此宏来确保资源查找在DLL的模块中进行,而不是在调用者(应用程序)的模块中。如果不使用,可能会出现资源加载错误(如对话框显示为空白或错误字符串),因为MFC默认使用主应用程序的资源句柄。引用[4]中的例子说明,如果不加,可能会使用应用程序的资源而不是DLL的资源,导致问题。总结:-AFX_MANAGE_STATE(AfxGetStaticModuleState())用于在DLL导出函数中切换模块状态,确保使用DLL自己的资源。-它通过一个局部变量(AFX_MAINTAIN_STATE2)在构造函数中设置新状态,析构函数恢复旧状态,实现自动状态管理。-只需在DLL导出函数的入口点使用一次。相关问题:1.在什么情况下必须使用AFX_MANAGE_STATE宏?2.如果不使用AFX_MANAGE_STATE宏,可能会遇到什么问题?3.AFX_MANAGE_STATE宏如何实现模块状态的自动恢复?4.在MFCDLL中,除了AFX_MANAGE_STATE,还有哪些方法管理模块状态?</think>### AFX_MANAGE_STATEAfxGetStaticModuleState解析 #### 功能用途 在MFC模块化开发中(特别是DLL开发),`AFX_MANAGE_STATE(AfxGetStaticModuleState())` 用于**模块状态切换**,核心解决资源定位问题: 1. **资源隔离**:确保DLL使用自己的资源(对话框/字符串等),而非调用方EXE的资源[^3][^4] 2. **状态管理**:自动保存和恢复模块状态,避免资源句柄冲突[^1][^5] 3. **多模块支持**:使DLL能独立使用MFC类库和资源[^2] #### 代码工作原理 ```cpp // 1. 获取当前模块状态 AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState() { AFX_MODULE_STATE* pModuleState = &afxModuleState; return pModuleState; // 返回DLL的全局状态对象 } // 2. 状态切换宏(简化版) #define AFX_MANAGE_STATE(pState) \ AFX_MAINTAIN_STATE2 _state(pState); // 3. 实际应用场景 extern "C" __declspec(dllexport) void ShowDlg() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // 状态切换 CMyDlg dlg; // 此时使用的资源来自DLL dlg.DoModal(); // 而非调用方EXE } ``` #### 执行机制 1. **进入函数时**: - 保存当前模块状态(通常为EXE状态) - 将新状态设置为`AfxGetStaticModuleState()`返回DLL状态[^1][^3] 2. **资源访问**: - `CDialog::DoModal()`等操作自动使用DLL的资源句柄[^4] 3. **退出函数时**: - 通过`AFX_MAINTAIN_STATE2`析构函数自动恢复原始状态[^5] > ⚠️ **关键设计**:使用RAII(资源获取即初始化)技术,通过局部对象`_state`的构造/析构实现状态自动切换和恢复[^3][^5]。 #### 使用规范 1. **作用域**:只需在DLL导出函数**最外层**使用一次[^5] 2. **嵌套禁止**:避免在已使用宏的函数内再次调用其他使用宏的函数 3. **典型场景**: - DLL中创建对话框/窗口 - 加载DLL专属资源(图标/字符串) - 调用MFC类库函数 > 示例:未使用宏时,`AfxMessageBox("hello")`可能显示EXE的资源导致乱码;使用后正确加载DLL资源[^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值