是否有ComDefaultInterface的一个COM可调用包装任何目的?目的、ComDefaultInterface、COM

2023-09-05 03:42:12 作者:小贴士。

的 ComDefaultInterfaceAttribute 属性,如果与被管理对象 ClassInterfaceType.None 被封为任何的IUnknown 的IDispatch 啊?

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

考虑下面的C#类 AuthenticateHelper ,它实现COM IAuthenticate进行的

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;
    }
}    

我刚刚了解到,.NET互操作运行时从 IAuthenticate进行的分离及其实施的IUnknown 这样的类:

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!

我已经了解到,在实施 IServiceProvder ,因为以下没有工作,它里面崩溃后的客户端code从 QueryService的返回:

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;
}

我天真地期望 AuthenticateHelper 的实例可以被编组为 IAuthenticate进行的,因为该类声明 [ComDefaultInterface(typeof运算(IAuthenticate进行))] ,所以 IAuthenticate进行的是唯一的,默认的COM接口按实施这个类。然而,没有工作,显然是因为该对象仍然被封为的IUnknown

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.

下面的作品,但它改变了 QueryService的的签名,并使得它不太友好的消费(而不是提供)对象:

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;
}

那么,为什么我会指定 ComDefaultInterface 在所有的,如果不影响编组?唯一的其他用途我看到的是对类型库的生成。

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.

这是管理的客户端COM code调用我的托管实现的IServiceProvider :: QueryService的。有没有一种方法,使 QueryService的工作在我的例子,而不诉诸低级别的东西,如 GetComInterfaceForObject

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?

推荐答案

如果你有一个对象实现多个接口 ComDefaultInterface 属性是唯一真正有用的。由一个对象显示的第一界面可以是在某些情况下很重要,但该命令是不实际的语言来指定。属性迫使你指定接口被第一发射,与任何其他人进来的非指定的顺序

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.

这也意味着你是从管理code到COM出口,因此类,谁得到你的班级还给他们比 CoCreateObject 其他客户端得到正确的'默认'接口(例如,如果你的类被标记为 [ClassInterface(ClassInterfaceType.None)] )。

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 was other than CoCreateObject get the correct 'default' interface (e.g. if your class is marked as [ClassInterface(ClassInterfaceType.None)]).

对于您通过管理code共事进口类或类只实现一个单一的接口,该属性是无害的,但基本上是无用的。

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.

此外,至于你的最后一个问题,你很少有完全管理code。使用COM对象时,采取低层次的接口查询。 C#编译器会自动处理的QueryInterface 电话如果你使用普通的键入强制关键字。在你的情况, AuthenticationHelper ,因为这就是你想要的被创建为AuthenticationHelper 类管理的;如果你知道你想要什么界面,你知道它的落实,要求是:

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;