Search code examples
c#.netcommarshallingmsxml

MSXMLl 6.0 XmlDOMNodeList fails when is invoked GetEnumerator


Our project uses MSXML 6.0 com object for working with XML through ComImport attribute. Below com Class which provides access to existing MSXML COM(I left only SelectNodes for clarify problem ).

[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;
    }
}

And We have problem with some parts of code. Example :

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()

Obtained object resultQuery as result of execution of SelectNodes has problem with GetEnumerator depending on some outside factors:

  1. In windows 7 or more early systems (system without support of WinRT technology)

    • obtained object resultQuery has type System.__ComObject
    • resultQuery.GetEnumerator() executes without any problem and provide working Enumerator
  2. In windows 8 or more later systems (system with support of WinRT technology)

    • obtained object resultQuery has type Windows.Data.Xml.Dom.XmlNodeList. This is WinRt type.
    • resultQuery.GetEnumerator() throws exception:System.ArgumentException: The object's type must not be a Windows Runtime type. I figured out that source of exception is Marshal.GetComObjectData. It means that our Marshalling process fails
  3. In windows 8 or more later systems with version of imported com MSXML 3.0 :

    • obtained object resultQuery has type System.__ComObject
    • resultQuery.GetEnumerator() executes without any problem and provide working Enumerator

At this moment I found three workarounds for point 2 (using msxml on windows with WinRT) but not the solutions for us (It can be helpful for developers with the same issue):

  1. Do not use foreach and GetEnumerator. it's clear :)
  2. Create custom marshalling for SelectNodes with using ICustomMarshaler which will create wrapper over WinRT type and provide custom GetEnumerator through for.
  3. Change version of msxml from 6 to 3.

I need help to find source of problem in point 2 and understand how to fix it. Thanks


Solution

  • After deep investigation i have found solution:

    1) I generated interop C# dll for msxml6 com with TLBimp.exe

    2) Opened generated dll with decompiler

    3) Found generated interface IXMLDOMNodeList. It has next implementation:

    [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();
    }
    

    How can we see generated interface has some additioanl methods : nextNode reset and overrided 'GetEnumerator'

    I addes missed methods to my implementation of IXMLDOMNodeList and this fixed all problem with GetEnumerator in systems with WinRT.