Last active
March 31, 2022 01:25
-
-
Save 2bam/b8ad3853c2cee42d07ec6dd074deb82a to your computer and use it in GitHub Desktop.
Acceptable way to pin/fix structs for C#-C interop including swap chain arrays by using-Dispose pattern without GetPinnableReference because that's garbage.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class CInteropProxy { | |
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] | |
unsafe delegate int C_stepDelegate(World.Pinned* world); | |
C_stepDelegate? c_step; // Set with kernel32.dll GetProcAddress() | |
public void Step(in World world) | |
{ | |
unsafe | |
{ | |
using (var pinWorld = world.Pin()) // using() instead of fixed() | |
{ | |
c_step?.Invoke(pinWorld); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public unsafe class Pin<T> : IDisposable where T : unmanaged | |
{ | |
public T value; | |
GCHandle[] _pinHandles; | |
int _pins; | |
IntPtr _structPtr; | |
public Pin() | |
{ | |
_structPtr = Marshal.AllocHGlobal(sizeof(T)); | |
_pinHandles = new GCHandle[32]; // TODO: static pooled | |
_pins = 0; | |
value = default(T); | |
} | |
public unsafe T* AlsoPin<T>(ref T[] managedReference) where T : unmanaged | |
{ | |
GCHandle handle; | |
_pinHandles[_pins++] = handle = GCHandle.Alloc(managedReference, GCHandleType.Pinned); | |
return (T*)handle.AddrOfPinnedObject(); | |
} | |
public static implicit operator T*(Pin<T> pin) => pin.Ptr(); | |
T* Ptr() | |
{ | |
Marshal.StructureToPtr(value, _structPtr, false); | |
return (T*)_structPtr; | |
} | |
// This will dispose pinned/unamanged memory | |
public void Dispose() | |
{ | |
while (_pins != 0) | |
{ | |
_pinHandles[--_pins].Free(); | |
} | |
Marshal.FreeHGlobal(_structPtr); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public struct World | |
{ | |
public Int32 width; | |
public Int32 height; | |
public Cell[] before; | |
public Cell[] after; | |
[StructLayout(LayoutKind.Sequential)] | |
public unsafe struct Pinned | |
{ | |
public Int32 width; | |
public Int32 height; | |
public Cell* before; | |
public Cell* after; | |
} | |
public unsafe Pin<Pinned> Pin() | |
{ | |
var pin = new Pin<Pinned>(); | |
pin.value = new Pinned | |
{ | |
width = width, | |
height = height, | |
before = pin.AlsoPin(ref before), | |
after = pin.AlsoPin(ref after) | |
}; | |
return pin; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note: width and height are read-only on the C side because they don't get copied back (the same way the actual references to the arrays and not the contents can't change neither).
I'm still thinking of an elegant solution.