Какова цель атрибута ComDefaultInterfaceAttribute, если управляемый объект с ClassInterfaceType.None
is 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
?
Да, только библиотека типов, он устанавливает атрибут [по умолчанию] на одном из интерфейсов, перечисленных в классе. Помогает компилятору языка, который не поддерживает интерфейсы, чтобы выяснить, какой интерфейс нужно запрашивать при создании объекта, а VB6 является стандартным примером. –