Skip to content

Commit 141679a

Browse files
committed
Added cleanup callback support
1 parent b7f03b2 commit 141679a

2 files changed

Lines changed: 239 additions & 6 deletions

File tree

src/DotNext.Tests/Runtime/InteropServices/OpaqueValueTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Runtime.CompilerServices;
12
using System.Runtime.InteropServices;
23

34
namespace DotNext.Runtime.InteropServices;
@@ -99,4 +100,49 @@ public static void InvalidValue()
99100
Throws<NotSupportedException>(static () => OpaqueValueType.get_Value(default(OpaqueValue<GCHandle>)));
100101
Throws<NotSupportedException>(static () => OpaqueValueType.get_Value<Pointer<int>>(default(OpaqueValue<Pointer<int>>)));
101102
}
103+
104+
[Fact]
105+
public static unsafe void CleanUpValueType()
106+
{
107+
var opaque = new OpaqueValue<Guid>(Guid.Empty);
108+
var cdeclPtr = (delegate*unmanaged[Cdecl]<nint, void>)opaque;
109+
cdeclPtr(Unsafe.BitCast<OpaqueValue<Guid>, nint>(opaque));
110+
111+
opaque = new OpaqueValue<Guid>(Guid.Empty);
112+
var stdCallPtr = (delegate*unmanaged[Stdcall]<nint, void>)opaque;
113+
stdCallPtr(Unsafe.BitCast<OpaqueValue<Guid>, nint>(opaque));
114+
115+
opaque = new OpaqueValue<Guid>(Guid.Empty);
116+
var cleanerPtr = (delegate*unmanaged<nint, void>)opaque;
117+
cleanerPtr(Unsafe.BitCast<OpaqueValue<Guid>, nint>(opaque));
118+
}
119+
120+
[Fact]
121+
public static unsafe void CleanUpRefType()
122+
{
123+
var opaque = new OpaqueValue<object>(new());
124+
var cdeclPtr = (delegate*unmanaged[Cdecl]<nint, void>)opaque;
125+
cdeclPtr(Unsafe.BitCast<OpaqueValue<object>, nint>(opaque));
126+
127+
opaque = new OpaqueValue<object>(new());
128+
var stdCallPtr = (delegate*unmanaged[Stdcall]<nint, void>)opaque;
129+
stdCallPtr(Unsafe.BitCast<OpaqueValue<object>, nint>(opaque));
130+
131+
opaque = new OpaqueValue<object>(new());
132+
var cleanerPtr = (delegate*unmanaged<nint, void>)opaque;
133+
cleanerPtr(Unsafe.BitCast<OpaqueValue<object>, nint>(opaque));
134+
}
135+
136+
[Fact]
137+
public static unsafe void CleanUpEmpty()
138+
{
139+
var opaque = new OpaqueValue<object>();
140+
Null((byte*)(delegate*unmanaged[Cdecl]<nint, void>)opaque);
141+
142+
opaque = new OpaqueValue<object>();
143+
Null((byte*)(delegate*unmanaged[Stdcall]<nint, void>)opaque);
144+
145+
opaque = new OpaqueValue<object>();
146+
Null((byte*)(delegate*unmanaged<nint, void>)opaque);
147+
}
102148
}

src/DotNext.Unsafe/Runtime/InteropServices/OpaqueValue.cs

Lines changed: 193 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics;
12
using System.Diagnostics.CodeAnalysis;
23
using System.Runtime.CompilerServices;
34
using System.Runtime.InteropServices;
@@ -65,29 +66,77 @@ public OpaqueValue(T? value)
6566

6667
internal OpaqueValue(nint handle) => this.handle = handle;
6768

69+
/// <summary>
70+
/// Returns the cleanup action of the <see cref="CallConvCdecl"/> calling convention that releases the opaque value.
71+
/// </summary>
72+
/// <param name="opaque">The opaque value.</param>
73+
/// <returns>The cleanup action that can be called from the unmanaged code.</returns>
74+
[CLSCompliant(false)]
75+
public static unsafe explicit operator delegate*unmanaged[Cdecl]<nint, void>(OpaqueValue<T> opaque)
76+
=> (delegate*unmanaged[Cdecl]<nint, void>)opaque.GetCleaner<CallConvCdecl, OpaqueValueCleaner>();
77+
78+
/// <summary>
79+
/// Returns the cleanup action of the <see cref="CallConvStdcall"/> calling convention that releases the opaque value.
80+
/// </summary>
81+
/// <param name="opaque">The opaque value.</param>
82+
/// <returns>The cleanup action that can be called from the unmanaged code.</returns>
83+
[CLSCompliant(false)]
84+
public static unsafe explicit operator delegate*unmanaged[Stdcall]<nint, void>(OpaqueValue<T> opaque)
85+
=> (delegate*unmanaged[Stdcall]<nint, void>)opaque.GetCleaner<CallConvStdcall, OpaqueValueCleaner>();
86+
87+
/// <summary>
88+
/// Returns the cleanup action of the native calling convention for the current platform that releases the opaque value.
89+
/// </summary>
90+
/// <remarks>
91+
/// The returned callback has the same effect as <see cref="Dispose"/> method.
92+
/// </remarks>
93+
/// <param name="opaque">The opaque value.</param>
94+
/// <returns>The cleanup action that can be called from the unmanaged code.</returns>
95+
[CLSCompliant(false)]
96+
public static unsafe explicit operator delegate*unmanaged<nint, void>(OpaqueValue<T> opaque)
97+
=> (delegate*unmanaged<nint, void>)opaque.GetCleaner<CallConvAuto, OpaqueValueCleaner>();
98+
99+
private bool IsNotAllocated => default(T) is IPointer || typeof(T) == typeof(GCHandle) || handle is 0;
100+
101+
private unsafe void* GetCleaner<TConvention, TCleaner>()
102+
where TConvention : class, new()
103+
where TCleaner : struct, ICleaner<TConvention>, allows ref struct
104+
{
105+
if (IsNotAllocated)
106+
return null;
107+
108+
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
109+
return TCleaner.FreeRef;
110+
111+
if (Unsafe.IsNaturallyAligned<T>())
112+
return TCleaner.Free;
113+
114+
return TCleaner.FreeAligned;
115+
}
116+
68117
/// <summary>
69118
/// Releases the underlying storage for the value.
70119
/// </summary>
71120
/// <remarks>
72121
/// This method is not idempotent and should not be called twice.
73122
/// </remarks>
74-
public unsafe void Dispose()
123+
public void Dispose()
75124
{
76-
if (default(T) is IPointer || typeof(T) == typeof(GCHandle) || handle is 0)
125+
if (IsNotAllocated)
77126
{
78127
// nothing to do
79128
}
80129
else if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
81130
{
82-
GCHandle<object>.FromIntPtr(handle).Dispose();
131+
ICleaner.FreeRef(handle);
83132
}
84133
else if (Unsafe.IsNaturallyAligned<T>())
85134
{
86-
NativeMemory.Free(handle.ToPointer());
135+
ICleaner.Free(handle);
87136
}
88137
else
89138
{
90-
NativeMemory.AlignedFree(handle.ToPointer());
139+
ICleaner.FreeAligned(handle);
91140
}
92141
}
93142

@@ -193,4 +242,142 @@ public object? Value
193242

194243
private GCHandle AsHandle() => GCHandle.FromIntPtr(opaque.handle);
195244
}
196-
}
245+
}
246+
247+
internal interface ICleaner
248+
{
249+
public static void FreeRef(nint handle)
250+
{
251+
Debug.Assert(handle is not 0);
252+
253+
GCHandle<object>.FromIntPtr(handle).Dispose();
254+
}
255+
256+
public static unsafe void Free(nint handle)
257+
{
258+
Debug.Assert(handle is not 0);
259+
260+
NativeMemory.Free(handle.ToPointer());
261+
}
262+
263+
public static unsafe void FreeAligned(nint handle)
264+
{
265+
Debug.Assert(handle is not 0);
266+
267+
NativeMemory.AlignedFree(handle.ToPointer());
268+
}
269+
}
270+
271+
internal unsafe interface ICleaner<TConvention> : ICleaner
272+
where TConvention : class, new()
273+
{
274+
public new static abstract void* FreeRef { get; }
275+
276+
public new static abstract void* Free { get; }
277+
278+
public new static abstract void* FreeAligned { get; }
279+
}
280+
281+
file readonly unsafe ref struct OpaqueValueCleaner : ICleaner<CallConvCdecl>, ICleaner<CallConvStdcall>, ICleaner<CallConvAuto>
282+
{
283+
static void* ICleaner<CallConvCdecl>.FreeRef
284+
{
285+
get
286+
{
287+
return (delegate*unmanaged[Cdecl]<nint, void>)&FreeCore;
288+
289+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
290+
static void FreeCore(nint handle) => ICleaner.FreeRef(handle);
291+
}
292+
}
293+
294+
static void* ICleaner<CallConvCdecl>.Free
295+
{
296+
get
297+
{
298+
return (delegate*unmanaged[Cdecl]<nint, void>)&FreeCore;
299+
300+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
301+
static void FreeCore(nint handle) => ICleaner.Free(handle);
302+
}
303+
}
304+
305+
static void* ICleaner<CallConvCdecl>.FreeAligned
306+
{
307+
get
308+
{
309+
return (delegate*unmanaged[Cdecl]<nint, void>)&FreeCore;
310+
311+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
312+
static void FreeCore(nint handle) => ICleaner.FreeAligned(handle);
313+
}
314+
}
315+
316+
static void* ICleaner<CallConvStdcall>.FreeRef
317+
{
318+
get
319+
{
320+
return (delegate*unmanaged[Stdcall]<nint, void>)&FreeCore;
321+
322+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
323+
static void FreeCore(nint handle) => ICleaner.FreeRef(handle);
324+
}
325+
}
326+
327+
static void* ICleaner<CallConvStdcall>.Free
328+
{
329+
get
330+
{
331+
return (delegate*unmanaged[Stdcall]<nint, void>)&FreeCore;
332+
333+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
334+
static void FreeCore(nint handle) => ICleaner.Free(handle);
335+
}
336+
}
337+
338+
static void* ICleaner<CallConvStdcall>.FreeAligned
339+
{
340+
get
341+
{
342+
return (delegate*unmanaged[Stdcall]<nint, void>)&FreeCore;
343+
344+
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
345+
static void FreeCore(nint handle) => ICleaner.FreeAligned(handle);
346+
}
347+
}
348+
349+
static void* ICleaner<CallConvAuto>.FreeRef
350+
{
351+
get
352+
{
353+
return (delegate*unmanaged<nint, void>)&FreeCore;
354+
355+
[UnmanagedCallersOnly]
356+
static void FreeCore(nint handle) => ICleaner.FreeRef(handle);
357+
}
358+
}
359+
360+
static void* ICleaner<CallConvAuto>.Free
361+
{
362+
get
363+
{
364+
return (delegate*unmanaged<nint, void>)&FreeCore;
365+
366+
[UnmanagedCallersOnly]
367+
static void FreeCore(nint handle) => ICleaner.Free(handle);
368+
}
369+
}
370+
371+
static void* ICleaner<CallConvAuto>.FreeAligned
372+
{
373+
get
374+
{
375+
return (delegate*unmanaged<nint, void>)&FreeCore;
376+
377+
[UnmanagedCallersOnly]
378+
static void FreeCore(nint handle) => ICleaner.FreeAligned(handle);
379+
}
380+
}
381+
}
382+
383+
file sealed class CallConvAuto;

0 commit comments

Comments
 (0)