Description
Background and motivation
In interop scenarios where we have unbounded generic APIs that can take blittable or non-blittable types, we can end up in a scenario where we have a generic type T
that has a corresponding type TAbi
. However, there is no way to represent TAbi
as a generic argument, so we have a mechanism to retrieve it as a System.Type
instance. This allows us to still reason about the type.
If we want to do anything with the type though (like copy values around, go from a pointer into a buffer to a managed representation of it), we end up needing to use the APIs on System.Runtime.InteropServices.Marshal
that take a System.Type
, which are slow and not AOT-compatible.
We hit this scenario in CsWinRT when we are trying to marshal from WinRT a T[]
where T
is non-blittable. Today CsWinRT uses the APIs on Marshal even though the type will always be blittable as there's no alternative APIs.
API Proposal
namespace System.Runtime.CompilerServices;
public static class RuntimeHelpers
{
+ public object? Box(ref byte target, RuntimeTypeHandle type);
}
API Usage
int length = ...;
IntPtr data = ...;
if (data == IntPtr.Zero)
{
return null;
}
var array = new T[length];
var data = (byte*)data.ToPointer();
var abi_element_size = Marshal.SizeOf(AbiType); // #97344 API would go here to get rid of the remaining usage of Marshal.SizeOf
for (int i = 0; i < abi.length; i++)
{
array[i] = Marshaler<T>.FromAbi(RuntimeHelpers.Box(ref *data, AbiType.TypeHandle));
data += abi_element_size;
}
return array;
Alternative Designs
Mentioned below in the collapsable section.
Risks
Minimal risk.
Previous Proposal
API Proposal
namespace System;
public ref struct TypedReference
{
+ public TypedReference(ref byte target, RuntimeTypeHandle type);
}
API Usage
int length = ...;
IntPtr data = ...;
if (data == IntPtr.Zero)
{
return null;
}
var array = new T[length];
var data = (byte*)data.ToPointer();
var abi_element_size = Marshal.SizeOf(AbiType); // #97344 API would go here to get rid of the remaining usage of Marshal.SizeOf
for (int i = 0; i < abi.length; i++)
{
var abi_element_ref = new TypedReference(ref *data, AbiType.TypeHandle);
array[i] = Marshaler<T>.FromAbi(abi_element_ref.ToObject());
data += abi_element_size;
}
return array;
Alternative Designs
We could provide another API on RuntimeHelpers
to read and box an unmanaged value-type from a buffer, or even provide a higher level API over a ReadOnlySpan<byte>
, like a MemoryMarshal.Read(ref ReadOnlySpan<byte>, System.Type)
method.
We could make this method a static method with an Unsafe
suffix to further describe its unsafeness.
Risks
System.TypedReference
is a rarely used type. There may be issues in the runtime implementations around supporting it.