| 1525 | } |
| 1526 | |
| 1527 | internal static class ValueTypeUnboxer |
| 1528 | { |
| 1529 | private static GCHandle[] pinnedBoxedValues = new GCHandle[256]; |
| 1530 | private static uint pinnedBoxedValuesPointer = 0; |
| 1531 | private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256]; |
| 1532 | private static uint pinnedAllocationsPointer = 0; |
| 1533 | |
| 1534 | private delegate IntPtr UnboxerDelegate(object value, object converter); |
| 1535 | |
| 1536 | private static ConcurrentDictionary<Type, (UnboxerDelegate deleg, object toNativeDeleg)> unboxers = new(1, 3); |
| 1537 | private static MethodInfo unboxerMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointer), BindingFlags.Static | BindingFlags.NonPublic); |
| 1538 | private static MethodInfo unboxerToNativeMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointerWithConverter), BindingFlags.Static | BindingFlags.NonPublic); |
| 1539 | |
| 1540 | internal static IntPtr GetPointer(object value, Type type) |
| 1541 | { |
| 1542 | if (!unboxers.TryGetValue(type, out var tuple)) |
| 1543 | { |
| 1544 | // Non-POD structures use internal layout (eg. SpriteHandleManaged in C++ with SpriteHandleMarshaller.SpriteHandleInternal in C#) so convert C# data into C++ data |
| 1545 | var attr = type.GetCustomAttribute<System.Runtime.InteropServices.Marshalling.NativeMarshallingAttribute>(); |
| 1546 | var toNativeMethod = attr?.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic); |
| 1547 | if (toNativeMethod != null) |
| 1548 | { |
| 1549 | tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, toNativeMethod.ReturnType).CreateDelegate<UnboxerDelegate>(); |
| 1550 | tuple.toNativeDeleg = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, toNativeMethod.ReturnType)); |
| 1551 | } |
| 1552 | else |
| 1553 | { |
| 1554 | tuple.deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate<UnboxerDelegate>(); |
| 1555 | } |
| 1556 | tuple = unboxers.GetOrAdd(type, tuple); |
| 1557 | } |
| 1558 | return tuple.deleg(value, tuple.toNativeDeleg); |
| 1559 | } |
| 1560 | |
| 1561 | private static void PinValue(object value) |
| 1562 | { |
| 1563 | // Prevent garbage collector from relocating the boxed value by pinning it temporarily. |
| 1564 | // The pointer should remain valid quite long time but will be eventually unpinned. |
| 1565 | uint index = Interlocked.Increment(ref pinnedBoxedValuesPointer) % (uint)pinnedBoxedValues.Length; |
| 1566 | ref GCHandle handle = ref pinnedBoxedValues[index]; |
| 1567 | if (handle.IsAllocated) |
| 1568 | handle.Free(); |
| 1569 | handle = GCHandle.Alloc(value, GCHandleType.Pinned); |
| 1570 | } |
| 1571 | |
| 1572 | private static IntPtr PinValue<T>(T value) where T : struct |
| 1573 | { |
| 1574 | // Store the converted value in unmanaged memory so it will not be relocated by the garbage collector. |
| 1575 | int size = TypeHelpers<T>.MarshalSize; |
| 1576 | uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length; |
| 1577 | ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index]; |
| 1578 | if (alloc.size < size) |
| 1579 | { |
| 1580 | if (alloc.ptr != IntPtr.Zero) |
| 1581 | NativeFree(alloc.ptr.ToPointer()); |
| 1582 | alloc.ptr = new IntPtr(NativeAlloc(size)); |
| 1583 | alloc.size = size; |
| 1584 | } |