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

Microsoft XML 解析器 (MSXML) COM 接口的 C++ 包装类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (29投票s)

2002年1月2日

9分钟阅读

viewsIcon

613175

downloadIcon

6977

提供的 MFC 扩展 DLL 为 Microsoft 的 DOM/SAX 实现的所有 COM 接口提供了易于使用的包装器类。

目录

  1. 引言
  2. 什么是 MSXMLCPP
  3. 概念
  4. XML SAX 包装器节点
  5. 缺点
  6. 要求
  7. Unicode 支持
  8. 包装器类是如何生成的
  9. 代码示例

引言

无论您是想进行进程间通信、存储应用程序的设置或文档,还是格式化文本 - XML 都为所有这些情况提供了很好的解决方案。此外,Microsoft 还提供了 XML DOM(Document Object Model)和 SAX(Simple API for XML)的完整实现,它允许以抽象的方式读取和写入 XML:Microsoft XML 解析器 (MSXML)。

除了提供处理 XML 数据所需的所有功能的优势外,Microsoft XML 解析器还具有在所有未来的 Windows 系统上都可用的优势。对我们 C++ 开发者来说,缺点是 Microsoft XML 解析器通过 COM 接口提供了所有这些功能。

在 C++ 中使用 COM 接口总会产生大量的源代码开销 - 您必须处理 HRESULT 返回值,您必须为逻辑返回值提供变量,无论您是否关心它们,因为它们是通过参数列表传递的,您必须在零终止字符串和 BSTR 之间进行转换。您必须处理引用计数等问题。

只需看看下面的两个代码示例。它们都做了同样的事情:它们使用 Microsoft 的 XML DOM 实现来读取 XML 元素属性的值。

C++
CString GetAttributeValue(IXMLDOMDocument *pDoc, CString strElementPath, 
                                              CString strAttribute)
{
  IXMLDOMNode         *pNode = NULL;
  IXMLDOMNamedNodeMap *pAttributes = NULL;
  BSTR                bstrPath = strElementPath.AllocSysString();
  BSTR                bstrAttribute = strAttribute.AllocSysString();

  try
  {
    HRESULT hr;
    
    hr = pDoc->selectSingleNode(strElementPath, &pNode);
    if (hr != S_OK)
      throw hr;
  
    hr = pNode->get_attributes(&pAttributes);
    if (hr != S_OK)
      throw hr;
      
    hr = pAttributes->getNamedItem(bstrAttribute, &pNode);
    if (hr != S_OK)
      throw hr;
      
    BSTR  bstrValue;
    pNode->Release();
    hr = pNode->get_text(&bstrValue);
    if (hr != S_OK)
      throw hr;
    
    pNode->Release();
    pAttributes->Release();
      
    return (LPCTSTR)_bstr_t(bstrValue, false);
  }
  catch (HRESULT hr)
  {
    SysFreeString(bstrPath);
    SysFreeString(bstrAttribute);
    if (pNode)
      pNode->Release();
    if (pAttributes)
      pAttributes->Release();
      
    return _T("");
  }
}
Visual Basic
Function GetAttributeValue(Doc As IXMLDOMDocument, _
        strElementPath As String, strAttribute As String) As String
    GetAttributeValue = Doc.selectSingleNode(strElementPath)._
                     attributes.getNamedItem(strAttribute).text
End Function

这简直难以置信:两个代码示例做的都是同样的事情。好吧,不可否认 C++ 示例做了更好的错误处理 ;-)。但这里显示的差异清楚地表明了为什么许多人宁愿编写自己的 XML 类,而不是使用 Microsoft XML 解析器 - 其用法太笨拙。

什么是 MSXMLCPP

这就是 MSXMLCPP (MSXML C++) 的用武之地。MSXMLCPP 是一个 MFC 扩展 DLL,它为 Microsoft DOM 和 SAX 实现的所有接口提供了包装器类。

功能包括:

  • Microsoft DOM 和 SAX 实现的所有接口的 C++ 包装器类。
  • C++ 包装器类,用于更轻松地实现您必须传递给 Microsoft 实现的接口(例如 ISAXContentHandler)。
  • 包装接口的智能引用计数。
  • 包装器方法返回 *实际*(逻辑)返回值,而不是 HRESULT
  • C++ 异常用于报告意外的 HRESULT
  • BSTRCString 之间的自动转换。
  • VARIANT 到更方便的 _variant_t 的自动转换。

如果您曾梦想过编写

CString GetAttributeValue(CXMLDOMDocument &Doc, 
           CString strElementPath, CString strAttribute)
{
  return Doc.SelectSingleNode(strElementPath).GetAttributes().
                         GetNamedItem(strAttribute).GetText();
}

而不是上面多行的源代码示例,那么:嘿,欢迎来到您的解决方案。

概念

本节介绍 MSXMLCPP 的基本概念。

包装器类型

MSXMLCPP 提供三种包装器类:

  1. 调用包装器
  2. 实现包装器
  3. Coclass 包装器

调用包装器

调用包装器是您可以使用它们来调用特定类包装的接口方法的类。所有调用包装器都派生自模板类 CInterfaceCallingWrapper(定义在 InterfaceWrapper.h 中)。

在使用调用包装器之前,您必须将一个接口附加到一个类的实例。这可以通过构造函数、赋值运算符或显式使用 Attach() 方法来完成。在将新的接口指针分配给包装器之前,您必须始终通过调用 Detach() 方法来分离之前附加的接口。

调用包装器类处理附加的接口指针的引用计数,因此您在正常使用中无需关心它。这些类提供了多个运算符重载,允许您在任何地方使用包装器类,而不是原始接口指针。有关调用包装器的更多信息,请查看 InterfaceWrapper.hCInterfaceCallingWrapper 的良好文档化声明。

调用包装器类提供了所属 COM 接口提供的所有方法和属性,但存在一些细微差别:

  • 方法名称已适应 MFC 约定(以大写字母开头)。
  • 获取属性的方法以 Get 开头,设置属性的方法以 SetSetRef 开头。
  • 方法返回值反映操作的逻辑结果(MIDL-[retval] 属性)。
  • BSTR 参数被替换为 LPCTSTRBSTR* 参数被替换为 CString&BSTR 返回值被替换为 CString 返回值。
  • VARIANT 参数被替换为 _variant_tVARIANT* 参数被替换为 _variant_t&VARIANT 返回值被替换为 _variant_t 返回值。
  • 如果接口的方法返回一个指向已定义了包装器类的接口的指针,则包装方法将返回该包装器类的对象,而不是指针。

由于调用包装器类不返回任何 HRESULT 值,因此它们使用 C++ 异常机制来通知您原始接口方法返回的意外值。每次收到一个 HRESULT 值,且 SUCCEEDED 宏不返回 TRUE 时,都会抛出类型为 CComException* 的异常。

实现包装器

实现包装器是允许您通过继承实现包装器类并覆盖高层纯虚函数来简单实现所属接口的类。实现包装器派生自 CInterfaceImplementationWrapperCDispatchInterfaceImplementationWrapper,具体取决于相关接口是否派生自 IUnknownIDispatch

实现包装是通过聚合完成的:实现包装器类包含一个成员(m_x...),该成员是原始接口的实现。此实现的每个方法都会调用聚合类的虚拟方法 MethodPrologue(),然后是聚合类的相应虚拟包装器方法。这些包装器方法是您必须在派生类中实现的纯虚拟方法。它们与接口的原始方法相同,除了某些更改:

  • 方法名称已适应 MFC 约定(以大写字母开头)。
  • 获取属性的方法以 Get 开头,设置属性的方法以 SetSetRef 开头。
  • 方法返回值反映操作的逻辑结果(MIDL-[retval] 属性)。
  • BSTR 参数被替换为 LPCTSTRBSTR* 参数被替换为 CString&BSTR 返回值被替换为 CString 返回值。
  • VARIANT 参数被替换为 _variant_tVARIANT* 参数被替换为 _variant_t&VARIANT 返回值被替换为 _variant_t 返回值。

这使得实现比使用所有 BSTRVARIANTHRESULT 来实现原始接口要容易得多。

如果您将接口基于实现包装器,作为独立类来实现,CInterfaceImplementationWrapper 中实现的标准引用计数机制应该没问题,但如果您想将接口实现用作实现更多接口的类的一部分,或者作为 CCmdTarget 派生类的一部分,您将需要通过覆盖虚拟 AddRef()Release() 方法来修改引用计数。此外,您可以覆盖 QueryInterface() 方法和 MethodPrologue() 方法,这在大多数 MFC 应用程序中是必要的,以激活正确的模块状态(例如,调用 AFX_MANAGE_STATE(AfxGetStaticModuleState()) 是个好主意)。

由于您无法通过派生类中的 HRESULT 返回值来指示错误,因此您可以通过调用全局 AfxThrowComException() 来抛出 CComException - 实现包装器中的所有方法调用都封装在一个 try 语句中,并链接了一个 catch(CComException *pE)。如果您在实现中抛出 CComException,实现包装器将返回给定的 HRESULT 值给调用者,而不是通常的 S_OK

CoClass 包装器

CoClass 包装器是小型包装器类,允许您创建 COM 对象并检索指定的接口。这使您无需调用 CoCreateInstance()

CComException

由于 HRESULT 值被封装在包装器类中,因此有必要提供一个允许您的软件处理意外 HRESULT 的机制。因此,类 CComException 已在 InterfaceWrapper.h 中声明。该类派生自 CException,并包含一个类型为 HRESULT 的单个属性 m_hr

要抛出 CComException,您应该使用静态 CComException::Throw() 方法或全局 AfxThrowComException() 函数。它们都会在堆上创建一个 CComException 对象,将其 m_hr 属性设置为指定值,然后抛出指针。

如果您捕获了一个 CComException 对象的指针并且不再需要它,您应该调用其 Delete() 方法来销毁它。

XML SAX 包装器节点

对于 SAX 实现,Microsoft 提供了两种接口:C++ 接口和 Visual Basic 接口。由于我使用一个 工具 生成了所有包装器类,该工具分析每个方法的参数来生成方法包装器,因此我需要使用 Visual Basic 接口来获得“漂亮”的包装器类。C++ 接口(可能速度稍快)的问题在于,它们为每个字符串使用两个参数:字符串本身和一个长度参数。我不想修改生成工具来识别这种情况,因为这种情况非常罕见,所以我使用了速度稍慢的 Visual Basic 接口,它们使用正常的 BSTR,这可以被生成工具识别。

缺点

像所有好东西一样,MSXMLCPP 也有一个缺点:包装器类会产生一些时间开销用于所有类型转换和额外的函数调用。因此,如果您的应用程序需要非常快,最好处理原始接口,但我认为时间开销应该很小,足以抵消代码更易读的优势。

要求

要将 MSXMLCPP 库用于您的应用程序,只需将 msxmlcpp.h 头文件包含到您的 stdafx.h(或任何其他地方)中,然后链接到 msxmlcpp.lib(调试版本为 msxmlcppD.lib)。MSXMLCPP 使用 RTTI(运行时类型信息),因此最好也为您的应用程序启用它。

要运行使用 MSXMLCPP DLL 的应用程序,系统必须安装 Microsoft XML 解析器 (MSXML)。您可以从 Microsoft 主页下载它。

这应该能在 Windows 95 或更高版本以及 Windows NT 3.1 或更高版本上运行。

Unicode 支持

虽然我没有测试过,但没有理由不支持 Unicode。

包装器类是如何生成的

所有包装器类都是通过我自己的一个工具生成的,该工具以类型库和一些规则作为输入,并根据类型库中的信息生成包装器类。我希望能在几周内提供该工具的第一个免费 beta 版本,在 CodeProject 上。我认为这将使我们所有 C++ 程序员的工作更加轻松。

代码示例

随完整的下载提供了两个使用该库的代码示例。这两个示例都是简单的控制台应用程序,以便专注于有趣的内容。两个示例都将文件名作为命令行参数,用作 XML 文件 - 一个有效的文件(books.xml)包含在 ZIP 存档的 Output 目录中。

DOMXMLDemo 应用程序演示了如何使用 DOM 包装器类来读取和写入 XML 文件。

SAXXMLDemo 应用程序演示了如何使用 SAX 包装器类读取 XML 文件。这还展示了如何使用实现包装器类来实现接口。

© . All rights reserved.