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

Research injecting an interface to resolve open generic members and types to closed ones in the managed static registrar #18356

Closed
Tracked by #18584
rolfbjarne opened this issue May 26, 2023 · 1 comment · Fixed by #18421
Assignees
Labels
app-size enhancement The issue or pull request is an enhancement performance If an issue or pull request is related to performance
Milestone

Comments

@rolfbjarne
Copy link
Member

rolfbjarne commented May 26, 2023

Ref: https://dotnetfiddle.net/orpEt8
Ref: dotnet/runtime#86649 (comment)

This would certainly speed up calling generic types/members from Objective-C, and might also have a size benefit.

using System;
using System.Runtime.InteropServices;

var inst = new GenericNSObject<Dummy>();
var handle = GCHandle.Alloc(inst);

var res = GenericNSObject_1__Registrar_Callbacks__.DoSomething((IntPtr)handle, 12, true);
Console.WriteLine($"res: {res}");

var res2 = GenericNSObject_1__Registrar_Callbacks__.DoSomething((IntPtr)handle, 24, false);
Console.WriteLine($"res2: {res2}");

class Dummy {}

// app code
public partial class GenericNSObject<T> : NSObject {
    // [Export("blah")]
    public int DoSomething(int p0, bool p1) {
        Console.WriteLine($"{typeof(T)} - {p0}, {p1}");
        return p1 ? p0 : -p0;
    }
}

// generated code
internal interface GenericNSObject_1__GenericsProxy {
    int DoSomething_Proxy(int p0, bool p1);
}

partial class GenericNSObject<T> : GenericNSObject_1__GenericsProxy {
    public int DoSomething_Proxy(int p0, bool p1) => DoSomething(p0, p1);
}

internal static class GenericNSObject_1__Registrar_Callbacks__ {
    // [UnmanagedCallersOnly(EntryPoint = "__calback_123_GenericNSObject_1__DoSomething")]
    public static int DoSomething(IntPtr handle, int p0, bool p1) {
        var obj = Runtime.GetNSObject<GenericNSObject_1__GenericsProxy>(handle);
        return obj.DoSomething_Proxy(p0, p1);
    }
}

public class NSObject
{
}

public class Runtime
{
    public static T GetNSObject<T>(IntPtr handle)
        => (T)GCHandle.FromIntPtr(handle).Target!;
}

Credit to @simonrozsival for this idea.

@rolfbjarne rolfbjarne added enhancement The issue or pull request is an enhancement performance If an issue or pull request is related to performance app-size labels May 26, 2023
@rolfbjarne rolfbjarne added this to the .NET 8 milestone May 26, 2023
@simonrozsival
Copy link
Contributor

I'll try to submit a PR for this issue this or the next week.

rolfbjarne pushed a commit that referenced this issue Jun 28, 2023
…c types (#18421)

Closes #18356

In the static UnmanagedCallersOnly methods we don't know the generic
parameters of the type we're working with and we need to use this trick
to be able to call methods on the generic type without using reflection.
When we call a non-generic interface method implemented on a generic
class, the .NET runtime will resolve the generic parameters for us. In
the implementation of the interface method, we can simply use the
generic parameters and generate the same code we usually generate in the
UnmanagedCallersOnly callback method.

This is an example of the code we generate in addition to user code:

```csharp
internal interface __IRegistrarGenericTypeProxy__CustomNSObject_1__
{
	void __IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (IntPtr p0);
}

public class CustomNSObject<T> : NSObject, __IRegistrarGenericTypeProxy__CustomNSObject_1__
	where T : NSObject
{
	[Export ("someMethod:")]
	public void SomeMethod (T someInput)
	{
		// ...
	}

	// generated implementation of the proxy interface:
	public void __IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (IntPtr sel, IntPtr p0, IntPtr* exception_gchandle)
	{
		try {
			var obj0 = (T) Runtime.GetNSObject<T> (p0);
			SomeMethod (obj0);
		} catch (Exception ex) {
			*exception_gchandle = Runtime.AllocGCHandle (ex);
		}
	}

	// generated registrar callbacks:
	private static class __Registrar_Callbacks__
	{
		[UnmanagedCallersOnly (EntryPoint = "_callback_1_CustomNSObject_1_SomeMethod")]
		public unsafe static void callback_1_CustomNSObject_1_SomeMethod (IntPtr pobj, IntPtr sel, IntPtr p0, IntPtr* exception_gchandle)
		{
			var proxy = (__IRegistrarGenericTypeProxy__CustomNSObject_1__)Runtime.GetNSObject (pobj);
			proxy.__IRegistrarGenericTypeProxy__CustomNSObject_1____SomeMethod (sel, p0, exception_gchandle);
		}
	}
}
```
```csharp
// regular non-generic class for comparison:
public class CustomNSObject : NSObject
	where T : NSObject
{
	[Export ("someMethod:")]
	public void SomeMethod (NSSet someInput)
	{
		// ...
	}
	
	private static class __Registrar_Callbacks__
	{
		[UnmanagedCallersOnly (EntryPoint = "_callback_1_CustomNSObject_1_SomeMethod")]
		public unsafe static void callback_1_CustomNSObject_1_SomeMethod (IntPtr pobj, IntPtr sel, IntPtr p0, IntPtr* exception_gchandle)
		{
			try {
				NSSet obj0 = Runtime.GetNSObject<NSSet> (p0);
				SomeMethod (obj0);
			} catch (Exception ex) {
				*exception_gchandle = Runtime.AllocGCHandle (ex);
			}
		}
	}
}
```

---------

Co-authored-by: Alex Soto <[email protected]>
@ghost ghost locked as resolved and limited conversation to collaborators Jul 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
app-size enhancement The issue or pull request is an enhancement performance If an issue or pull request is related to performance
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants