ComDefaultInterfaceAttribute属性的目的是什么,如果托管对象ClassInterfaceType.None
is marshaled为IUnknown
或IDispatch
?
考虑以下C#class AuthenticateHelper
,它实现了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;
}
}
我刚刚了解到,.NET interop运行时将IUnknown
的实现与IAuthenticate
分开,用于此类:
AuthenticateHelper ah = new AuthenticateHelper();
IntPtr unk1 = Marshal.GetComInterfaceForObject(ah, typeof(IAuthenticate));
IntPtr unk2 = Marshal.GetIUnknownForObject(ah);
Debug.Assert(unk1 == unk2); // will assert!
我了解到,在实施IServiceProvder
时,由于以下无法正常工作,因此从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;
}
我天真地期望AuthenticateHelper
的实例将被封送为IAuthenticate
,因为该类声明[ComDefaultInterface(typeof(IAuthenticate))]
,因此 IAuthenticate
是唯一的默认COM接口由此类实现。但是,这不起作用,显然是因为对象仍然被封送为IUnknown
。
以下作品,但它会更改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] 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;
}
那么,为什么我会指定ComDefaultInterface
,如果它不影响编组?我看到的唯一其他用途是类型库生成。
这是非托管客户端COM代码,它调用IServiceProvider::QueryService
的托管实现。有没有办法让QueryService
在我的例子中工作而不诉诸GetComInterfaceForObject
等低级别的东西?
答案 0 :(得分:5)
如果在单个对象上实现了多个接口,则ComDefaultInterface
属性才真正有用。在某些情况下,对象公开的“第一个”接口可能很重要,但该语言实际上并未指定顺序。该属性强制首先发出您指定的接口,其他任何接口都以非指定顺序发送。
它也适用于从托管代码导出到COM的类,因此让您的类返回给它们的客户端不是CoCreateObject
获取正确的“默认”接口(例如,如果您的类被标记为[ClassInterface(ClassInterfaceType.None)]
)。
对于通过托管代码使用的导入类,或仅实现单个接口的类,该属性是无害的,但基本上没用。
此外,就您的上一个问题而言,在完全托管代码中使用COM对象时,很少需要使用低级接口查询。如果您使用普通的QueryInterface
和as
类型强制关键字,C#编译器将自动处理is
次调用。在您的情况下,AuthenticationHelper
被创建为托管AuthenticationHelper
类,因为这就是您所要求的;如果你知道你想要什么接口并且你知道它已经实现了,那就请求:
AuthenticateHelper ah = new AuthenticateHelper();
IAuthenticate ia = ah as IAuthenticate;