mirror of
https://gitee.com/csharpui/CPF.git
synced 2025-11-24 08:33:23 +08:00
初始化
This commit is contained in:
12
CPF.Windows/MicroCom/IMicroComExceptionCallback.cs
Normal file
12
CPF.Windows/MicroCom/IMicroComExceptionCallback.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
public interface IMicroComExceptionCallback
|
||||
{
|
||||
void RaiseException(Exception e);
|
||||
}
|
||||
}
|
||||
14
CPF.Windows/MicroCom/IMicroComShadowContainer.cs
Normal file
14
CPF.Windows/MicroCom/IMicroComShadowContainer.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
public interface IMicroComShadowContainer
|
||||
{
|
||||
MicroComShadow Shadow { get; set; }
|
||||
void OnReferencedFromNative();
|
||||
void OnUnreferencedFromNative();
|
||||
}
|
||||
}
|
||||
11
CPF.Windows/MicroCom/IUnknown.cs
Normal file
11
CPF.Windows/MicroCom/IUnknown.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
public interface IUnknown : IDisposable
|
||||
{
|
||||
}
|
||||
}
|
||||
20
CPF.Windows/MicroCom/LocalInterop.cs
Normal file
20
CPF.Windows/MicroCom/LocalInterop.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
unsafe class LocalInterop
|
||||
{
|
||||
public static unsafe void CalliStdCallvoid(void* thisObject, void* methodPtr)
|
||||
{
|
||||
throw null;
|
||||
}
|
||||
|
||||
public static unsafe int CalliStdCallint(void* thisObject, Guid* guid, IntPtr* ppv, void* methodPtr)
|
||||
{
|
||||
throw null;
|
||||
}
|
||||
}
|
||||
}
|
||||
113
CPF.Windows/MicroCom/MicroComProxyBase.cs
Normal file
113
CPF.Windows/MicroCom/MicroComProxyBase.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
public unsafe class MicroComProxyBase : CriticalFinalizerObject, IUnknown
|
||||
{
|
||||
private IntPtr _nativePointer;
|
||||
private bool _ownsHandle;
|
||||
private SynchronizationContext _synchronizationContext;
|
||||
|
||||
public IntPtr NativePointer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_nativePointer == IntPtr.Zero)
|
||||
throw new ObjectDisposedException(this.GetType().FullName);
|
||||
return _nativePointer;
|
||||
}
|
||||
}
|
||||
|
||||
public void*** PPV => (void***)NativePointer;
|
||||
|
||||
public MicroComProxyBase(IntPtr nativePointer, bool ownsHandle)
|
||||
{
|
||||
_nativePointer = nativePointer;
|
||||
_ownsHandle = ownsHandle;
|
||||
_synchronizationContext = SynchronizationContext.Current;
|
||||
if (!_ownsHandle)
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual int VTableSize => 3;
|
||||
|
||||
public void AddRef()
|
||||
{
|
||||
LocalInterop.CalliStdCallvoid(PPV, (*PPV)[1]);
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
LocalInterop.CalliStdCallvoid(PPV, (*PPV)[2]);
|
||||
}
|
||||
|
||||
public int QueryInterface(Guid guid, out IntPtr ppv)
|
||||
{
|
||||
IntPtr r = default;
|
||||
var rv = LocalInterop.CalliStdCallint(PPV, &guid, &r, (*PPV)[0]);
|
||||
ppv = r;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public T QueryInterface<T>() where T : IUnknown
|
||||
{
|
||||
var guid = MicroComRuntime.GetGuidFor(typeof(T));
|
||||
var rv = QueryInterface(guid, out var ppv);
|
||||
if (rv == 0)
|
||||
return (T)MicroComRuntime.CreateProxyFor(typeof(T), ppv, true);
|
||||
throw new COMException("QueryInterface failed", rv);
|
||||
}
|
||||
|
||||
public bool IsDisposed => _nativePointer == IntPtr.Zero;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_nativePointer == null)
|
||||
return;
|
||||
if (_ownsHandle)
|
||||
{
|
||||
Release();
|
||||
_ownsHandle = false;
|
||||
}
|
||||
_nativePointer = IntPtr.Zero;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
public bool OwnsHandle => _ownsHandle;
|
||||
|
||||
public void EnsureOwned()
|
||||
{
|
||||
if (!_ownsHandle)
|
||||
{
|
||||
GC.ReRegisterForFinalize(true);
|
||||
AddRef();
|
||||
_ownsHandle = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly SendOrPostCallback _disposeDelegate = DisposeOnContext;
|
||||
|
||||
private static void DisposeOnContext(object state)
|
||||
{
|
||||
(state as MicroComProxyBase)?.Dispose(false);
|
||||
}
|
||||
|
||||
~MicroComProxyBase()
|
||||
{
|
||||
if (!_ownsHandle)
|
||||
return;
|
||||
if (_synchronizationContext == null)
|
||||
Dispose();
|
||||
else
|
||||
_synchronizationContext.Post(_disposeDelegate, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
CPF.Windows/MicroCom/MicroComRuntime.cs
Normal file
129
CPF.Windows/MicroCom/MicroComRuntime.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
public static unsafe class MicroComRuntime
|
||||
{
|
||||
private static ConcurrentDictionary<Type, IntPtr> _vtables = new ConcurrentDictionary<Type, IntPtr>();
|
||||
|
||||
private static ConcurrentDictionary<Type, Func<IntPtr, bool, object>> _factories =
|
||||
new ConcurrentDictionary<Type, Func<IntPtr, bool, object>>();
|
||||
private static ConcurrentDictionary<Type, Guid> _guids = new ConcurrentDictionary<Type, Guid>();
|
||||
private static ConcurrentDictionary<Guid, Type> _guidsToTypes = new ConcurrentDictionary<Guid, Type>();
|
||||
|
||||
static MicroComRuntime()
|
||||
{
|
||||
Register(typeof(IUnknown), new Guid("00000000-0000-0000-C000-000000000046"),
|
||||
(ppv, owns) => new MicroComProxyBase(ppv, owns));
|
||||
RegisterVTable(typeof(IUnknown), MicroComVtblBase.Vtable);
|
||||
}
|
||||
|
||||
public static void RegisterVTable(Type t, IntPtr vtable)
|
||||
{
|
||||
_vtables[t] = vtable;
|
||||
}
|
||||
|
||||
public static void Register(Type t, Guid guid, Func<IntPtr, bool, object> proxyFactory)
|
||||
{
|
||||
_factories[t] = proxyFactory;
|
||||
_guids[t] = guid;
|
||||
_guidsToTypes[guid] = t;
|
||||
}
|
||||
|
||||
public static Guid GetGuidFor(Type type) => _guids[type];
|
||||
|
||||
public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
|
||||
public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
|
||||
|
||||
public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle);
|
||||
|
||||
public static IntPtr GetNativeIntPtr<T>(this T obj, bool owned = false) where T : IUnknown
|
||||
=> new IntPtr(GetNativePointer(obj, owned));
|
||||
public static void* GetNativePointer<T>(T obj, bool owned = false) where T : IUnknown
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
if (obj is MicroComProxyBase proxy)
|
||||
{
|
||||
if (owned)
|
||||
proxy.AddRef();
|
||||
return (void*)proxy.NativePointer;
|
||||
}
|
||||
|
||||
if (obj is IMicroComShadowContainer container)
|
||||
{
|
||||
//container.Shadow ??= new MicroComShadow(container);
|
||||
if (container.Shadow==null)
|
||||
{
|
||||
container.Shadow = new MicroComShadow(container);
|
||||
}
|
||||
void* ptr = null;
|
||||
var res = container.Shadow.GetOrCreateNativePointer(typeof(T), &ptr);
|
||||
if (res != 0)
|
||||
throw new COMException(
|
||||
"Unable to create native callable wrapper for type " + typeof(T) + " for instance of type " +
|
||||
obj.GetType(),
|
||||
res);
|
||||
if (owned)
|
||||
container.Shadow.AddRef((Ccw*)ptr);
|
||||
return ptr;
|
||||
}
|
||||
throw new ArgumentException("Unable to get a native pointer for " + obj);
|
||||
}
|
||||
|
||||
public static object GetObjectFromCcw(IntPtr ccw)
|
||||
{
|
||||
var ptr = (Ccw*)ccw;
|
||||
var shadow = (MicroComShadow)GCHandle.FromIntPtr(ptr->GcShadowHandle).Target;
|
||||
return shadow.Target;
|
||||
}
|
||||
|
||||
public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t);
|
||||
|
||||
public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr);
|
||||
|
||||
public static void UnhandledException(object target, Exception e)
|
||||
{
|
||||
if (target is IMicroComExceptionCallback cb)
|
||||
{
|
||||
try
|
||||
{
|
||||
cb.RaiseException(e);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We've tried
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static T CloneReference<T>(this T iface) where T : IUnknown
|
||||
{
|
||||
var proxy = (MicroComProxyBase)(object)iface;
|
||||
var ownedPointer = GetNativePointer(iface, true);
|
||||
return CreateProxyFor<T>(ownedPointer, true);
|
||||
}
|
||||
|
||||
public static T QueryInterface<T>(this IUnknown unknown) where T : IUnknown
|
||||
{
|
||||
var proxy = (MicroComProxyBase)unknown;
|
||||
return proxy.QueryInterface<T>();
|
||||
}
|
||||
|
||||
public static void UnsafeAddRef(this IUnknown unknown)
|
||||
{
|
||||
((MicroComProxyBase)unknown).AddRef();
|
||||
}
|
||||
|
||||
public static void UnsafeRelease(this IUnknown unknown)
|
||||
{
|
||||
((MicroComProxyBase)unknown).Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
176
CPF.Windows/MicroCom/MicroComShadow.cs
Normal file
176
CPF.Windows/MicroCom/MicroComShadow.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
public unsafe class MicroComShadow : IDisposable
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private readonly Dictionary<Type, IntPtr> _shadows = new Dictionary<Type, IntPtr>();
|
||||
private readonly Dictionary<IntPtr, Type> _backShadows = new Dictionary<IntPtr, Type>();
|
||||
private GCHandle? _handle;
|
||||
private volatile int _refCount;
|
||||
internal IMicroComShadowContainer Target { get; }
|
||||
internal MicroComShadow(IMicroComShadowContainer target)
|
||||
{
|
||||
Target = target;
|
||||
Target.Shadow = this;
|
||||
}
|
||||
|
||||
internal int QueryInterface(Ccw* ccw, Guid* guid, void** ppv)
|
||||
{
|
||||
if (MicroComRuntime.TryGetTypeForGuid(*guid, out var type))
|
||||
return QueryInterface(type, ppv);
|
||||
else
|
||||
return unchecked((int)0x80004002u);
|
||||
}
|
||||
|
||||
internal int QueryInterface(Type type, void** ppv)
|
||||
{
|
||||
if (!type.IsInstanceOfType(Target))
|
||||
return unchecked((int)0x80004002u);
|
||||
|
||||
var rv = GetOrCreateNativePointer(type, ppv);
|
||||
if (rv == 0)
|
||||
AddRef((Ccw*)*ppv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
internal int GetOrCreateNativePointer(Type type, void** ppv)
|
||||
{
|
||||
if (!MicroComRuntime.GetVtableFor(type, out var vtable))
|
||||
return unchecked((int)0x80004002u);
|
||||
lock (_lock)
|
||||
{
|
||||
|
||||
if (_shadows.TryGetValue(type, out var shadow))
|
||||
{
|
||||
var targetCcw = (Ccw*)shadow;
|
||||
AddRef(targetCcw);
|
||||
*ppv = targetCcw;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var intPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Ccw)));
|
||||
var targetCcw = (Ccw*)intPtr;
|
||||
*targetCcw = default;
|
||||
targetCcw->RefCount = 0;
|
||||
targetCcw->VTable = vtable;
|
||||
if (_handle == null)
|
||||
_handle = GCHandle.Alloc(this);
|
||||
targetCcw->GcShadowHandle = GCHandle.ToIntPtr(_handle.Value);
|
||||
_shadows[type] = intPtr;
|
||||
_backShadows[intPtr] = type;
|
||||
*ppv = targetCcw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal int AddRef(Ccw* ccw)
|
||||
{
|
||||
if (Interlocked.Increment(ref _refCount) == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
Target.OnReferencedFromNative();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MicroComRuntime.UnhandledException(Target, e);
|
||||
}
|
||||
}
|
||||
|
||||
return Interlocked.Increment(ref ccw->RefCount);
|
||||
}
|
||||
|
||||
internal int Release(Ccw* ccw)
|
||||
{
|
||||
Interlocked.Decrement(ref _refCount);
|
||||
var cnt = Interlocked.Decrement(ref ccw->RefCount);
|
||||
if (cnt == 0)
|
||||
return FreeCcw(ccw);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
int FreeCcw(Ccw* ccw)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Shadow got resurrected by a call to QueryInterface from another thread
|
||||
if (ccw->RefCount != 0)
|
||||
return ccw->RefCount;
|
||||
|
||||
var intPtr = new IntPtr(ccw);
|
||||
var type = _backShadows[intPtr];
|
||||
_backShadows.Remove(intPtr);
|
||||
_shadows.Remove(type);
|
||||
Marshal.FreeHGlobal(intPtr);
|
||||
if (_shadows.Count == 0)
|
||||
{
|
||||
_handle?.Free();
|
||||
_handle = null;
|
||||
try
|
||||
{
|
||||
Target.OnUnreferencedFromNative();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MicroComRuntime.UnhandledException(Target, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Needs to be called to support the following scenario:
|
||||
1) Object created
|
||||
2) Object passed to native code, shadow is created, CCW is created
|
||||
3) Native side has never called AddRef
|
||||
|
||||
In that case the GC handle to the shadow object is still alive
|
||||
*/
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
List<IntPtr> toRemove = null;
|
||||
foreach (var kv in _backShadows)
|
||||
{
|
||||
var ccw = (Ccw*)kv.Key;
|
||||
if (ccw->RefCount == 0)
|
||||
{
|
||||
if (toRemove == null)
|
||||
{
|
||||
toRemove = new List<IntPtr>();
|
||||
}
|
||||
toRemove.Add(kv.Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
foreach (var intPtr in toRemove)
|
||||
FreeCcw((Ccw*)intPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct Ccw
|
||||
{
|
||||
public IntPtr VTable;
|
||||
public IntPtr GcShadowHandle;
|
||||
public volatile int RefCount;
|
||||
public MicroComShadow GetShadow() => (MicroComShadow)GCHandle.FromIntPtr(GcShadowHandle).Target;
|
||||
}
|
||||
}
|
||||
44
CPF.Windows/MicroCom/MicroComVtblBase.cs
Normal file
44
CPF.Windows/MicroCom/MicroComVtblBase.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace CPF.Windows.MicroCom
|
||||
{
|
||||
public unsafe class MicroComVtblBase
|
||||
{
|
||||
private List<IntPtr> _methods = new List<IntPtr>();
|
||||
[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)]
|
||||
private delegate int AddRefDelegate(Ccw* ccw);
|
||||
|
||||
[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)]
|
||||
private delegate int QueryInterfaceDelegate(Ccw* ccw, Guid* guid, void** ppv);
|
||||
|
||||
public static IntPtr Vtable { get; } = new MicroComVtblBase().CreateVTable();
|
||||
public MicroComVtblBase()
|
||||
{
|
||||
AddMethod((QueryInterfaceDelegate)QueryInterface);
|
||||
AddMethod((AddRefDelegate)AddRef);
|
||||
AddMethod((AddRefDelegate)Release);
|
||||
}
|
||||
|
||||
protected void AddMethod(Delegate d)
|
||||
{
|
||||
GCHandle.Alloc(d);
|
||||
_methods.Add(Marshal.GetFunctionPointerForDelegate(d));
|
||||
}
|
||||
|
||||
protected unsafe IntPtr CreateVTable()
|
||||
{
|
||||
var ptr = (IntPtr*)Marshal.AllocHGlobal((IntPtr.Size + 1) * _methods.Count);
|
||||
for (var c = 0; c < _methods.Count; c++)
|
||||
ptr[c] = _methods[c];
|
||||
return new IntPtr(ptr);
|
||||
}
|
||||
|
||||
static int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) => ccw->GetShadow().QueryInterface(ccw, guid, ppv);
|
||||
static int AddRef(Ccw* ccw) => ccw->GetShadow().AddRef(ccw);
|
||||
static int Release(Ccw* ccw) => ccw->GetShadow().Release(ccw);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user