65.9K
CodeProject 正在变化。 阅读更多。
Home

ActiveX事件与MFC状态

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.51/5 (9投票s)

2004 年 5 月 21 日

8分钟阅读

viewsIcon

101250

downloadIcon

2501

本文介绍了如何在接收ActiveX事件时确保MFC状态得到正确维护。

引言

本文描述了在 MFC 应用程序中处理 ActiveX 事件时可能遇到的一些问题。虽然有很多文章展示了如何接收 ActiveX 事件,但很少提及正确维护 MFC 状态的细节。如果您想在事件接收器中使用 AfxGetApp()AfxGetResourceHandle() 之类的调用,维护 MFC 状态至关重要。

尽管每当您在不同模块之间进行调用或回调时,MFC 状态的概念都会成为一个问题,但本文将重点关注 ActiveX 事件的情况。

背景

本文假设您熟悉 ActiveX 事件以及连接到事件源所需的基本概念。要回顾此主题,请参阅文章 "VC 客户端用于 VB ActiveX DLL"

此外,本文仅在您开发用于内部使用 MFC 的 ActiveX 事件源的 MFC 客户端时才相关。

MFC 状态

MFC 使用三种不同类型的状态信息。

  • 模块
  • 进程
  • 线程

本文仅关注第一种,即模块状态。尽管当前模块状态是按线程维护的,但本文将侧重于单线程情况。有关 MFC 状态的深入讨论,请参阅 George Shepherd 和 Scot Wingo 的著作 《MFC Internals, Inside the Microsoft Foundation Class Architecture》

跨越模块边界发生在代码执行调用另一个 DLL、ActiveX 控件,甚至 ActiveX 控件回调到应用程序时。后者发生在触发 ActiveX 事件时。每当跨越 MFC 模块边界时,必须通知 MFC 框架,以便模块状态保持最新。否则,任何特定于状态的调用都将引用前一个模块的状态。下图演示了这一概念。

在这个简单的例子中,MFC 应用程序(MainApp)调用了一个 MFC ActiveX 控件。发生这种情况时,必须更改当前模块状态。接下来,ActiveX 控件触发了 MainApp 已连接的事件。再次,当转换回 MainApp 时,必须更改当前模块状态。

MFC 使用模块状态的概念来跟踪当前 CWinApp 对象和当前实例句柄等内容。为了正确地与 MFC 交互,您需要确保保持状态的正确同步。

例如,许多 MFC 程序员使用的一个常见技巧是使用 AfxGetApp() 函数检索当前的 CWinApp 对象指针。然后将返回的指针转换为自己的应用程序对象,并使用该指针调用一些自定义方法。

//
// Example code that counts on MFC state to get the current WinApp object
//
CMainApp *pApp = (CMainApp *) AfxGetApp();
pApp->DoSomething(); 

此代码依赖于 MFC 状态。在内部,AfxGetApp 使用当前模块状态检索 App 对象指针。如果模块状态不正确,那么从 AfxGetApp 返回的指针将指向错误的 App 对象,并且调用可能会失败、损坏内存甚至发生访问冲突。

另一个依赖于 MFC 状态的情况是尝试加载资源时。当您按 ID 加载资源时,通常希望从当前模块的资源中加载它。CString::LoadString 函数就是这样一个例子。此函数将根据 ID 从当前模块加载字符串(由当前状态确定)。如果模块状态不正确,将加载错误的字符串,或者根本找不到字符串资源。

如何管理 MFC 状态

每当进行跨越 MFC 模块边界的调用时,您可以使用 AFX_MANAGE_STATE 宏来更新 MFC 状态。此信息在函数返回之前都有效,函数返回后,以前的状态会自动恢复(状态在函数返回时从堆栈中弹出)。此函数接受指向 AFX_MODULE_STATE 结构的指针。通常,当您在外部 DLL 中编写代码时,将使用 AfxGetStaticModuleState() 函数来提供此指针。

在使用 Visual Studio 时,在大多数情况下,当您创建一个支持 MFC 的模块(DLL、ActiveX 控件或组件)时,当您添加新方法或导出函数时,AFX_MANAGE_STATE(AfxGetStaticModuleState()) 调用将自动为您插入。向导会将宏添加到函数顶部。从那时起,只要您继续在同一模块内进行调用,就不需要更改模块状态。另一方面,如果您再次跨越模块边界,也许是为了调用另一个支持 MFC 的 DLL,则必须更新 MFC 状态。

注意:状态管理规则存在一些例外情况,这使得该主题更加混乱。静态链接到 MFC 的常规 DLL 和 MFC 扩展 DLL 不应使用 AFX_MANAGE_STATE。有关更多详细信息,请参阅 MSDN 文章“动态链接到 MFC 的常规 DLL”。

ActiveX 事件

最后我们达到了感兴趣的主题。如前一节所述,我们必须确保在每次跨越模块边界时都正确维护 MFC 状态。当触发 ActiveX 事件时,这正是发生的情况。因此,我们必须确保在客户端(ActiveX 事件接收器)中维护 MFC 状态。

有几种接收 ActiveX 事件的方法。在我之前提到的文章 "VC 客户端用于 VB ActiveX DLL" 中,作者提出了两种不同的方法。

  • MFC 客户端
  • ATL 客户端
接下来的章节将讨论不同的选择。

MFC ActiveX 事件接收器

此方法涉及创建一个具有自动化支持的 CCmdTarget 派生类。然后添加方法以匹配触发的事件源例程并按需处理事件。

此方法的美妙之处在于您无需执行任何操作来管理 MFC 状态!MFC 基类 CCmdTarget 在您的代码在事件源和事件接收器之间转换时(在 COleDispatchImpl::Invoke 例程中)自动处理状态更改管理。CCmdTarget 知道基于对象构造时存储的状态的正确模块状态。

您可以使用本文随附的演示程序来验证这一点。有关详细信息,请参阅 CMfcEventSink 类。

ATL ActiveX 事件接收器

此方法涉及创建一个 IDispEventSimpleImpl 派生类。再次,必须添加方法以匹配事件源。

与 MFC 方法不同,在这种情况下,您负责自己管理 MFC 状态。要做到这一点,您应该使用前面讨论的 AFX_MANAGE_STATE 宏。但在这种情况下,由于事件接收器对象在主应用程序中实例化,因此使用 AfxGetAppModuleState() 函数来提供指向 AFX_MODULE_STATE 结构的指针。

例如,在主应用程序中构造的对象的一个典型事件接收器函数应以以下代码开头。

//
// Make sure the MFC state is correct.
//
         AFX_MANAGE_STATE(AfxGetAppModuleState())    
//
//       The rest of your code...
//

如前所述,当此函数返回时,MFC 状态将自动恢复。

演示程序在 CAtlEventSink 类中说明了此技术的用法。

演示程序

本文随附了一个演示项目,允许您使用调试器单步调试代码并理解 MFC 状态管理。演示程序包含两个项目:一个主 MFC 程序和一个 ATL ActiveX 控件。MFC 主程序实现为一个简单的对话框应用程序。

为上述 MFC 和 ATL 方法分别提供了一个事件接收器类示例。这些类分别命名为 CMfcEventSinkCAtlEventSink

为了触发事件,ActiveX 控件实现了一个接口 - IEventControlEvents。该控件有一个方法 - TestMethod - 该方法触发一个事件 - TestEvent。标有“Fire Events”的按钮调用控件的 TestMethod 函数。

CMfcEventSinkCAtlEventSink 类都有一个 OnTestEvent 方法,该方法在事件触发时被调用。在此方法中,使用 CWinApp 对象对 MFC 状态进行简单的测试。然后显示一个消息框,指示 MFC 状态是否正确。

事件接收器对象在主程序的 CMainDlg::OnInitDialog() 例程中实例化并连接。

您可以修改代码来理解函数调用之间 MFC 状态的变化。

要运行主程序,您必须首先使用 Regsvr32 注册该控件(control.dll)。或者,如果您重新构建项目,该控件将在构建过程中自行注册。

除了手动创建的两个事件接收器之外,我还使用控件的容器站点代码挂接了控件的事件。这是使用 MFC ClassWizard 完成的。我添加这个只是为了说明最简单的情况。在这种情况下, MFC 状态管理由控件容器站点类为您处理。

结论

本文重点介绍了一些在使用 MFC 客户端接收其他 MFC 控件和组件的事件时可能遇到的问题。

我第一次遇到这个问题是在发布了一个 MFC 应用程序的 1.0 版本之后,该版本成功连接并处理了来自几个其他(非 MFC)组件的事件。事件接收器代码没有问题,一切都按预期运行。

然后 came version 1.1。我们添加了第一个 MFC 组件。添加新组件后,之前运行正常的现有代码中立即出现断言。经过大量研究,我发现我们的事件接收器遇到了 MFC 状态问题。

希望本文能帮助您避免同样的问题。

历史

  • 2004年5月15日 原始
© . All rights reserved.