Есть ли какая-либо цель ComDefaultInterface для вызываемой оболочки COM?

Какова цель атрибута ComDefaultInterfaceAttribute, если управляемый объект с ClassInterfaceType.None маршалируется как IUnknown или IDispatch все равно?

Рассмотрим следующий класс C# 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 отделяет свою реализацию 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?


person noseratio    schedule 14.10.2013    source источник
comment
Да, просто библиотека типов, она устанавливает атрибут [по умолчанию] на одном из интерфейсов, перечисленных в коклассе. Помогает компилятору языка, который не поддерживает интерфейсы, выяснить, какой интерфейс запрашивать при создании объекта, стандартным примером является VB6.   -  person Hans Passant    schedule 14.10.2013


Ответы (1)


Атрибут ComDefaultInterface действительно полезен только в том случае, если у вас есть более одного интерфейса, реализованного на одном объекте. «Первый» интерфейс, предоставляемый объектом, может быть важен в определенных случаях, но порядок на самом деле не определяется языком. Атрибут заставляет интерфейс, который вы указываете, быть испущенным первым, а все остальные - в неуказанном порядке.

Это также предназначено для классов, которые вы экспортируете из управляемого кода в COM, чтобы клиенты, которые возвращают им ваш класс в формате, отличном от CoCreateObject, получали правильный интерфейс «по умолчанию» (например, если ваш класс помечен как [ClassInterface(ClassInterfaceType.None)]).

Для импортированных классов, с которыми вы работаете через управляемый код, или классов, которые реализуют только один интерфейс, этот атрибут безвреден, но по сути бесполезен.

Кроме того, что касается вашего последнего вопроса, вам редко приходится прибегать к низкоуровневым запросам интерфейса при использовании COM-объектов в полностью управляемом коде. Компилятор C# автоматически обработает вызовы QueryInterface, если вы используете обычные ключевые слова приведения типов as и is. В вашем случае AuthenticationHelper создается как управляемый класс AuthenticationHelper, потому что это то, о чем вы просили; если вы знаете, какой интерфейс вам нужен, и знаете, что он реализован, попросите об этом:

AuthenticateHelper ah = new AuthenticateHelper();
IAuthenticate ia = ah as IAuthenticate;
person Michael Edenfield    schedule 14.10.2013
comment
Итак, я бы не согласился со следующим утверждением, если я правильно его понял: Это также предназначено для классов, которые вы экспортируете из управляемого кода в COM, чтобы клиенты, которые получают ваш класс, возвращались к ним способами, отличными от CoCreateObject получите правильный интерфейс "по умолчанию" (например, если ваш класс помечен как [ClassInterface(ClassInterfaceType.None)]). Видимо, в моем случае это не работает. - person noseratio; 14.10.2013
comment
Я не уверен, что именно вы там делаете не так; Я никогда не реализовывал IServiceProvider, поэтому не знаю, чем QueryService отличается от, скажем, QueryInterface, но могу сказать вам, что ComDefaultInterface не имеет смысла в классе только с одним интерфейсом. - person Michael Edenfield; 14.10.2013
comment
Я думал, что ComDefaultInterface будет указывать, как управляемый класс маршалируется на неуправляемый клиент по умолчанию. Под этим я подразумеваю исходящую маршализацию как общую object, то есть [Out, MarshalAs(UnmanagedType.Interface)] out object result подпись, например QueryInterface. Это не сработало для меня. Вы говорите, что это все еще возможно, без Marshal.GetComInterfaceForObject/Marshal.GetIUnknownForObject/Marshal.QueryInterface? - person noseratio; 14.10.2013