AFX_MANAGE_STATE(AfxGetStaticModuleState())讲解

2023-10-30

以前写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 isan 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 avoidWindows 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_DATADEFCThreadLocal<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 stateserves 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();

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

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AFX_MANAGE_STATE(AfxGetStaticModuleState())讲解 的相关文章

  • Python 中的六边形自组织映射

    我在寻找六边形 自组织映射 http en wikipedia org wiki Self organizing map在Python上 准备好模块 如果存在的话 绘制六边形单元格的方法 将六边形单元作为数组或其他方式使用的算法 About
  • MFC:如何设置CEdit框的焦点?

    我正在开发我的第一个简单的 MFC 项目 但我正在努力解决一个问题 想要设置所有的焦点CEdit其中一个对话框中的框 我的想法是 当打开对话框时 焦点位于第一个编辑框上 然后使用 选项卡 在它们之间交换 我看到了方法SetFocus 但我无
  • Python MySQL 模块

    我正在开发一个需要与 MySQL 数据库交互的 Web 应用程序 但我似乎找不到任何真正适合 Python 的模块 我特别寻找快速模块 能够处理数十万个连接 和查询 所有这些都在短时间内完成 而不会对速度产生重大影响 我想我的答案将是游戏领
  • 在Framework 4.6项目中使用.net core DLL

    我已经在 net core 2 0 中构建了一个 DLL 现在我想在使用 net 4 6 1 框架的 WinForms 项目中使用它 我可以引用该 dll 但收到 System IO FileLoadException 表示找不到 Syst
  • 模块路径格式错误...第一个路径元素中缺少点

    我有一个包含 2 个不同可执行文件的项目 每个可执行文件都有自己的依赖项以及对根的共享依赖项 如下所示 Root gt server gt main go gt someOtherFiles go gt go mod gt go sum g
  • 使用 Python-VLC 的 PyInstaller:无属性“media_player_new”错误

    我使用 Python VLC 创建视频播放器 并使用 PyInstaller 在 Windows 10 计算机上生成可执行文件 最初 它给了我错误 Import Error Failed to load dynlib dll libvlc
  • 如何获取通过网络驱动器访问的文件的 UNC 路径?

    我正在 VC 中开发一个应用程序 其中网络驱动器用于访问文件 驱动器由用户手动分配 然后在应用程序中选择驱动器 这会导致驱动器并不总是映射到相同的服务器 我该如何获取此类文件的 UNC 路径 这主要是为了识别目的 这是我用来将普通路径转换为
  • 在 Android 库项目中禁用 Crashlytics 进行调试

    我有一个包含多个模块的项目 模块的公共代码位于库模块中 问题是我们最近将 Crashlytics 添加到了我们的项目中 在库模块中 即使我们处于调试模式 我们也会不断收到错误报告 我在网上搜索了一下 发现库总是被视为Release模式 现在
  • 将 Visual Studio 2012 C++ 单元测试项目链接到 exe 会导致访问冲突

    我从现有的整体 exe 本机 Visual Studio 2012 项目开始 我想添加一个本机单元测试项目 根据http msdn microsoft com en us library hh419385 aspx objectRef ht
  • MSBuild 命令行 - 添加 dll 引用

    我使用 makefile 来编译我的 C 项目 在这个makefile中 我创建了一个库tools dll 调用csc exe OK 现在 我想在我的项目中使用这个 dll 由于某些原因 我必须使用使用 csproj 文件的 MSBuild
  • 在 Windows 安装项目中注册和取消注册 DLL

    我有几个 dll 文件需要在安装 卸载 Windows 安装程序时分别注册 取消注册 我尝试了以下方法 创建一个 bat 文件来注册 dll 问题是我无法在安装项目中使用 自定义操作 添加 bat 文件 另外 如何在卸载时运行注销dll 请
  • 为什么“[DllImport]”会因“RtlSecureZeroMemory”入口点而失败,即使它是一个有据可查的入口点?

    尝试使用kernel32函数SecureZeroMemory 使用下面的代码失败了 System EntryPointNotFoundException 尽管有详细记录在这里 在 PInvoke 上 https www pinvoke ne
  • 无法加载 DLL(找不到模块 HRESULT:0x8007007E)

    我有一个 dll 库 其中包含我需要在 NET 4 0 应用程序中使用的非托管 C API 代码 但我尝试加载 dll 的每种方法都会出现错误 无法加载 DLL MyOwn dll 找不到指定的模块 HRESULT 异常 0x8007007
  • 跨 dll 边界的内存分配和释放

    我知道在一个 dll 中进行内存分配然后在另一个 dll 中释放内存可能会导致各种问题 尤其是与 CRT 相关的问题 当涉及到导出 STL 容器时 此类问题尤其成问题 我们之前遇到过此类问题 在编写与我们的库链接的自定义 Adob e 插件
  • 是否有与 gcc --kill-at 等效的 Visual C++?

    也就是说 DLL 名称末尾有一个额外的 8 这会造成问题 显然 在 gcc 中使用 kill at 标志可以解决这个问题 但我找不到任何类似的 MSVC 建议 编辑 更多信息 我试图让 C JNI dll 工作 但我总是得到 线程 Thre
  • 通过命令行参数选择要使用的 ocaml 模块

    在我的代码中我有module M Implementation1然后我参考M 代替Implementation1 问题是 我必须重新编译我的程序才能改变Implementation1 to Implementation2 我想通过命令行参数
  • 从内存加载动态库

    是否可以从内存而不是从 mac gcc 上的文件系统加载库 在 Windows 中 我使用 MemoryModule 但它显然不跨平台兼容 首先 要做到这一点 我建议您阅读OS X ABI 动态加载器参考 https developer a
  • Pandas datetools模块错误

    我正在尝试从 pandas datetools 调用模块 但收到错误消息 指出 mofule 对象没有我所调用的名称的属性 想知道是否有人可以阐明这个问题 下面是我尝试使用的代码 import blpapi import pandas as
  • MFC:从另一个线程调用 CWnd 方法安全吗?

    其实我有两个问题 打电话安全吗SendMessage来自工作线程 Do CWnd方法 比如MessageBox 调用API函数SendMessage幕后 根据我的理解 当工作线程调用时SendMessage 它将消息推送到UI线程的消息队列
  • 如何将tchar指针转换为char指针

    我想将 tchar 转换为 char 这可能吗 如果是的话该怎么做 我使用unicode设置 A TCHAR要么是一个普通的char or a wchar t取决于您的项目的设置 如果是后者 您需要使用WideCharToMultiByte

随机推荐

  • python调用搜狗OCR接口实现图片文字识别

    import requests multiple files pic 1111111 jpg open r QQ截图20180905172943 jpg rb image jpg resp requests post r http ocr
  • 自适应控制---自校正PID控制器

    PID算法 其中e 期望输出 实际输出 自校正PID控制器参数的确定 注 F q 1 中有 1 f1q 部分是为了提高分母的次数 便于实现 PID自校正控制算法 对于PI或者P控制 只要将对应的系数去掉即可
  • Android:开启一个服务循环ping服务器,记录ping 5次均失败触发某条件

    import android app Service import android content Intent import android os Handler import android os IBinder import andr
  • SpringMVC(07) -- RESTful

    SpringMVC学习笔记 源码地址 7 1 RESTful简介 REST Representational State Transfer 表现层资源状态转移 7 1 1 资源 资源是一种看待服务器的方式 即 将服务器看作是由很多离散的资源
  • 3、数组——二维数组中的查找(python版)

    剑指Offer 题目描述 在一个二维数组中 每个一维数组的长度相同 每一行都按照从左到右递增的顺序排序 每一列都按照从上到下递增的顺序排序 请完成一个函数 输入这样的一个二维数组和一个整数 判断数组中是否含有该整数 重要信息 二维数组 每行
  • Java合并两个有序的整数数组

    题目描述 给出两个有序的整数数组 A和B 请将数组B 合并到数组 A中 变成一个有序的数组 注意 可以假设A 数组有足够的空间存放 B数组的元素 A和 B中初始的元素数目分别为 m和Nn public class Solution publ
  • 泛微oa主表赋值明细表_Java学习第89天--OA系统

    学习主题 OA系统 学习目标 1 掌握web开发项目实战 熟练使用web开发基础技术 对应作业 1 报销管理 添加报销单 业务层 1 在报销主表和明细表中都有一个字段叫expid 这个字段在数据库中是利用序列获取的 但是在业务层中 我们要控
  • 将postgresql数据库内容导出至sqlite数据库

    上周为应对去外地投标演示网站demo时可能没网的问题 经理让我把远程服务器的postgresql数据库上的数据导出至access或sqlite数据库中 几经波折 终于完成 现将过程记录如下 demo所用S2SH框架 经查询了解hiberna
  • (python 毕业设计)基于“协同过滤”算法的订餐推荐小程序

    B站小程序演示视频 https www bilibili com video BV1Lg411D7mP spm id from 333 337 search card all click 总体架构 核心功能 美食推荐 根据学号查询该学生的消
  • 在linux中DATAX和DATAX-WEB安装指引

    DATAX介绍 DataX 是一个异构数据源离线同步工具 致力于实现包括关系型数据库 MySQL Oracle等 HDFS Hive ODPS HBase FTP等各种异构数据源之间稳定高效的数据同步功能 DATAX WEB介绍 DataX
  • 论文总结——Cluster Canonical Correlation Analysis

    原文链接 http xueshu baidu com s wd paperuri ba0044ede74ce3a08eb2f83cc970284b filter sc long sign sc ks para q 3DCluster Can
  • Mac OS 修改ROOT账户密码

    方法一 首先 启动机器 启动时按住Apple和S键 以单用户模式 single user mode 进入系统 输入 mount uw 然后回车 输入 passwd 短用户名 如果你知道的话 如果你不知道短用户名 可以输入 passwd ro
  • MySQL 复合查询 && 内外连接

    目录 基本查询回顾 多表查询 自连接 group by可以带多个 子查询 在from子句中使用子查询 合并查询 表的内连和外连 内连接 外连接 左外连接 右外连接 关于自连接和内连接 chatgpt 基本查询回顾 查询工资高于 500 或岗
  • 线性代数——分块矩阵计算行列式的方法

    https blog csdn net wwxy1995 article details 104477088
  • 406什么错误ajax,ajax406错误

    如上 ajax请求时一直返回error 但是后台已经正确返回 网上给出的解决办法是spring3 的 但我的是sppring 4 的 应该不适用 我也没试 思索一下 406 not acceptable 直译过来是不接受 不接受什么呢 后台
  • 下载nrm,不能切换镜像源

    需要管理一下npm下载源的 打算用nrm管理的 结果可以成功下载下来nrm 但是不能nrm ls 查看所有源 看了看报错 search了几下 以为是报错的那个路径文件有问题 但是看这个文件和search的结果里改的文件 多少有些不一样 报错
  • 使用boost::gil模块进行像素重采样的数字扩展示例(C/C++)

    使用boost gil模块进行像素重采样的数字扩展示例 C C 在图像处理中 像素重采样是一种常见的操作 它可以改变图像的分辨率或者调整图像的大小 在C 中 Boost库提供了Gil模块 其中包含了一些用于图像处理的功能 包括像素重采样 本
  • 数据库表中有多个“主键“

    数据库表中的多个主键称为联合主键 sql 中一个表设置两个主键是 将两个字段联合起来设置为主键 一个表只能有一个主键 1 只有id为主键时 2 id和name这2个列一起构成为联合主键时 甚至可以全部字段设置为主键
  • 11.14 Python __file__属性:查看模块的源文件路径

    前面章节提到 当指定模块 或包 没有说明文档时 仅通过 help 函数或者 doc 属性 无法有效帮助我们理解该模块 包 的具体功能 在这种情况下 我们可以通过 file 属性查找该模块 或包 文件所在的具体存储位置 直接查看其源代码 仍以
  • AFX_MANAGE_STATE(AfxGetStaticModuleState())讲解

    以前写MFC的DLL的时候 总会在自动生成的代码框架里看到提示 需要在每一个输出的函数开始添加上AFX MANAGE STATE AfxGetStaticModuleState 一直不明白这样做的含义 也一直没有这样做 而且代码也工作得好好