To IDispatch or not to IDispatch, that is the question

Recently I got strange situation in our application. We have two different classes in .NET that exposed to managed code via COM interfaces. Both classes look very similar but one class by some reason supports IDispatch while another class does not. We have piece of code that work quite differently depending on IDispatch support. And was asked to investigate what is going on and why these two classes behave different.

Here is some data. First class looks like this:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Foo : BaseClass1, IDisposable

Second class looks like this:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class Bar : BaseClass2, IMyInterface

IMyInterface declared like this:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("XXX")]

If somebody asked me about code above: “do these classes support IDispatch” and I will say “no”. But first class does support it and second does not support it. Then I found this article in Microsoft documentation that stated “However, the runtime always provides the implementation for the IUnknown and IDispatch interfaces”. Well in my case both these classes were used via CCW wrappers (I checked this in WinDbg) but only one supports IDispatch.

My first step was to check inheritance and I did check BaseClass1 and BaseClass2. They are declared with exactly same attributes and they inherited from similarly declared classes and/or implement interfaces similar to IMyInterface. So, everything is right here. Then I checked assembly attributes. They were different but changing them does not change IDispatch support.

Then after few hours I come to reasonable conclusion that there should be something in .NET code that add support for IDispatch based on some conditions. And I decided to check source code. I really appreciate that Microsoft made source code available for .NET because it saved me a lot of time. And I think I found why does it happens. Here is function:

HRESULT MDInternalRW::GetIfaceTypeOfTypeDef(
    mdTypeDef   classdef,               // [IN] given classdef.
    ULONG       *pIface)                // [OUT] 0=dual, 1=vtable, 2=dispinterface
{
    HRESULT     hr;                     // A result.
    const BYTE  *pVal;                  // The custom value.
    ULONG       cbVal;                  // Size of the custom value.
    ULONG       ItfType = DEFAULT_COM_INTERFACE_TYPE;    // Set the interface type to the default.

    // all of the public functions that it calls have proper locked

    // If the value is not present, the class is assumed dual.
    hr = GetCustomAttributeByName(classdef, INTEROP_INTERFACETYPE_TYPE, (const void**)&pVal, &cbVal);
    if (hr == S_OK)
    {
        _ASSERTE("The ComInterfaceType custom attribute is invalid" && cbVal);
            _ASSERTE("ComInterfaceType custom attribute does not have the right format" && (*pVal == 0x01) && (*(pVal + 1) == 0x00));
        ItfType = *(pVal + 2);
        if (ItfType >= ifLast)
            ItfType = DEFAULT_COM_INTERFACE_TYPE;
    }

    // Set the return value.
    *pIface = ItfType;

    return hr;
} // MDInternalRW::GetIfaceTypeOfTypeDef

#define DEFAULT_COM_INTERFACE_TYPE ifDual.

Key phrase is “If the value is not present, the class is assumed dual”. I am not sure that this function is actually called, but it gave me idea. I checked IDisposable and found that it is declared as ComVisible(true) and it does not have InterfaceType attribute. As result I decided to remove IDisposable from list of supported interfaces for class Foo and after that Foo immediately stopped supporting IDispatch. Great, but how to make proper fix, because I want Foo to support IDispatch. Well, it was a bit tricky. I did try to make Foo implement one of interface that base class implements but it did not help. But finally, I found solution. All I have to do is to add ComDefaultInterface attribute. Something like this:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IBaseInterface))]
public class Foo : BaseClass1, IDisposable

Where IBaseInterface is interface that BaseClass1 implements. After that everything is started working as expected.

I hope it will help someone.

Comments

Post comment