committed by
GitHub
43 changed files with 211 additions and 618 deletions
@ -0,0 +1,49 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace Avalonia.Utilities |
||||
|
{ |
||||
|
public static class SpanHelpers |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static bool TryParseUInt(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out uint value) |
||||
|
{ |
||||
|
#if NETSTANDARD2_0
|
||||
|
return uint.TryParse(span.ToString(), style, provider, out value); |
||||
|
#else
|
||||
|
return uint.TryParse(span, style, provider, out value); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static bool TryParseInt(this ReadOnlySpan<char> span, out int value) |
||||
|
{ |
||||
|
#if NETSTANDARD2_0
|
||||
|
return int.TryParse(span.ToString(), out value); |
||||
|
#else
|
||||
|
return int.TryParse(span, out value); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static bool TryParseDouble(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out double value) |
||||
|
{ |
||||
|
#if NETSTANDARD2_0
|
||||
|
return double.TryParse(span.ToString(), style, provider, out value); |
||||
|
#else
|
||||
|
return double.TryParse(span, style, provider, out value); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static bool TryParseByte(this ReadOnlySpan<char> span, NumberStyles style, IFormatProvider provider, out byte value) |
||||
|
{ |
||||
|
#if NETSTANDARD2_0
|
||||
|
return byte.TryParse(span.ToString(), style, provider, out value); |
||||
|
#else
|
||||
|
return byte.TryParse(span, style, provider, out value); |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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