Question

What is the purpose of ComDefaultInterfaceAttribute attribute, if the managed object with ClassInterfaceType.None is marshaled as either IUnknown or IDispatch, anyway?

Consider the following C# class AuthenticateHelper, which implements COM IAuthenticate:

[ComImport]
[Guid("79eac9d0-baf9-11ce-8c82-00aa004ba90b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAuthenticate
{
    [PreserveSig]
    int Authenticate(
        [In, Out] ref IntPtr phwnd,
        [In, Out, MarshalAs(UnmanagedType.LPWStr)] ref string pszUsername,
        [In, Out, MarshalAs(UnmanagedType.LPWStr)] ref string pszPassword);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IAuthenticate))]
public class AuthenticateHelper: IAuthenticate
{
    public int Authenticate(ref IntPtr phwnd, ref string pszUsername, ref string pszPassword)
    {
        phwnd = IntPtr.Zero;
        pszUsername = String.Empty;
        pszPassword = String.Empty;
        return 0;
    }
}    

I've just learnt that .NET interop runtime separates its implementation of IUnknown from IAuthenticate for such class:

AuthenticateHelper ah = new AuthenticateHelper();
IntPtr unk1 = Marshal.GetComInterfaceForObject(ah, typeof(IAuthenticate));
IntPtr unk2 = Marshal.GetIUnknownForObject(ah);
Debug.Assert(unk1 == unk2); // will assert!

I've learn that while implementing IServiceProvder, because the following did not work, it was crashing inside the client code upon returning from QueryService:

[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServiceProvider
{
    [PreserveSig]
    int QueryService(
        [In] ref Guid guidService,
        [In] ref Guid riid,
        [Out, MarshalAs(UnmanagedType.Interface, IidParameterIndex=1)] out object ppvObject    
}

// ...

public readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");

AuthenticateHelper ah = new AuthenticateHelper();

int IServiceProvider.QueryService(ref Guid guidService, ref Guid riid, out object ppvObject)
{
    if (guidService == typeof(IAuthenticate).GUID && (riid == IID_IUnknown || riid == guidService))
    {
        ppvObject = this.ah; // same as ppvObject = (IAuthenticate)this.ah
        return S_OK;
    }
    ppvObject = null;
    return E_NOINTERFACE;
}

I naively expected the instance of AuthenticateHelper would be marshaled as IAuthenticate because the class declares [ComDefaultInterface(typeof(IAuthenticate))], so IAuthenticate is the only and the default COM interface implemented by this class. However, that did not work, obviously because the object still gets marshaled as IUnknown.

The following works, but it changes the signature of QueryService and makes it less friendly for consuming (rather than providing) objects:

[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServiceProvider
{
    [PreserveSig]
    int QueryService(
        [In] ref Guid guidService,
        [In] ref Guid riid,
        [Out] out IntPtr ppvObject);
}

// ...

int IServiceProvider.QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject)
{
    if (guidService == typeof(IAuthenticate).GUID && (riid == IID_IUnknown || riid == guidService))
    {
        ppvObject = Marshal.GetComInterfaceForObject(this.ah, typeof(IAuthenticate));
        return S_OK;
    }
    ppvObject = IntPtr.Zero;
    return E_NOINTERFACE;
}

So, why would I specify ComDefaultInterface at all, if it doesn't affect marshaling? The only other use I see is for type library generation.

It's unmanaged client COM code that calls my managed implementation of IServiceProvider::QueryService. Is there a way to make QueryService work in my example without resorting to low-level stuff like GetComInterfaceForObject?

Was it helpful?

Solution

The ComDefaultInterface attribute is only really useful if you have more than one interface implemented on a single object. The "first" interface exposed by an object can be important in certain cases, but the order is not actually specified by the language. The attribute forces the interface you specify to be emitted first, with any others coming in a non-specified order.

It is also meant for classes that you are exporting from managed code to COM, so that clients who get your class returned to them in ways other than CoCreateObject get the correct 'default' interface (e.g. if your class is marked as [ClassInterface(ClassInterfaceType.None)]).

For imported classes that you work with via managed code, or classes that only implement a single interface, the attribute is harmless but essentially useless.

Also, as far as your last question, you rarely have to resort to low-level interface querying when using COM objects in fully managed code. The C# compiler will automatically handle the QueryInterface calls if you use the normal as and is type coercion keywords. In your case, AuthenticationHelper is being created as a managed AuthenticationHelper class because that's what you asked for; if you know what interface you want and you know it's implemented, ask for that:

AuthenticateHelper ah = new AuthenticateHelper();
IAuthenticate ia = ah as IAuthenticate;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top