GithubHelp home page GithubHelp logo

genericbindingprototype's Introduction

POC for binding Java generics as C# generics

Today, we allow Java to invoke into a C# type by using a static method in the C# type which we can call with reflection.

For example, given:

public class ArrayList
{
	public virtual void Add (Object obj) { ... }
}

We can give Java a static function delegate to call which can invoke the method on the instance:

public class ArrayList
{
	public virtual void Add (Object obj) { ... }

	public static void n_Add (IntPtr jnienv, IntPtr native__this, IntPtr native_p0)
	{
		var __this = global::Java.Lang.Object.GetObject<global::ArrayList> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
		var p0 = global::Java.Lang.Object.GetObject<global::Java.Lang.Object> (native_p0, JniHandleOwnership.DoNotTransfer);
		__this.Add (p0);
	}
}

However if ArrayList was generic, you cannot call it without knowing what T is, which you cannot know in a static method.

public class ArrayList<T>
{
	public static void n_Add (IntPtr jnienv, IntPtr native__this, IntPtr native_p0)
	{
		// What goes here? ------------------------------------------------˅
		var __this = global::Java.Lang.Object.GetObject<global::ArrayList<???> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
		...
	}
}

You also can't call the method in the first place with reflection, because you need to provide T to invoke the method:

public class Test<T>
{
	public static void DoThing () { }
}

var type = Assembly.GetExecutingAssembly ().GetType ("Test`1");
var method = type.GetMethod ("DoThing", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
method.Invoke (null, null);

produces:

System.InvalidOperationException: Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.

This POC proposes having both a static invoker AND an instance invoker:

  • Java calls the static invoker
  • The static invoker calls the instance invoker
  • The instance invoker calls the desired method

The instance invoker method is defined on a non-generic "Invoker" interface so it can be called without needing generic type information, but is implemented as an instance method in the generic bound class which does have access to generic type information needed to call the user's method.

This extra level of indirection incurs a ~2%-3% performance penalty when marshaling methods that use generic types: dotnet/java-interop#918 (comment)

Binding a generic Java class example

Full sample:

public interface IArrayListInvoker : IJavaObject, Java.Interop.IJavaPeerable
{
	void InvokeAdd (Object obj);
}

[global::Android.Runtime.Register ("java/util/ArrayList", DoNotGenerateAcw=true)]
public class ArrayList<T> : IArrayListInvoker
{
	// User's method
	[Register ("add", "(Ljava/lang/Object;)Z", "GetAdd_Ljava_lang_Object_Handler")]
	public virtual void Add (T obj) { ... }

	// Static invoker
	public static void n_Add (IntPtr jnienv, IntPtr native__this, IntPtr native_p0)
	{
		// Cast as non-generic IArrayListInvoker type
		var __this = global::Java.Lang.Object.GetObject<global::IArrayListInvoker> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
		var p0 = global::Java.Lang.Object.GetObject<global::Java.Lang.Object> (native_p0, JniHandleOwnership.DoNotTransfer);
		__this.InvokeAdd (p0);
	}

	// Instance invoker
	public void IArrayListInvoker.InvokeAdd (Object obj) => Add (obj.JavaCast<T> ());
}

Binding a generic Java interface example

Full sample:

Binding a generic Java interface is a bit more challenging, but we can (ab)use default interface members in order to accomplish the same "static invoker" -> "instance invoker" strategy.

Generic Java interface

public interface CustomList<T> {
	public abstract boolean add (T obj);
}

C# binding

public interface ICustomListInterfaceInvoker : IJavaObject, Java.Interop.IJavaPeerable
{
	bool InvokeAdd (global::Java.Lang.Object obj);
}

[Register ("example/CustomList", "", "Example.ICustomListInvoker")]
public partial interface ICustomList<T> : IJavaObject, Java.Interop.IJavaPeerable, ICustomListInterfaceInvoker where T : global::Java.Lang.Object
{
	// User's method
	[Register ("add", "(Ljava/lang/Object;)Z", "GetAdd_Ljava_lang_Object_Handler:Example.ICustomListInvoker, Generic-Binding-Lib")]
	bool Add (T p0);

	// Instance invoker (DIM)
	bool ICustomListInterfaceInvoker.InvokeAdd (global::Java.Lang.Object obj) => Add (obj.JavaCast<T> ());
}

[global::Android.Runtime.Register ("example/CustomList", DoNotGenerateAcw=true)]
internal partial class ICustomListInvoker : global::Java.Lang.Object
{
	static Delegate cb_add_Ljava_lang_Object_;

	static Delegate GetAdd_Ljava_lang_Object_Handler ()
		=> cb_add_Ljava_lang_Object_ ??= JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_Z) n_Add_Ljava_lang_Object_);

	// Static invoker
	static bool n_Add_Ljava_lang_Object_ (IntPtr jnienv, IntPtr native__this, IntPtr native_p0)
	{
		// Cast as non-generic ICustomListInterfaceInvoker type
		var __this = global::Java.Lang.Object.GetObject<global::Example.ICustomListInterfaceInvoker> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
		var p0 = global::Java.Lang.Object.GetObject<global::Java.Lang.Object> (native_p0, JniHandleOwnership.DoNotTransfer);
		return __this.InvokeAdd (p0);
	}
}

genericbindingprototype's People

Contributors

jpobst avatar

Watchers

 avatar  avatar

Forkers

jonpryor

genericbindingprototype's Issues

README.md clarifications

Context: dotnet/java-interop#918

Shared terminology, or improving our terminology, is important. Two terms of importance are:

  • "marshal methods", and
  • Registration of marshal methods.

readme.md indirectly mentions both of these. It would help matters if it explicitly mentioned these; https://github.com/jpobst/GenericBindingPrototype/blame/main/readme.md#L44

You also can't call the method in the first place with reflection, because you need to provide T to invoke the method:

is the marshal method registration scenario.

It may also help to clarify whether Java or C# is being referred to, and sometimes I'm confused: https://github.com/jpobst/GenericBindingPrototype/blob/main/readme.md#L30

However if `ArrayList` was generic, you cannot call it without knowing what `T` is,
which you cannot know in a `static` method.

C# does allow static methods to take advantage of type parameters in the declaring type. Java does not:

class G<T> {
    // error: non-static type variable T cannot be referenced from a static context
    public static void m(T value) {}
}

Invoker Interface?

Is the e.g. IArrayListInvoker interface necessary?

A "generic" concern is around "corner cases", and two corner cases that come to mind are:

  • Use of "raw" types. If a Java method wants ArrayList<?>, e.g. MethodHandle.invokeWithArguments(List<?> arguments, we either need to bind this as IList (raw type) or as IList<object>.
  • Some Java constructs will require the use of a "raw" type anyway.

For example, static methods on generic Java types cannot use the declaring type parameter:

class G<T> {
    // error: non-static type variable T cannot be referenced from a static context
    public static void m(T value) {}
   
    public static void n(String s) {}
}

This suggests that we probably need the "raw" type anyway; a G<T>.n() binding would need to be:

class G : Java.Lang.Object {
    [Register ("n", "(Ljava/lang/String;)V",)]
    public static void N () =>;
}

If we need the raw type anyway -- which currently looks likely to be true -- then do we actually need the e.g IArrayListInvoker type? Or could we instead "merge" that with the raw type instead?

Meaning, instead of:

public interface IGenericTypeInvoker : IJavaObject, Java.Interop.IJavaPeerable {
    static void n_PerformanceMethod_Ljava_lang_Object_ (IntPtr jnienv, IntPtr native__this, IntPtr native_p0);
}

public partial class GenericType<T> : global::Java.Lang.Object, IGenericTypeInvoker
    where T : global::Java.Lang.Object
{
    [Register ("PerformanceMethod", "(Ljava/lang/Object;)V", "GetPerformanceMethod_Ljava_lang_Object_Handler:Example.IGenericTypeInvoker, Generic-Binding-Lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
    public virtual unsafe void PerformanceMethod (T p0);
}

we could instead do:

// "raw" binding, ~identical to what `generator` already emits
public class GenericType : Java.Lang.Object {
    static Delegate GetPerformanceMethod_Ljava_lang_Object_Handler();
    static void n_PerformanceMethod_Ljava_lang_Object_ (IntPtr jnienv, IntPtr native__this, IntPtr native_p0);

    [Register ("PerformanceMethod", "(Ljava/lang/Object;)V", "GetPerformanceMethod_Ljava_lang_Object_Handler:")]
    public virtual unsafe void PerformanceMethod (Java.Lang.Object obj);
}

// "new" generic binding
public class GenericType<T> : GenericType
    where T : Java.Lang.Object
{
    public override void PerformanceMethod (Java.Lang.Object obj) => PerformanceMethod (ob?.JavaCast<T>());
    public virtual void PerformanceMethod (T obj);
}

I think that would work?

(I'd test it and submit a PR, except my app crashes; see #2.)

App crashes on startup?

When I try to run Generic-Binding-Lib-Sample, it crashes on startup:

E ding_lib_sampl: * Assertion at /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mono/mini/mini-exceptions.c:882, condition `klass' not met
F libc    : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 14383 (ding_lib_sample), pid 14383 (ding_lib_sample)
E DEBUG   : failed to read /proc/uptime: Permission denied
F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
F DEBUG   : Build fingerprint: 'google/raven/raven:12/SQ1D.220105.007/8030436:user/release-keys'
F DEBUG   : Revision: 'MP1.0'
F DEBUG   : ABI: 'arm64'
F DEBUG   : Timestamp: 2022-02-11 14:17:52.817057153-0500
F DEBUG   : Process uptime: 0s
F DEBUG   : Cmdline: com.companyname.generic_binding_lib_sample
F DEBUG   : pid: 14383, tid: 14383, name: ding_lib_sample  >>> com.companyname.generic_binding_lib_sample <<<
F DEBUG   : uid: 10247
F DEBUG   : tagged_addr_ctrl: 0000000000000001
F DEBUG   : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
F DEBUG   :     x0  0000000000000000  x1  000000000000382f  x2  0000000000000006  x3  0000007fe8f7d840
F DEBUG   :     x4  64692e7164636b68  x5  64692e7164636b68  x6  64692e7164636b68  x7  7f7f7f7f7f7f7f7f
F DEBUG   :     x8  00000000000000f0  x9  00000077bb9720b0  x10 0000000000000000  x11 ffffff80fffffbdf
F DEBUG   :     x12 0000000000000001  x13 0000000000000091  x14 0000007fe8f7c6e0  x15 000035d2e038daa4
F DEBUG   :     x16 00000077bba0f050  x17 00000077bb9eccd0  x18 00000077b51406cc  x19 000000000000382f
F DEBUG   :     x20 000000000000382f  x21 00000000ffffffff  x22 0000000000000000  x23 000000000000006e
F DEBUG   :     x24 0000000000008041  x25 00000074a38f487c  x26 00000074a38f4890  x27 00000000ffffffff
F DEBUG   :     x28 b40000757de6faa0  x29 0000007fe8f7d8c0
F DEBUG   :     lr  00000077bb99f82c  sp  0000007fe8f7d820  pc  00000077bb99f85c  pst 0000000000001000
F DEBUG   : backtrace:
F DEBUG   :       #00 pc 000000000004f85c  /apex/com.android.runtime/lib64/bionic/libc.so (abort+168) (BuildId: 28943f8bb3b7b23557619af9a38223c5)
F DEBUG   :       #01 pc 00000000000273cc  /data/app/~~h7Qx3Y_lU7NCtIBfGcy8Kg==/com.companyname.generic_binding_lib_sample-LzhwCazXg2hOzWDrEUQnBg==/lib/arm64/libmonodroid.so (xamarin::android::internal::MonodroidRuntime::mono_log_handler(char const*, char const*, char const*, int, void*)+144) (BuildId: 89953becfb9856d7cc44a501117b735af4c0f536)
F DEBUG   :       #02 pc 000000000028b6f8  /data/app/~~h7Qx3Y_lU7NCtIBfGcy8Kg==/com.companyname.generic_binding_lib_sample-LzhwCazXg2hOzWDrEUQnBg==/lib/arm64/libmonosgen-2.0.so (monoeg_g_logv_nofree+192)

I'm not yet sure why this is happening.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.