ComDefaultInterface 对于 COM 可调用包装器有什么用途吗?

本文关键字:什么 COM 对于 调用 包装 ComDefaultInterface | 更新日期: 2023-09-27 18:35:08

如果带有ClassInterfaceType.None的托管对象被封送为IUnknownIDispatch,那么ComDefaultInterfaceAttribute属性的目的是什么?

请考虑以下实现 COM IAuthenticate的 C# 类AuthenticateHelper

[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 互操作运行时将其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这样的低级东西?

ComDefaultInterface 对于 COM 可调用包装器有什么用途吗?

只有当您

在单个对象上实现多个接口时,ComDefaultInterface 属性才真正有用。在某些情况下,对象公开的"第一个"接口可能很重要,但顺序实际上并不是由语言指定的。该属性强制首先发出您指定的接口,任何其他接口按非指定顺序发出。

它还适用于从托管代码导出到 COM 的类,以便以CoCreateObject以外的方式将获取类的客户端返回给它们的客户端获得正确的"默认"接口(例如,如果您的类标记为 [ClassInterface(ClassInterfaceType.None)] )。

对于通过托管代码使用的导入类或仅实现单个接口的类,该属性是无害的,但基本上是无用的。

此外,就

最后一个问题而言,在完全托管的代码中使用 COM 对象时,很少需要求助于低级接口查询。如果使用普通asis类型强制关键字,C# 编译器将自动处理QueryInterface调用。 在您的情况下,AuthenticationHelper 被创建为托管AuthenticationHelper类,因为这是您要求的;如果您知道自己想要什么接口并且知道它已实现,请询问:

AuthenticateHelper ah = new AuthenticateHelper();
IAuthenticate ia = ah as IAuthenticate;