Skip to content
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]: Add support for converting to/from IntPtr for some handles #67090

Closed
DaZombieKiller opened this issue Mar 24, 2022 · 17 comments · Fixed by #69363
Closed

[API Proposal]: Add support for converting to/from IntPtr for some handles #67090

DaZombieKiller opened this issue Mar 24, 2022 · 17 comments · Fixed by #69363
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Milestone

Comments

@DaZombieKiller
Copy link
Contributor

DaZombieKiller commented Mar 24, 2022

Background and motivation

The Type.GetTypeFromHandleUnsafe method allows you to retrieve the Type object associated with a type handle pointer retrieved via RuntimeTypeHandle.Value.

Currently, this API is internal with no public-facing equivalent. This means there is currently no safe way for you to get the original Type or RuntimeTypeHandle value back, leading to the RuntimeTypeHandle.Value property being of little use.

Having a way to retrieve the original Type or RuntimeTypeHandle would also allow the type handle pointer to be used to easily pass types to native code (such as when writing a custom runtime host).

API Proposal

public struct RuntimeTypeHandle
{
+     public static RuntimeTypeHandle FromIntPtr(IntPtr value);
+     public static IntPtr ToIntPtr(RuntimeTypeHandle value);
}

public struct RuntimeFieldHandle
{
+    public static RuntimeFieldHandle FromIntPtr(IntPtr value);
+    public static IntPtr ToIntPtr(RuntimeFieldHandle value);
}

public struct RuntimeMethodHandle
{
+    public static RuntimeMethodHandle FromIntPtr(IntPtr value);
+    public static IntPtr ToIntPtr(RuntimeMethodHandle value);
}

API Usage

var api = new NativeAPI
{
    GetTypeHandle      = &GetTypeHandle,
    GetFullTypeName    = &GetFullTypeName,
    GetFunctionPointer = &GetFunctionPointer
};

// This native method may be interested in querying for
// pointers to UnmanagedCallersOnly methods to invoke.
SomeNativeMethod(&api);
[UnmanagedCallersOnly]
static IntPtr GetTypeHandle(IntPtr typeName)
{
    return Type.GetType(Marshal.PtrToStringUni(typeName)).TypeHandle.Value;
}

[UnmanagedCallersOnly]
static IntPtr GetFunctionPointer(IntPtr type, IntPtr methodName)
{
    var type   = Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(type));
    var method = type.GetMethod(Marshal.PtrToStringUni(methodName), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
    return method.MethodHandle.GetFunctionPointer();
}

[UnmanagedCallersOnly]
static IntPtr GetFullTypeName(IntPtr type)
{
    return Marshal.StringToHGlobalUni(Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(type))?.FullName);
}

Alternative Designs

No response

Risks

Being an unmanaged pointer, RuntimeTypeHandle.Value will not keep a collectible type alive. This would need to be clearly called out in documentation and could be a potential problem when the API is not used correctly.

@DaZombieKiller DaZombieKiller added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Mar 24, 2022
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Mar 24, 2022
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@tfenise
Copy link
Contributor

tfenise commented Mar 24, 2022

I guess the IntPtr value of RuntimeTypeHandle.Value does not keep a collectible type alive, and thus may be invalidated or recycled unexpectedly.

@DaZombieKiller
Copy link
Contributor Author

I guess the IntPtr value of RuntimeTypeHandle.Value does not keep a collectible type alive, and thus may be invalidated or recycled unexpectedly.

This is a good point, I'll add it to the risks section. It's definitely something that a user of the API would need to be keenly aware of.

@Joe4evr
Copy link
Contributor

Joe4evr commented Mar 24, 2022

🍝 Just throwing this out for consideration: Should the public entrypoint for this API be placed somewhere under S.R.InteropServices (or whatever's more appropriate) instead?

@ghost
Copy link

ghost commented Mar 24, 2022

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

The Type.GetTypeFromHandleUnsafe method allows you to retrieve the Type object associated with a type handle pointer retrieved via RuntimeTypeHandle.Value.

Currently, this API is internal with no public-facing equivalent. This means there is currently no safe way for you to get the original Type or RuntimeTypeHandle value back, leading to the RuntimeTypeHandle.Value property being of little use.

Having a way to retrieve the original Type or RuntimeTypeHandle would also allow the type handle pointer to be used to easily pass types to native code (such as when writing a custom runtime host).

API Proposal

namespace System
{
    public abstract partial class Type
    {
        public static Type? GetTypeFromHandleUnsafe(IntPtr handle);
    }
}

API Usage

var api = new NativeAPI
{
    GetTypeHandle      = &GetTypeHandle,
    GetFullTypeName    = &GetFullTypeName,
    GetFunctionPointer = &GetFunctionPointer
};

// This native method may be interested in querying for
// pointers to UnmanagedCallersOnly methods to invoke.
SomeNativeMethod(&api);
[UnmanagedCallersOnly]
static IntPtr GetTypeHandle(IntPtr typeName)
{
    return Type.GetType(Marshal.PtrToStringUni(typeName)).TypeHandle.Value;
}

[UnmanagedCallersOnly]
static IntPtr GetFunctionPointer(IntPtr type, IntPtr methodName)
{
    var type   = Type.GetTypeFromHandleUnsafe(type);
    var method = type.GetMethod(Marshal.PtrToStringUni(methodName), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
    return method.MethodHandle.GetFunctionPointer();
}

[UnmanagedCallersOnly]
static IntPtr GetFullTypeName(IntPtr type)
{
    return Marshal.StringToHGlobalUni(Type.GetTypeFromHandleUnsafe(type)?.FullName);
}

Alternative Designs

No response

Risks

Being an unmanaged pointer, RuntimeTypeHandle.Value will not keep a collectible type alive. This would need to be clearly called out in documentation and could be a potential problem when the API is not used correctly.

Author: DaZombieKiller
Assignees: -
Labels:

api-suggestion, area-System.Runtime.InteropServices, untriaged

Milestone: -

@DaZombieKiller
Copy link
Contributor Author

🍝 Just throwing this out for consideration: Should the public entrypoint for this API be placed somewhere under S.R.InteropServices (or whatever's more appropriate) instead?

That sounds reasonable to me -- Type is extremely discoverable, so it might not be the best location for an API as unsafe as this.

@AaronRobinsonMSFT
Copy link
Member

@jkotas or @davidwrighton Can either of you imagine a scenario where the TypeHandle wouldn't be static? Unless unloaded that is. I am trying to determine if there is any chance of the IntPtr value being invalid for any other reason than an unload type. There are a number of places in the runtime that assume a TypeHandle address doesn't move but I'd like to make sure there isn't any potential future scenario where that assumption could be changed.

@jkotas
Copy link
Member

jkotas commented Mar 24, 2022

It is very likely that every realistic .NET runtime implementation has stable IntPtr (like TypeHandle in CoreCLR) that represents a type and that can be used to implement this marshaling very efficiently. The API contract should not say what this IntPtr actually points to.

In a hypothetical case where .NET runtime implementation does not have natural stable IntPtr that represents a type (e.g. the MethodTables are allocated on GC heap and can move), the API contract can be still maintained by a table on the side.

This API is about marshaling System.Type into IntPtr and back. We have API for the System.Type -> IntPtr direction (RuntimeTypeHandle.Value). We do not have an API for the IntPtr ->System.Type direction and this is proposing to fill this hole. Mono embedding APIs can give you pointer that represents a type and then use this pointer to get back to the type, so an alternative way to look at this request is managed surface feature parity with Mono embedding APIs.

@AaronRobinsonMSFT AaronRobinsonMSFT removed the untriaged New issue has not been triaged by the area owner label Mar 24, 2022
@AaronRobinsonMSFT AaronRobinsonMSFT added this to the 7.0.0 milestone Mar 24, 2022
@AaronRobinsonMSFT
Copy link
Member

AaronRobinsonMSFT commented Mar 24, 2022

Thoughts on adding this to RuntimeTypeHandle itself. This would follow the GCHandle pattern and developers could then use Type.GetTypeFromHandle() to get back to Type.

public struct RuntimeTypeHandle
{
+     public static RuntimeTypeHandle FromIntPtr(IntPtr value);
}

@jkoritzinsky
Copy link
Member

I like putting it on RuntimeTypeHandle personally. It makes it a little harder to find than if it were on System.Type, while still putting it on a related type (and as you mentioned, following the design styles of GCHandle).

@teo-tsirpanis
Copy link
Contributor

I agree, it makes more sense to add it to RuntimeTypeHandle. I don't think of a good place in InteropServices that could fit.

@jkotas
Copy link
Member

jkotas commented Mar 25, 2022

I agree that RuntimeTypeHandle sounds like a good place for this. If we were to follow the GCHandle pattern, should we add both FromIntPtr and ToIntPtr methods for discoverability?

@AaronRobinsonMSFT
Copy link
Member

If we were to follow the GCHandle pattern, should we add both FromIntPtr and ToIntPtr methods for discoverability?

Yep. That makes perfect sense.

@DaZombieKiller I'm going to update the proposal with this approach and then move it to ready for review. Please feel free to push back if you have another opinion.

@AaronRobinsonMSFT AaronRobinsonMSFT added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Mar 25, 2022
@DaZombieKiller
Copy link
Contributor Author

For the sake of parity, it may also be useful to expose APIs for RuntimeMethodHandle and RuntimeFieldHandle, which both also offer IntPtr Value { get; } properties with no way to convert back to a RuntimeMethodHandle/RuntimeFieldHandle.

public struct RuntimeFieldHandle
{
+    public static RuntimeFieldHandle FromIntPtr(IntPtr value);
+    public static IntPtr ToIntPtr(RuntimeFieldHandle value);
}

public struct RuntimeMethodHandle
{
+    public static RuntimeMethodHandle FromIntPtr(IntPtr value);
+    public static IntPtr ToIntPtr(RuntimeMethodHandle value);
}

@AaronRobinsonMSFT
Copy link
Member

@DaZombieKiller Sure. I'll add that as well.

@AaronRobinsonMSFT AaronRobinsonMSFT changed the title [API Proposal]: Make Type.GetTypeFromHandleUnsafe public [API Proposal]: Add support for converting to/from IntPtr for some handles Mar 25, 2022
@terrajobst
Copy link
Member

  • For consistency, you might want to also add explicit casting operators too, like GCHandle does.
namespace System;

public struct RuntimeTypeHandle
{
    public static RuntimeTypeHandle FromIntPtr(IntPtr value);
    public static IntPtr ToIntPtr(RuntimeTypeHandle value);
}

public struct RuntimeFieldHandle
{
    public static RuntimeFieldHandle FromIntPtr(IntPtr value);
    public static IntPtr ToIntPtr(RuntimeFieldHandle value);
}

public struct RuntimeMethodHandle
{
    public static RuntimeMethodHandle FromIntPtr(IntPtr value);
    public static IntPtr ToIntPtr(RuntimeMethodHandle value);
}

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Apr 14, 2022
@AaronRobinsonMSFT AaronRobinsonMSFT added this to To do in Interop-CoreCLR 7.0 via automation May 9, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label May 15, 2022
@ghost
Copy link

ghost commented May 15, 2022

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

The Type.GetTypeFromHandleUnsafe method allows you to retrieve the Type object associated with a type handle pointer retrieved via RuntimeTypeHandle.Value.

Currently, this API is internal with no public-facing equivalent. This means there is currently no safe way for you to get the original Type or RuntimeTypeHandle value back, leading to the RuntimeTypeHandle.Value property being of little use.

Having a way to retrieve the original Type or RuntimeTypeHandle would also allow the type handle pointer to be used to easily pass types to native code (such as when writing a custom runtime host).

API Proposal

public struct RuntimeTypeHandle
{
+     public static RuntimeTypeHandle FromIntPtr(IntPtr value);
+     public static IntPtr ToIntPtr(RuntimeTypeHandle value);
}

public struct RuntimeFieldHandle
{
+    public static RuntimeFieldHandle FromIntPtr(IntPtr value);
+    public static IntPtr ToIntPtr(RuntimeFieldHandle value);
}

public struct RuntimeMethodHandle
{
+    public static RuntimeMethodHandle FromIntPtr(IntPtr value);
+    public static IntPtr ToIntPtr(RuntimeMethodHandle value);
}

API Usage

var api = new NativeAPI
{
    GetTypeHandle      = &GetTypeHandle,
    GetFullTypeName    = &GetFullTypeName,
    GetFunctionPointer = &GetFunctionPointer
};

// This native method may be interested in querying for
// pointers to UnmanagedCallersOnly methods to invoke.
SomeNativeMethod(&api);
[UnmanagedCallersOnly]
static IntPtr GetTypeHandle(IntPtr typeName)
{
    return Type.GetType(Marshal.PtrToStringUni(typeName)).TypeHandle.Value;
}

[UnmanagedCallersOnly]
static IntPtr GetFunctionPointer(IntPtr type, IntPtr methodName)
{
    var type   = Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(type));
    var method = type.GetMethod(Marshal.PtrToStringUni(methodName), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
    return method.MethodHandle.GetFunctionPointer();
}

[UnmanagedCallersOnly]
static IntPtr GetFullTypeName(IntPtr type)
{
    return Marshal.StringToHGlobalUni(Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(type))?.FullName);
}

Alternative Designs

No response

Risks

Being an unmanaged pointer, RuntimeTypeHandle.Value will not keep a collectible type alive. This would need to be clearly called out in documentation and could be a potential problem when the API is not used correctly.

Author: DaZombieKiller
Assignees: -
Labels:

api-approved, area-System.Runtime, in-pr

Milestone: 7.0.0

@ghost ghost added in-pr There is an active PR which will close this issue when it is merged and removed in-pr There is an active PR which will close this issue when it is merged labels May 15, 2022
Interop-CoreCLR 7.0 automation moved this from To do to Done May 16, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label May 16, 2022
@dotnet dotnet locked as resolved and limited conversation to collaborators Jun 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

8 participants