Предыстория была такова, что заметил небольшой интересный факт:
При вызове Marshal.ReleaseComObject(...)
, освобождается только RCW
, а у интерфейсов всегда оставались ссылки и они не освобождались.
Столкнулся с проблемой очень долгого создания объектов COM
, используя именно стандартные средства .NET
, а если быть точнее, то это интерфейсы помеченные аттрибутами
[ComImport, Guid(...), IntyerfaceType(...)] interface ISomeIUnknownInterface { ... }
Написанная мной обертка, работает непосредственно с указателем на интерфейс, имеет IUnknown
интерфейс, и работает через виртуальную таблицу этого объекта.
Сделал тест скорости создания как минимум главного объекта IDXGIFactory
, и при работе со стандартными интерфейсами, объект был получен спустя 2 секунды после вызова, когда возврат указателя на объект занимает порядка 10~100 мс.
Интерфейс и класс выглядят вот так:
[Guid("00000000-0000-0000-C000-000000000046")] public interface IUnknown : IDisposable { int QueryInterface(in Guid riid, out IntPtr unknownObjectPtr); uint AddRef(); uint Release(); }
public class Unknown : IUnknown { public const uint LastMethodId = 2u; protected readonly int MethodsCount = typeof(IUnknown).GetMethods().Length; public Unknown(IntPtr objectPtr) { if (IntPtr.Zero == objectPtr) { throw new ArgumentException("IUnknown object pointer cannot be IntPtr.Zero or null.", nameof(objectPtr)); } Pointer = objectPtr; AddMethodsToVTableList(0, MethodsCount); } protected IntPtr Pointer { get; set; } public bool IsDisposed { get; protected set; } public bool IsValid => Pointer != IntPtr.Zero; protected List<IntPtr> VirtualTableAddresses { get; private set; } = new List<IntPtr>(); public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public int QueryInterface(in Guid riid, out IntPtr unknownObjectPtr) { return GetMethodDelegate<QueryInterfaceDelegate>().Invoke(this, in riid, out unknownObjectPtr); } public uint AddRef() { return GetMethodDelegate<AddRefDelegate>().Invoke(this); } public uint Release() { #if DEBUG uint result = #else return #endif GetMethodDelegate<ReleaseDelegate>().Invoke(this); #if DEBUG Trace.WriteLine($ "{typeof(Unknown).Namespace} — {this}.Release() return value: {result}", "DEBUG"); #endif return result; } protected virtual void Dispose(bool isDisposed) { if (IsDisposed || !IsValid) { IsDisposed = true; return; } Release(); if (isDisposed) { Pointer = IntPtr.Zero; VirtualTableAddresses.Clear(); VirtualTableAddresses = null; } IsDisposed = true; } ~Unknown() { Dispose(false); } protected void AddMethodsToVTableList(int startMethodId, int methodsCount) { IntPtr virtualTablePtr = Marshal.ReadIntPtr(this); for (int i = startMethodId; i < methodsCount + startMethodId; i++) { VirtualTableAddresses.Add(Marshal.ReadIntPtr(virtualTablePtr, i * IntPtr.Size)); } } protected T GetMethodDelegate<T>() where T : Delegate { ComMethodIdAttribute attribute = typeof(T).GetCustomAttribute<ComMethodIdAttribute>(); return Marshal.GetDelegateForFunctionPointer<T>(VirtualTableAddresses[(int) attribute.Id]); } public static implicit operator IntPtr(Unknown obj) { return obj.Pointer; } [ComMethodId(0u), UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int QueryInterfaceDelegate(IntPtr selfPtr, in Guid riid, out IntPtr unknownObjectPtr); [ComMethodId(1u), UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate uint AddRefDelegate(IntPtr selfPtr); [ComMethodId(2u), UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate uint ReleaseDelegate(IntPtr selfPtr); }
Один из наследованных интерфейсов и объектов:
[Guid("aec22fb8-76f3-4639-9be0-28eb43a67a2e")] public interface IObject : IUnknown { int SetPrivateData(in Guid name, uint dataSize, byte[] data); int SetPrivateDataInterface(in Guid name, IUnknown unknown = null); int GetPrivateData(in Guid name, ref uint dataSize, [In, Out] byte[] data = null); int GetParent(in Guid riid, out IntPtr parent); }
public class Object : Unknown, IObject { protected new const uint LastMethodId = Unknown.LastMethodId + 4u; protected new readonly int MethodsCount = typeof(IObject).GetMethods().Length; public Object(IntPtr objectPtr) : base(objectPtr) { AddMethodsToVTableList(base.MethodsCount, MethodsCount); MethodsCount = base.MethodsCount + MethodsCount; } public int SetPrivateData(in Guid name, uint dataSize, byte[] data) { return GetMethodDelegate<SetPrivateDataDelegate>().Invoke(this, in name, dataSize, data); } public int SetPrivateDataInterface(in Guid name, IUnknown iUnknown = null) { return GetMethodDelegate<SetPrivateDataInterfaceDelegate>() .Invoke(this, in name, (Unknown) iUnknown ?? IntPtr.Zero); } public int GetPrivateData(in Guid name, ref uint dataSize, [In, Out] byte[] data = null) { return GetMethodDelegate<GetPrivateDataDelegate>().Invoke(this, in name, ref dataSize, data); } public int GetParent(in Guid riid, out IntPtr parent) { return GetMethodDelegate<GetParentDelegate>().Invoke(this, in riid, out parent); } [ComMethodId(Unknown.LastMethodId + 1u), UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int SetPrivateDataDelegate(IntPtr thisPtr, in Guid name, uint dataSize, [MarshalAs(UnmanagedType.LPArray)] byte[] data); [ComMethodId(Unknown.LastMethodId + 2u), UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int SetPrivateDataInterfaceDelegate(IntPtr thisPtr, in Guid name, IntPtr iUnknown); [ComMethodId(Unknown.LastMethodId + 3u), UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetPrivateDataDelegate(IntPtr thisPtr, in Guid name, ref uint dataSize, [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] data = null); [ComMethodId(Unknown.LastMethodId + 4u), UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetParentDelegate(IntPtr thisPtr, in Guid riid, out IntPtr parent); }
Такой интерйес и базовый класс, позволяют очень быстро описывать новые объекты, без необходимости дублирования наследуемых методов.
Почему такая реализация быстрее чем стандартные средства работы с COM
в .NET
?