0

I face some weird performance issues and I suspect that they may arise from having a dynamic specified as return type of a COM interface method. Specifically this is how IHTMLDOMChildrenCollection interface ends up in the interop:

[DefaultMember("item")]
[Guid("3050F5AB-98B5-11CF-BB82-00AA00BDCE0B")]
[TypeLibType(4160)]
public interface IHTMLDOMChildrenCollection : IEnumerable
{
    [DispId(-4)]
    [TypeLibFunc(65)]
    IEnumerator GetEnumerator();
    [DispId(0)]
    dynamic item(int index); // HERE is the dynamic

    [DispId(1500)]
    int length { get; }
}

and I suspect that once my code calls item() method then some extra wiring is created which reduces performance in the other code.

The code is organized like this:

var document = (IHTMLDocument)documentBrowser.Document;
var selector = (IDocumentSelector)document;
IHTMLDOMChildrenCollection allElements = selector.querySelectorAll("*");
int length = allElements.length;
for (int index = 0; index < length; index++)
{
    var item = allElements.item(index);
}

This answer states that performance maybe would improve if IHTMLDOMChildrenCollection interface was declared such that item() returned object, not dynamic.

Would it be possible to declare a new interface and somehow convince the CLR to give me an RCW implementing that interface?

I tried the following:

[DefaultMember("item")]
[Guid("3050F5AB-98B5-11CF-BB82-00AA00BDCE0B")]
[TypeLibType(4160)]
public interface IHTMLDOMChildrenCollectionCopy : IEnumerable
{
    [DispId(-4)]
    [TypeLibFunc(65)]
    IEnumerator GetEnumerator();
    [DispId(0)]
    [return: MarshalAs(UnmanagedType.IDispatch)]
    object item(int index); // HERE it's object, not dynamic

    [DispId(1500)]
    int length { get; }
}

and in the code:

IHTMLDOMChildrenCollection allElementsOriginal = selector.querySelectorAll("*");
var unknown = Marshal.GetIUnknownForObject(allElements);
var newElements = (IHTMLDOMChildrenCollectionCopy)Marshal.GetTypedObjectForIUnknown(
   unknown , typeof(IHTMLDOMChildrenCollectionCopy));
int length = newElements.length; // this fails

and it works until the line reading .length is executed. The latter fails with

System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

Should this work? What am I doing wrong?

sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • `dynamic` can certainly impact performance as it creates lots of wrappers on C# side. Try this instead: `[PreserveSig] int Item(int index, [MarshalAs(UnmanagedType.IDispatch)] out object ppItem);` – Simon Mourier Oct 01 '20 at 11:50
  • `[PreserveSig]` doesn't work for some reasons. However I did "replace" the interface - it had to be declared with `[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]` – sharptooth Oct 01 '20 at 12:24
  • I thought the interface was fully generated (TypeLibType, TypeLibFunc, DefautlMember are rarely used), so I didn't take a closer look at it's definition. It should be InterfaceIsIDual, not pure dispatch, otherwise you will again suffer perf issues (.NET will always use Invoke instead of calling methods directly). [PreserveSig] should work. – Simon Mourier Oct 01 '20 at 12:29
  • Using InterfaceIsIDual causes AccessViolationException as it was without attribute. – sharptooth Oct 01 '20 at 12:49
  • In this case, .NET calls the methods directly (which is what you want for perf) instead, so it means the definition is incorrect. In fact, it is, the interface binary layout is `get_length` first, then `get__newEnum` and `item` like this: https://pastebin.com/raw/4cnbrCrQ – Simon Mourier Oct 01 '20 at 13:15
  • Yes, dual works both with and without `PreserveSig` when members are reordered this way: length -> GetEnumerator() -> item() – sharptooth Oct 01 '20 at 13:27
  • This doesn't improve performance at all however. – sharptooth Oct 01 '20 at 13:31

1 Answers1

1

Yes, this should work. The missing part was the interface was not decorated with [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

This is complete declaration:

[DefaultMember("item")]
[Guid("3050F5AB-98B5-11CF-BB82-00AA00BDCE0B")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(4160)]
public interface IHTMLDOMChildrenCollectionCopy : IEnumerable
{
   // same members as in the question
}
sharptooth
  • 167,383
  • 100
  • 513
  • 979