java.interop icon indicating copy to clipboard operation
java.interop copied to clipboard

Support C# Function Pointers?

Open jonpryor opened this issue 5 years ago • 1 comments

Context: https://github.com/dotnet/csharplang/blob/master/proposals/function-pointers.md Context: https://github.com/dotnet/runtime/issues/32963

There is a proposal to add function pointers to the C# language. This would allow obtaining a pointer to a C# static method and hand it to native code without going through Delegates, and would be more efficient.

By @jonpryor's reading of the spec, In order to create a function pointer which can be handed off to native code, the method needs to have a [NativeCallableAttribute] declaration, but:

Methods marked by [[NativeCallable]] attribute are only callable from native code, not managed (can’t call methods, create a delegate, etc …).

This restriction means that we can't use the existing JNINativeWrapper.CreateDelegate() approach, which is used as part of exception marshaling, e.g.

https://github.com/xamarin/java.interop/blob/ef1d37b00ca7f84f9d07e057857af938816205a3/tests/generator-Tests/Tests-Core/expected.ji/Android.Text.ISpannable.cs#L70

The "easiest" way to support C# function pointers, if/when they ever exist, will be to integrate support into jnimarshalmethod-gen, such that instead of emitting

partial class __<$>_jni_marshal_methods {
    public static IntPtr FuncIJavaObject (IntPtr __jnienv, IntPtr __this) {…}

    public static void __RegisterNativeMembers (JniNativeMethodRegistrationArguments args)
    {
        args.AddRegistrations (new JniNativeMethodRegistration[]{
                new JniNativeMethodRegistration ("funcIJavaObject",
                    "()Ljava/lang/Object;",
                    new Func<IntPtr, IntPtr, IntPtr> (FuncIJavaObject)),
                …
        });
    }
}

We would emit "equivalent" code which uses [NativeCallable] and function pointers:

partial class __<$>_jni_marshal_methods {
    [NativeCallable]
    public static IntPtr FuncIJavaObject (IntPtr __jnienv, IntPtr __this) {…}

    public static void __RegisterNativeMembers (JniNativeMethodRegistrationArguments args)
    {
        delegate*<IntPtr, IntPtr> fp = &FuncIJavaObject;
        args.AddRegistrations (new JniNativeMethodRegistration[]{
                new JniNativeMethodRegistration ("funcIJavaObject",
                    "()Ljava/lang/Object;",
                    (IntPtr) fp),
                …
        });
    }
}

Note that for the above to work, we'd need to add a JniNativeMethodRegistration(string, string, IntPtr) constructor, which may be difficult to do without an ABI break, as that type only exposes fields, not properties, and we can't change the size of that struct:

https://github.com/xamarin/java.interop/blob/master/src/Java.Interop/Java.Interop/JniNativeMethodRegistration.cs

I doubt anybody is currently using this struct, so it might not be a problem to break ABI, but if it is, we'll need to think of a workaround.

jonpryor avatar Jun 19 '20 14:06 jonpryor

Alternatively we might try to modify our current code which uses the System.Linq.Expression and generate the IL with ldftn instructions ourselves. It would need a bit of investigation whether it is possible and how to do it.

radekdoulik avatar Jun 19 '20 15:06 radekdoulik