2013-10-14 2 views
3

Какова цель атрибута ComDefaultInterfaceAttribute, если управляемый объект с ClassInterfaceType.Noneis marshaled либо IUnknown, либо IDispatch, так или иначе?Есть ли какая-либо цель ComDefaultInterface для COM-Callable Wrapper?

Рассмотрим следующий 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 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, потому что следующее не работает, это был cras Хины внутри кода клиента после возвращения из 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?

+1

Да, только библиотека типов, он устанавливает атрибут [по умолчанию] на одном из интерфейсов, перечисленных в классе. Помогает компилятору языка, который не поддерживает интерфейсы, чтобы выяснить, какой интерфейс нужно запрашивать при создании объекта, а VB6 является стандартным примером. –

ответ

4

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

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

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

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

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

Итак, я бы не согласился со следующим утверждением, если бы правильно понял: * Он также предназначен для классов, которые вы экспортируете из управляемого кода в COM, чтобы клиенты, которые получили ваш класс, возвращались к ним способами, отличными от ' CoCreateObject' получает правильный интерфейс по умолчанию (например, если ваш класс помечен как '[ClassInterface (ClassInterfaceType.None)]') *. По-видимому, это не работает в моем случае. – Noseratio

+0

Я не уверен, что вы там делаете неправильно; Я никогда не реализовал 'IServiceProvider', поэтому я не знаю, почему' QueryService' будет отличаться от, скажем, 'QueryInterface', но могу сказать, что' ComDefaultInterface' не имеет смысла в классе с одним интерфейсом. –

+0

Я думал, что 'ComDefaultInterface' будет указывать, как управляемый класс маршалируется неуправляемому клиенту по умолчанию. Под этим я подразумеваю исходящее маршалинг как общий «объект», т. Е. '[Out, MarshalAs (UnmanagedType.Interface)] из сигнатуры результата объекта, например' QueryInterface'. Это не сработало для меня. Вы говорите, что это все еще возможно, без 'Marshal.GetComInterfaceForObject/Marshal.GetIUnknownForObject/Marshal.QueryInterface'? – Noseratio

Смежные вопросы