committed by
GitHub
28 changed files with 26 additions and 530 deletions
@ -1,9 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public interface IMicroComExceptionCallback |
|||
{ |
|||
void RaiseException(Exception e); |
|||
} |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public interface IMicroComShadowContainer |
|||
{ |
|||
MicroComShadow Shadow { get; set; } |
|||
void OnReferencedFromNative(); |
|||
void OnUnreferencedFromNative(); |
|||
} |
|||
} |
|||
@ -1,8 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.MicroCom |
|||
{ |
|||
public interface IUnknown : IDisposable |
|||
{ |
|||
} |
|||
} |
|||
@ -1,17 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,110 +0,0 @@ |
|||
using System; |
|||
using System.Runtime.ConstrainedExecution; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia.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 == IntPtr.Zero) |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,143 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.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>(); |
|||
|
|||
internal static readonly Guid ManagedObjectInterfaceGuid = Guid.Parse("cd7687c0-a9c2-4563-b08e-a399df50c633"); |
|||
|
|||
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 T CreateProxyOrNullFor<T>(void* pObject, bool ownsHandle) where T : class => |
|||
pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); |
|||
|
|||
public static T CreateProxyOrNullFor<T>(IntPtr pObject, bool ownsHandle) where T : class => |
|||
pObject == IntPtr.Zero ? null : (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); |
|||
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 IsComWrapper(IUnknown obj) => obj is MicroComProxyBase; |
|||
|
|||
public static object TryUnwrapManagedObject(IUnknown obj) |
|||
{ |
|||
if (obj is not MicroComProxyBase proxy) |
|||
return null; |
|||
if (proxy.QueryInterface(ManagedObjectInterfaceGuid, out _) != 0) |
|||
return null; |
|||
// Successful QueryInterface always increments ref counter
|
|||
proxy.Release(); |
|||
return GetObjectFromCcw(proxy.NativePointer); |
|||
} |
|||
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,177 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
|
|||
namespace Avalonia.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 if (*guid == MicroComRuntime.ManagedObjectInterfaceGuid) |
|||
{ |
|||
ccw->RefCount++; |
|||
*ppv = ccw; |
|||
return 0; |
|||
} |
|||
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<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) |
|||
{ |
|||
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; |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.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(void* f) |
|||
{ |
|||
_methods.Add(new IntPtr(f)); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue