Reputation: 61716
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
?
Upvotes: 6
Views: 3277
Reputation: 28338
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;
Upvotes: 6