Skip to content

[API Proposal]: RuntimeHelpers.Box to create a box around a dynamically-typed byref #97341

Closed
@jkoritzinsky

Description

@jkoritzinsky

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.Runtimein-prThere is an active PR which will close this issue when it is merged

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions