MSXMLl 6.0 XmlDOMNodeList 在调用 GetEnumerator 时失败

MSXMLl 6.0 XmlDOMNodeList fails when is invoked GetEnumerator

我们的项目使用 MSXML 6.0 com 对象通过 ComImport 属性处理 XML。下面的 com Class 提供对现有 MSXML COM 的访问(我只留下 SelectNodes 来澄清问题)。

[ComImport]
[ComSourceInterfaces("MSXML2.XMLDOMDocumentEvents")]
[TypeLibType(TypeLibTypeFlags.FCanCreate)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("88d96a06-f192-11d4-a65f-0040963251e5")]
public class FreeThreadedDOMDocumentClass : IXMLDOMDocument2
{
    [return: MarshalAs(UnmanagedType.Interface)]
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x1d)]
    public extern object SelectNodes([In, MarshalAs(UnmanagedType.BStr)] string queryString);

}

[ComImport, Guid("2933BF95-7B36-11D2-B20E-00C04F983E60"), TypeLibType((short)0x10c0)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IXMLDOMDocument2
{
    [return: MarshalAs(UnmanagedType.Interface)]
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0x1d)]
    object SelectNodes([In, MarshalAs(UnmanagedType.BStr)] string queryString);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix"), ComImport]
[Guid("2933BF82-7B36-11D2-B20E-00C04F983E60")]
[TypeLibType(TypeLibTypeFlags.FDispatchable)]//(short) 0x10c0
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IXMLDOMNodeList: IEnumerable
{

    [DispId(0)]
    IXMLDOMNode this[int index]
    {
      [return: MarshalAs(UnmanagedType.Interface)]
      [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(0)]
      get;
    }


    [DispId(0x4a)]
    int Count
    {
      [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(0x4a)]
      get;
    }
}

而且我们的部分代码有问题。示例:

string xmlText = "<Item> <Element></Element></Item>";
IXMLDOMDocument2 domDocument = new FreeThreadedDOMDocumentClass();
domDocument.LoadXml(xmlText);
string xPath = "//Item";
IXMLDOMNodeList resultQuery = domDocument.SelectNodes(xPath) as IXMLDOMNodeList;
resultQuery.GetEnumerator()

由于执行 SelectNodes 而获得的对象 resultQuery 根据某些外部因素,GetEnumerator 有问题:

  1. 在windows7及以上早期系统(不支持WinRT技术的系统)

    • 获得的对象resultQuery具有类型System.__ComObject
    • resultQuery.GetEnumerator() 执行没有任何问题并提供有效的 Enumerator
  2. 在windows8以上的系统中(支持WinRT技术的系统)

    • 获得的对象 resultQuery 的类型为 Windows.Data.Xml.Dom.XmlNodeList。这是 WinRt 类型。
    • resultQuery.GetEnumerator() 抛出异常:System.ArgumentException: The object's type must not be a Windows Runtime type。我发现异常来源是 Marshal.GetComObjectData。这意味着我们的编组过程失败
  3. 在windows8以上的系统中导入com版本MSXML 3.0:

    • 获得的对象resultQuery具有类型System.__ComObject
    • resultQuery.GetEnumerator() 执行没有任何问题并提供有效的 Enumerator

此时我找到了第 2 点的三个解决方法(在 windows 上使用 msxml 和 WinRT)但不是我们的解决方案(它可以帮助开发人员遇到同样的问题):

  1. 不要使用 foreach 和 GetEnumerator。很清楚:)
  2. 使用 ICustomMarshalerSelectNodes 创建自定义编组,这将在 WinRT 类型上创建包装器并通过 for.
  3. 提供自定义 GetEnumerator
  4. 将 msxml 的版本从 6 更改为 3。

我需要帮助来找到第 2 点中的问题根源并了解如何修复它。 谢谢

经过深入调查,我找到了解决方案:

1) 我使用 TLBimp.exe

为 msxml6 com 生成了互操作 C# dll

2) 使用反编译器打开生成的 dll

3) 找到生成的接口 IXMLDOMNodeList。 它有下一个实现:

[Guid("2933BF82-7B36-11D2-B20E-00C04F983E60"), TypeLibType(TypeLibTypeFlags.FDual | TypeLibTypeFlags.FNonExtensible | TypeLibTypeFlags.FDispatchable)]
[ComImport]
public interface IXMLDOMNodeList : IEnumerable
{
    // Token: 0x1700012B RID: 299
    [DispId(0)]
    IXMLDOMNode this[[In] int index]
    {
        [DispId(0)]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [return: MarshalAs(UnmanagedType.Interface)]
        get;
    }

    // Token: 0x1700012C RID: 300
    // (get) Token: 0x060003D5 RID: 981
    [DispId(74)]
    int length
    {
        [DispId(74)]
        [MethodImpl(MethodImplOptions.InternalCall)]
        get;
    }

    // Token: 0x060003D6 RID: 982
    [DispId(76)]
    [MethodImpl(MethodImplOptions.InternalCall)]
    [return: MarshalAs(UnmanagedType.Interface)]
    IXMLDOMNode nextNode();

    // Token: 0x060003D7 RID: 983
    [DispId(77)]
    [MethodImpl(MethodImplOptions.InternalCall)]
    void reset();

    // Token: 0x060003D8 RID: 984
    [TypeLibFunc(TypeLibFuncFlags.FRestricted | TypeLibFuncFlags.FHidden), DispId(-4)]
    [MethodImpl(MethodImplOptions.InternalCall)]
    [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(EnumeratorToEnumVariantMarshaler))]
    IEnumerator GetEnumerator();
}

我们如何才能看到生成的接口有一些额外的方法:nextNode reset 和覆盖 'GetEnumerator'

我在 IXMLDOMNodeList 的实现中添加了遗漏的方法,这解决了 WinRT 系统中 GetEnumerator 的所有问题。