Draft function pointers in reflection spec
The reflection stack should treat function pointer arguments as standard pointer arguments for the purposes of method invocation. This means that boxed IntPtr arguments should be accepted as parameters to these methods in MethodInfo.Invoke
scenarios. For methods which return function pointers, they should be returned as boxed IntPtrs.
The reflection stack, for convenience, should also consider allowing developers to pass System.Reflection.Pointer
instances as input arguments to these methods.
This assumes the existence of a unique Type
object per function pointer. It does not cover the work required to make the C# typeof(delegate*<void>)
syntax return an instance of such type.
Given a type that represents a function pointer, a caller is likely to perform 3 operations with it:
- Inspect its properties, including arguments, return type, and calling convention.
- Create a managed typed
MulticastDelegate
instance around a function pointer of this type. - Invoke it directly without first assigning a
MulticastDelegate
instance.
(This latter scenario assumes dynamic invocation. If the exact type was known to the caller, it's expected they'd use a standard calli
invocation.)
The Type
class has a few existing APIs of interest to us.
Type.IsPointer : bool
Type.GetElementType() : Type?
The IsPointer property currently represents types like void*
and int*
. Callers can "unwrap" the pointer and retrieve the underlying type via the GetElementType API.
I propose the addition of new APIs to allow the inspection of function pointer types.
namespace System.Reflection
{
public class Type
{
public virtual bool IsFunctionPointer { get; }
// this method throws if IsFunctionPointer returns false
public virtual FunctionPointerInfo GetFunctionPointerInfo();
}
public abstract class FunctionPointerInfo
{
public abstract Type UnderlyingType { get; }
public abstract ParameterInfo ReturnParameter { get; }
public abstract ParameterInfo[] GetParameters();
public abstract Type[] GetCallConvs();
public abstract object? DynamicInvoke(void* pfnTarget, params object?[]? args);
public abstract Delegate CreateDelegate(void* pfnTarget, Type delegateType);
}
}
The intent of moving these APIs onto a separate FunctionPointerInfo type is to prevent normal Type consumers from seeing these concepts and to allow for clustering of any future function pointer-related APIs.
The DynamicInvoke and CreateDelegate methods are expected to perform type safety checks where feasible. For instance, an info object representing a delegate*<int, void>
should not accept a string for an argument. But if the info object represents a delegate*<delegate*<int, void>, void>
, it might not be possible to validate that the IntPtr passed in represents a valid, compatible function pointer.
The DynamicInvoke and CreateDelegate methods aren't guaranteed to root any particular object. The caller must ensure the underlying
void*
is valid for the lifetime of the call or the lifetime of the returned delegate. (Similar constraint as theMarshal.GetDelegateForFunctionPointer
API.)
The DynamicInvoke and CreateDelegate methods are expected to honor any mandatory calling convention. If the calling convention cannot be honored, the method must fail.
Currently, if IsPointer returns true, GetElementType returns non-null. The addition of a function pointer concept will introduce the scenario where if IsPointer is true, GetElementType will only return non-null if IsFunctionPointer is false.