更高效地在 MFC 中使用 ActiveX 控件






3.60/5 (9投票s)
增强自动代码生成提供的接口访问级别。
引言
各位好!欢迎来到本文。我写文章有一段时间了,所以如果有拼写错误或其他错误,请务必告知我,以便我进行修正。
背景
Code Project 上已经有一些文章介绍了在 MFC 程序中使用 ActiveX 控件的基本用法。如果您不熟悉如何将 ActiveX 控件添加到 MFC 项目中,我推荐 Hazem Nasereddin 的文章 ActiveX 控件使用示例:将 Internet Explorer 插入对话框。
至于本文的原因,我们稍后会再回来讨论。
包装 ActiveX 控件的问题
通过遵循上述文章,您可以轻松地将控件添加到您的程序中,并利用其基本功能。但是,许多控件还具有其他接口,可提供高级功能或设置。其中一些接口可能被隐藏,或者不可创建,因此基本的查询将无法正常工作。
为了举例说明,让我们继续 Mr. Nasereddin 的工作。我们在对话框中添加了 Web 浏览器控件,并且它运行良好。我们还在对话框类中添加了成员变量,以及用于通过 IWebBrowser2
及其基类提供的方法的 IDispatch
包装器类。此接口提供了大量可与控件进行操作的功能。
然而,在导航到某个多帧网页后,我们现在有一个任意的需求,就是找出该页面上所有帧的名称。有两种方法可以做到这一点:一种是使用 IWebBrowser2
接口获取文档,然后通过它进行搜索;另一种是设法访问浏览器的 ITargetFrame2
接口。
现在出现的问题是,为我们自动生成的包装器类没有提供一个可以用来获取 ITargetFrame2
接口的方法。那么,我们该如何进行呢?
深入了解 IUnknown
幸运的是,包装器类是从 CWnd
基类派生的。此类旨在容纳 ActiveX 控件,因此它恰好为我们提供了一个名为 CWnd::GetControlUnknown
的方法。此方法为我们提供了指向创建的 ActiveX 对象的 IUnknown
接口的指针的副本。
// Assuming that m_ctrlBrowser is the added member variable
// First, here is the IUnknown pointer
IUnknown* pUnk;
// Let's get the object's interface
pUnk = m_ctrlBrowser.GetControlUnknown();
现在,GetControlUnknown
返回的指针是 IUnknown
指针的副本。此指针由 MFC 框架不断用于维护对话框中的控件。毋庸置疑,如果您释放此接口指针,MFC 将无法再操作该控件,如果碰巧它是该对象上最后一个接口指针,则该对象甚至会自行关闭(在所有接口都已释放后自毁)。
因此,此接口指针不应该被释放。当您不再需要该指针时,将其设置为 NULL
即可。
脱离困境的方法
那么,我们用这个指针能做什么呢?很简单:我们现在可以完全访问对象包含的每个接口,无论它们是否可创建。我们在 MFC 框架为我们打开的层级上操作,简单来说,我们对该对象拥有完全的访问权限。
让我们开始访问 ITargetFrame2
接口。遵循基本的 COM 函数调用,可以通过发出以下命令来实现:
// The interface pointer
ITargetFrame2* pFrame;
// Query for it
HRESULT hr = pUnk->QueryInterface( __uuidof( ITargetFrame2), (void**) &pFrame );
嘿!那个 __uuidof()
调用是什么?这个巧妙的函数允许您在所有包含的模块中搜索您知道名称的接口的 GUID。大多数类型库(或从类型库生成的头文件)都通过定义 IID_*
变量来标识接口 GUID。但并非所有情况都如此。这个函数就是为了应对那些“这次不行”的情况。
最后一步,结论
我们到这里了。我们现在拥有一个有效、可用的接口指针,可以用来对我们的 Web 浏览器控件进行任意操作。接下来做什么选择权在您。您可以使用 ITargetFrame2
接口指针,或者您可以查询浏览器控件中您需要的另一个接口。
本文的关键点在于展示了访问 IUnknown
接口以及通过它访问对象所有其他接口所需的步骤。这种方法的最大优点是返回的 IUnknown
指针不是新创建的指针,而是现有指针的副本。因此,对象拥有的所有接口都变得可用,无论它们是否可创建。只需记住,您从 IUnknown
指针查询的所有其他接口指针都应正常释放。
要访问不可创建接口的示例,请在您的对话框中添加一个 Microsoft RDP 客户端控件。该控件默认只提供对其接口的非常有限的访问权限,通过遵循此方法,我们可以访问其所有接口。甚至可以自动登录到终端服务器这一棘手的过程。