102 lines
3.4 KiB
C#
102 lines
3.4 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|