COM 可调用包装器中的 ComDefaultInterface 是否有任何作用?

6
如果使用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互操作运行时将其对于IUnknownIAuthenticate的实现分开处理,用于这样的类:
AuthenticateHelper ah = new AuthenticateHelper();
IntPtr unk1 = Marshal.GetComInterfaceForObject(ah, typeof(IAuthenticate));
IntPtr unk2 = Marshal.GetIUnknownForObject(ah);
Debug.Assert(unk1 == unk2); // will assert!

我了解到,在实现IServiceProvider时,由于以下方法无法正常工作,在从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;
}

我天真地以为,由于该类声明了[ComDefaultInterface(typeof(IAuthenticate))],因此AuthenticateHelper的实例将作为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这样的低级操作?

2
是的,只有类型库,它在coclass中列出的一个接口上设置了[default]属性。这有助于不支持接口的语言的编译器在对象创建时找到要请求的接口,VB6是标准示例。 - Hans Passant
1个回答

6
ComDefaultInterface属性仅在单个对象实现多个接口时才真正有用。对象公开的“第一个”接口在某些情况下可能很重要,但实际上语言没有指定顺序。该属性强制发出您指定的接口,其他任何接口以非指定顺序出现。
此外,它适用于从托管代码导出到COM的类,以便以其他方式返回您的类的客户端(例如,如果将您的类标记为[ClassInterface(ClassInterfaceType.None)]),可以获得正确的“默认”接口。
对于您通过托管代码使用的导入类或仅实现单个接口的类,该属性是无害但基本上无用的。
另外,关于您的最后一个问题,在完全托管代码中使用COM对象时,您很少需要使用低级接口查询。如果您使用正常的asis类型强制转换关键字,则C#编译器将自动处理QueryInterface调用。在您的情况下,AuthenticationHelper被创建为托管的AuthenticationHelper类,因为这是您要求的;如果您知道要使用的接口并且知道它已实现,请请求该接口:
AuthenticateHelper ah = new AuthenticateHelper();
IAuthenticate ia = ah as IAuthenticate;

所以,如果我理解正确的话,我不同意以下陈述:它也适用于从托管代码导出到COM的类,以便以其他方式返回您的类的客户端获得正确的“默认”接口(例如,如果将您的类标记为[ClassInterface(ClassInterfaceType.None)]。显然,在我的情况下,这并不起作用。 - noseratio - open to work
我不确定你在那里做错了什么;我从未实现过 IServiceProvider,所以我不知道为什么 QueryService 会与 QueryInterface 有任何不同,但我可以告诉你,在只有一个接口的类上使用 ComDefaultInterface 是没有意义的。 - Michael Edenfield
我原本以为ComDefaultInterface可以默认指定托管类的转发方式,从而与非托管客户端进行交互。我的意思是根据传出类型自动转换为泛型的object,例如 [Out, MarshalAs(UnmanagedType.Interface)] out object result这样的签名,就像QueryInterface方法一样。但这对我不起作用。你是说可以不用Marshal.GetComInterfaceForObject/Marshal.GetIUnknownForObject/Marshal.QueryInterface来实现吗? - noseratio - open to work

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接