-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Reflection introspection support for FunctionPointer #69273
Comments
Tagging subscribers to this area: @dotnet/area-system-reflection Issue DetailsBackground and motivationThis is the proposed API to support introspection of Function Pointers ( In summary, when function pointers were added in 5.0 the corresponding support for reflection was not added which resulted in an Corresponding support will also be added to This API issue is focused on introspection. Once this is in we can consider:
API Proposalnamespace System.Reflection
{
// A function pointer is a stand-alone signature, meaning it does not have an independent,
// reusable declaration like a Delegate.
+ public abstract class FunctionPointerInfo
+ {
+ internal FunctionPointerInfo(); // prevent external derivation
+
+ public abstract CallingConvention CallingConvention { get; }
+ public abstract ParameterInfo ReturnParameter { get; }
+ public abstract Type UnderlyingType { get; }
+ public abstract ReadOnlySpan<ParameterInfo> GetParameters();
// Although each function pointer declaration will be its own instance (pending possible caching strategy)
// we support Equals to compare against them.
+ public abstract bool Equals(object? obj);
+ public abstract int GetHashCode();
+ public abstract string? ToString();
// Future invoke and MemberInfo scenarios:
// public abstract unsafe object? DynamicInvoke(void* pfnTarget, params object?[]? args);
// public abstract unsafe Delegate CreateDelegate(void* pfnTarget, Type delegateType);
+ }
public class Type
{
+ public virtual bool IsFunctionPointer { get; }
// Throws InvalidOperationException if IsFunctionPointer is false.
+ public virtual FunctionPointerInfo GetFunctionPointerInfo();
// Note that IsPointer will return 'false' for function pointers.
public virtual bool IsPointer { get; }
}
// We are not re-using this as it exposes "Any". Ideally this could have been called "AnyManaged"
// in which case we may have extended it.
// Currently used in limited scenarios on MethodBase primarily to filter via GetMethods() and GetConstructor()
[Flags]
public enum CallingConventions
{
Standard = 0x1,
VarArgs = 0x2,
Any = 0x3,
HasThis = 0x20,
ExplicitThis = 0x40, // would be set for function pointers
}
// New CallingConvention that matches System.Reflection.Metadata.SignatureCallingConvention except uses Managed\Unmanaged prefixes
+ public enum CallingConvention : byte
+ {
+ Managed = 0,
+ UnmanagedCdecl = 1,
+ UnmanagedStdCall = 2,
+ UnmanagedThisCall = 3,
+ UnmanagedFastCall = 4,
+ ManagedVarArgs = 5, // not supported with function pointers
+ Unmanaged = 9,
+ }
} API Usageusing System.Reflection;
namespace ConsoleApp
{
internal class Program
{
static void Main(string[] args)
{
// typeof:
Type type = typeof(delegate*<void>);
bool b = type.IsFunctionPointer(); // true
FunctionPointerInfo fpi = Type.GetFunctionPointerInfo();
string s = fpi.ToString(); // "void ()" (actual is TBD, but will list all parameters if there any)
Type returnType = fpi.ReturnParameter.ParameterType; // System.Void
// Also works with reflection:
MethodInfo method = typeof(Util).GetMethod(nameof(Util.GetLog), BindingFlags.Public | BindingFlags.Instance)!;
type = method.ReturnType;
b = type.IsFunctionPointer(); // true
fpi = Type.GetFunctionPointerInfo();
}
}
unsafe public class Util
{
public static void Log() { Console.WriteLine("Log"); }
public delegate*<void> GetLog()
{
return &Log;
}
}
} Alternative Designs
RisksThe breaking change.
|
Instead of reusing that enum, which has aged poorly, I would follow the pattern established by |
|
If it does exist, it would end up allocating FunctionPointerInfo instance that the caller may not be interested at all. I think there is a value in having |
What's the motivation behind FunctionPointerInfo? There's no GenericParameterInfo, ArrayInfo, PointerInfo, ByReferenceInfo, or GenericInstanceInfo - the properties/methods that are only relevant for pareterized types/generic instances are dumped directly onto System.Type. It's up to the caller to decide whether the properties/methods make sense to call for a given System.Type instance. Function pointers are constructed types like any other constructed type - why do we shape the reflection API differently? |
Personally, I dislike that design: it makes it harder to discover which members you need or to ensure you're using them correctly. To me, this proposal looks good, since it clearly separates the concept of function pointers from the rest of the API surface, while not being too out of place regarding the existing API (unlike e.g. a new So I see it as a question of choosing something that's better in isolation, but less consistent versus choosing something worse on its own, but more consistent. |
I'm not a fan of that design either but it has advantages in some cases. E.g. if I want a Type that tweaks one of the properties of an existing type, I would use a System.Refection.TypeDelegator and override that one property. It's unclear how TypeDelegator fits with FunctionPointerInfo. E.g. if I want the exact same function pointer type but with a different calling convention. The reflection stack is built around dumping everything onto System.Type. |
Function pointers are more like methods and have more metadata than generics, arrays, existing pointer, etc so instead of adding directly to
The stand-alone option has the least friction and doesn't pollute We may also want to add |
Yes thanks to support that we would need to add a |
I think there might be some confusion about the amount of metadata function pointer carries. They really don't carry much. Also the choice of Here's a list of APIs on
I'm discussing pretty much just adding three properties to
I understand we would also like to add "execution" APIs for this. Those should go on a separate class. There's prior art:
The "execution" APIs never go on
That would not address the problem in a consistent way. For the scenario I wrote above (just change calling convention), do I really need to reimplement all of |
Thanks for the detailed feedback @MichalStrehovsky. Per offline discussion, the original intent of
|
Thank you for the good offline discussion! I have a couple of observations for the new design:
For the |
Thanks. Removed
The |
|
Yep thanks. I just updated to say "non-CallConv" mod opts. |
@bartonjs You forgot to hit "Comment". |
Watched the recording. If we're proposing IsPointer return true for function pointers, how are we going to reconcile it with places like this: Line 101 in 57bfe47
Will we always have to do IsPointer && !IsFunctionPointer? Or be very careful about the order in which we check the properties (treat function pointers first and the rest is what everyone else considers pointer types)? Function pointers are not a subset of unmanaged pointers in the ECMA-335 spec either (page 44 of the PDF has a helpful hierarchy - function pointers are pointers same way as byrefs are pointers). Do we also return true from HasElementType or do we only implement GetElementType to return void? I'm not sure I agree with the argument that we need IsPointer to be true so that NetStandard serializers can use it to ignore function pointers. Desktop CLR is going to treat them as UIntPtr forever and IsPointer is false for those. We'll get inconsistent behavior no matter what. |
For those interested in the design, please review dotnet/designs#282. |
cc @MichalStrehovsky @GrabYourPitchforks @jkotas We had a review today and @tannergooding was asking if
Tanner was suggesting that we do this (as was originally in the 7.0 proposal as well)
-or-
|
Should that be |
I summed up my arguments about IsPointer here: #69273 (comment) |
Yes it should be |
My vote is to keep POR proposal (
I do not see enough added clarity over |
The clarity is that when you look at IntelliSense, you see both |
The same applies to docs and any other context where you might be looking at the available |
The same problem exists for IsByRef. Byrefs are pointers in the more general sense as well - they are called managed pointers in number of contexts. Reflection object model has many intricacies that are not discoverable using IntelliSense. One has to read the docs. |
The difference is they don't have pointer in the name for the API side of things, and so while one might expect IsPointer to return true, there isn't any implication. However, |
People who don't read docs are going to interpret this in all sorts of way. There's prior art in Cecil (or CCI, or others). We can just look for how people use this. E.g. here is someone puzzled why They're right, delegates are also conceptually function pointers. One needs to read docs... |
Looks good as proposed. namespace System
{
public partial class Type
{
public virtual bool IsFunctionPointer { get; }
public virtual bool IsUnmanagedFunctionPointer { get; }
// These throw InvalidOperationException if IsFunctionPointer = false:
public virtual Type GetFunctionPointerReturnType();
public virtual Type[] GetFunctionPointerParameterTypes();
// These require a "modified type" to return custom modifier types:
public virtual Type[] GetRequiredCustomModifiers();
public virtual Type[] GetOptionalCustomModifiers();
public virtual Type[] GetFunctionPointerCallingConventions(); // Throws if IsFunctionPointer = false
}
}
// Return a "modified type" from a field, property or parameter if its type is a:
// - Function pointer type
// - Pointer or array since they may reference a function pointer
// - Parameter or return type from a function pointer
namespace System.Reflection
{
public partial class FieldInfo
{
public virtual Type GetModifiedFieldType() => throw new NotSupportedException();
}
public partial class PropertyInfo
{
public virtual Type GetModifiedPropertyType() => throw new NotSupportedException();
}
public partial class ParameterInfo
{
public virtual Type GetModifiedParameterType() => throw new NotSupportedException();
}
} |
Background and motivation
This is the proposed API to support introspection of Function Pointers (
delegate*
) based on the user story, original issue and function pointer design. In summary, when function pointers were added in 5.0 the corresponding support for reflection was not added which resulted in anIntPtr
being returned as the type when usingtypeof
or reflection. This feature changes that to now returnSystem.Type
which then allows access to function pointer metadata including the calling convention, return type and parameters. This is a breaking change.UPDATE: the original approved API for .NET 7 was not implemented due to concerns with breaking scenarios with managed C++ and adding unnecessary runtime overhead. This .NET 8 proposal changes the design to return "modified types" that expose custom modifiers on function pointers instead of exposing them on runtime-based types.
Corresponding support will also be added to
MetadataLoadContext
. The inbox version will have full support but the netstandard version, for simplicity, will throwNotSupportedException
for the new members that return new types (since they are not in netstandard). For members that don't return new types, it will be possible to use reflection to invoke them.This API issue is focused on introspection; the links above have additional features that can be layered on this including support for IL Emit.
API Proposal
A modified type's
UnderlyingSystemType
property returns the unmodified type and all members on a modified forward to that except:GetRequiredCustomModifiers()
GetOptionalCustomModifiers()
GetFunctionPointerCallingConventions()
GetFunctionPointerParameterTypes()
GetFunctionPointerReturnType()
which instead return the information kept on the modified type.
API Usage
See the design referenced above. Here's some short examples:
Risks
The breaking change nature of not returning
IntPtr
any longer.The text was updated successfully, but these errors were encountered: