using System; using System.Runtime.InteropServices; namespace VisualStudioMock { delegate int GetInterfaceDelegate(IntPtr @this, [MarshalAs(UnmanagedType.LPStruct)] Guid iid, out IntPtr ppv); delegate int AddRefDelegate(IntPtr @this); delegate int ReleaseDelegate(IntPtr @this); sealed class ComProxy { private const int S_OK = 0; private const int E_NOINTERFACE = -2147467262; private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-c000-000000000046"); private static readonly Guid IID_IManagedObject = new Guid("C3FCC19E-A970-11D2-8B5A-00A0C9B7C9C4"); private readonly object _realInstance; private readonly IntPtr _realInstanceN; private readonly IntPtr _proxyInstanceN; private readonly GetInterfaceDelegate _getInterface; private readonly IntPtr _getInterfaceN; private readonly AddRefDelegate _addRef; private readonly ReleaseDelegate _release; internal ComProxy(object instance) { this._realInstance = instance; this._realInstanceN = Marshal.GetIUnknownForObject(instance); this._proxyInstanceN = Marshal.AllocHGlobal(IntPtr.Size * 4); IntPtr vtbl = _proxyInstanceN + IntPtr.Size; Marshal.WriteIntPtr(_proxyInstanceN, vtbl); Marshal.WriteIntPtr(vtbl, this._getInterfaceN = Marshal.GetFunctionPointerForDelegate((Delegate)(this._getInterface = new GetInterfaceDelegate(GetInterface)))); Marshal.WriteIntPtr(vtbl, IntPtr.Size * 1, Marshal.GetFunctionPointerForDelegate((Delegate)(this._addRef = new AddRefDelegate(AddRef)))); Marshal.WriteIntPtr(vtbl, IntPtr.Size * 2, Marshal.GetFunctionPointerForDelegate((Delegate)(this._release = new ReleaseDelegate(Release)))); } internal object GetIUnknown() { return Marshal.GetObjectForIUnknown(_proxyInstanceN); } private int GetInterface(IntPtr @this, [MarshalAs(UnmanagedType.LPStruct)] Guid iid, out IntPtr ppv) { if (iid == IID_IUnknown) { ppv = _proxyInstanceN; Ext.LogCall(_realInstance.GetType().Name, nameof(GetInterface), "IUnknown OK"); return S_OK; } if (iid == IID_IManagedObject) { ppv = IntPtr.Zero; Ext.LogCall(_realInstance.GetType().Name, nameof(GetInterface), "IManagedObject Fail"); return E_NOINTERFACE; } int result = Marshal.QueryInterface(_realInstanceN, ref iid, out ppv); if (result == S_OK) { // Patch the vtable with our own thunks IntPtr origVtbl = Marshal.ReadIntPtr(ppv); Type iType = null; foreach (Type i in _realInstance.GetType().GetInterfaces()) if (i.GUID == iid) { iType = i; break; } if (iType != null) { // TODO generate proper thunks in machine code int slotCount = Marshal.GetEndComSlot(iType) + 1; int slotsSizeBytes = IntPtr.Size * slotCount; IntPtr newVtbl = Marshal.AllocHGlobal(slotsSizeBytes); ... } Ext.LogCall(_realInstance.GetType().Name, nameof(GetInterface), (iType == null ? iid.ToString() : iType.Name) + " OK"); } else { Ext.LogCall(_realInstance.GetType().Name, nameof(GetInterface), iid.ToString() + " Fail"); } return result; } private int AddRef(IntPtr @this) { return 1; } private int Release(IntPtr @this) { return 1; } } }