Skip to content

Commit

Permalink
Introduce an "explicit ABI" concept
Browse files Browse the repository at this point in the history
Context: https://devblogs.microsoft.com/oldnewthing/20210830-00/?p=105617
Context: dotnet/java-interop#795

It occurs to @jonpryor that what we're dealing with is *kinda* like
the world of COM and COM-related technologies; see [oldnewthing][0].

> C++/WinRT and C++/CX set up parallel universes like this:
>
> | C++/WinRT               | ABI                       | C++/CX            |
> | ----------------------- | ------------------------- | ----------------- |
> | winrt::Contoso::Widget  | ABI::Contoso::Widget\*    | Contoso::Widget^  |

We have an "ABI": what JNI expects/requires.

Then we have a "projection" ("binding") from the ABI, for consumption
by end users.

*Historically*, we "hand-waved" over the ABI; it was *implicit* to
how things worked, but *not* something that customers interacted with
directly.

@jonpryor would like to change that, and this seems like a reasonable
"two birds, one stone" opportunity.

The support for generic types, as described in `readm.md`, is
contingent upon a "layer of indirection":

> - Java calls the static invoker
> - The static invoker calls the instance invoker
> - The instance invoker calls the desired method

The "instance invoker" is the "layer of indirection".

The original prototype "named" this "layer of indirection"
`I[Java type name]Invoker`; e.g. given Java:

	// Java
	package example;

	public interface ArrayList<E> {
	    void add(E obj);
	}

we'd have "binding + infrastructure" of:

	// C#
	namespace Example {
	    public partial interface IArrayList<E> {
	        void Add(E obj);
	    }
	    public partial interface IArrayListInvoker {
	        void InvokeAdd(Java.Lang.Object obj);
	    }
	}

My proposal is to take some ideas from dotnet/java-interop#795
and WinRT projections, "formalizing" the ABI:

	// Convention: `jniabi.` + *Java* package name
	namespace jniabi.example {

	    // Convention: Identical to Java type name
	    public static partial class ArrayList {
	        // Convention: Java method name + "suffix" for overloads; uses "lowest level" types.
	        public static void add(IJavaPeerable self, JniObjectReference obj);
	    }

	    // Convention: `_` prefix + Java name suffix;
	    [Register ("example/ArrayList", DoNotGenerateAcw=true)]
	    public partial interface _ArrayList : IJavaObject, IJavaPeerable {
	        // Convention: Java method name + "suffix" for overloads; uses "lowest level" types.
	        [Register ("add", "(Ljava/lang/Object;)V", …);
	        void add(JniObjectReference obj);

		// jni marshal method glue…
		private static bool n_Add_Ljava_lang_Object_ (IntPtr jnienv, IntPtr native__this, IntPtr native_p0)
	        {
	            var __this = Java.Lang.Object.GetObject<_ArrayList>(native__this);
	            __this!.add (new JniObjectReference (native_p0));
	        }
	    }
	}

The "un-mangled" `jniabi.example.ArrayList` type is for "manual
binding" scenarios; see also xamarin-android/src/Mono.Android, which
has a fair bit of "hand-written" code which was produced by taking
`generator` output and copying it.  `jniabi.example.ArrayList` is
meant to remove the need for hand-copying code, but *only* provides
low-level bindings.  Actual type marshaling/etc. is "elsewhere".

The "mangled" `jniabi.example._ArrayList` type is the layer of
indirection, so that generic types *can* work.  It's not "meant" for
implementation by "normal" people, but *can* be implemented if they
want/need to take control of marshaling.  (For example, Photo data
marshaling is like a 12MB `byte[]` that needs to be marshaled EVERY
TIME, contributing to perf issues.  Being able to interject and do
"something else" could be *really* useful for some scenarios.)

Note: wrt dotnet/java-interop#795, `_ArrayList` was instead written
as `jnimarshalmethod.example.ArrayList`.  I tried this initially,
but wasn't "thrilled" with how it looked.  Please discuss.

We then separate out the "projection" ("binding"), in terms of the ABI:

	namespace Example {
	    // Note: *NO* [Register]; always implements ABI
	    public partial interface IArrayList<E> : IJavaObject, IJavaPeerable, global::jniabi.example._ArrayList
	    {
	        void Add (E obj); // projection

	        // "glue" from ABI to projection; explicitly implemented
	        void jniabi.example._ArrayList.add(JniObjectReference native_obj)
	        {
	            var obj = Java.Lang.Object.GetObject<T>(native_obj.Handle);
	            Add (obj);
	        }
	    }
	}

"Of course", we also want/need the raw types in the projection, if
only for backward compatibility (we've historically *only* bound raw
types), and this "slots in":

	namespace Example {
	    // Note: *NO* [Register]; always implements ABI
	    public partial interface IArrayList : IJavaObject, IJavaPeerable, global::jniabi.example._ArrayList
	    {
	        void Add (Java.Lang.Object? obj); // projection

	        // "glue" from ABI to projection; explicitly implemented
	        void jniabi.example._ArrayList.add(JniObjectReference native_obj)
	        {
	            var obj = Java.Lang.Object.GetObject<Java.Lang.Object>(native_obj.Handle);
	            Add (obj);
	        }
	    }
	}

The downside to having the raw type implement the ABI `_ArrayList`
type is that it *also* incurs the indirection penalty.

It *is* really flexible though!

Pros to this approach vs. the "original prototype":

  * Reduces "namespace pollution" by moving the "indirection" types
    into a separate set of namespaces.  I rather doubt that anyone
    will "accidentally" find the `jniabi.…` namespace.

  * Makes the ABI explicit, which in turn inserts flexibility.
    What if you don't *want* `java.util.Collection` to be marshaled
    as an `IEnumerable<T>`?  With this approach, you can control it.

  * It's also really awesome looking to be within a generic class and
    use `Object.GetObject<T>()`, using the type parameter directly.

  * Use of Java-originated identifier names (hopefully?) reduces
    complexity.  (I can hope, right?)

Cons:

  * Use of Java-originated identifier names may complicate things.
    Sure, they're still accessible via the `generator` object model,
    but that doesn't mean everything is "fine".
    (If wishes were horses…)

  * "ABI" names *will* need to involve "hashes" for overloads, at
    some point, because all reference types "devolve" to
    `JniObjectReference`, so unless the number of parameters differs,
    method overloads that use "separate yet reference types" will
    collide, e.g. `java.lang.String.getBytes(String)` vs.
    `java.lang.String.getBytes(java.nio.charset.Charset)`.
    The `jniabi.java.lang.String` type will need to expose e.g.
    `getBytes_hash1()` and `getBytes_hash2()` methods, where the
    hash is e.g. the CRC for the JNI method signature.

[0]: https://devblogs.microsoft.com/oldnewthing/20210830-00/?p=105617
  • Loading branch information
jonpryor committed Mar 10, 2022
1 parent 8cdf204 commit ee23b1b
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 113 deletions.
175 changes: 144 additions & 31 deletions Generic-Binding-Lib/Additions/Example.GenericType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,96 +8,209 @@

namespace Example
{
public interface IGenericTypeInvoker : IJavaObject, Java.Interop.IJavaPeerable
[global::Android.Runtime.Register ("example/GenericType", "", "Example.IGenericTypeInvoker", DoNotGenerateAcw = true)]
public partial class GenericType : global::Java.Lang.Object, global::jniabi.example._GenericType
{
static readonly Java.Interop.JniPeerMembers _members = new XAPeerMembers ("example/GenericType", typeof (IGenericTypeInvoker));
[Register ("PerformanceMethod", "(Ljava/lang/Object;)V", "GetPerformanceMethod_Ljava_lang_Object_Handler:jniabi.example._GenericType, Generic-Binding-Lib")]
public virtual unsafe void PerformanceMethod (Java.Lang.Object? p0)
{
jniabi.example.GenericType.PerformanceMethod (this, p0?.PeerReference ?? default);
}

void InvokePerformanceMethod (global::Java.Lang.Object obj);
void global::jniabi.example._GenericType.PerformanceMethod (global::Java.Interop.JniObjectReference native_p0)
{
var p0 = global::Java.Lang.Object.GetObject<Java.Lang.Object> (native_p0.Handle, JniHandleOwnership.DoNotTransfer);
PerformanceMethod (p0);
}

static Delegate cb_PerformanceMethod_Ljava_lang_Object_;
#pragma warning disable 0169
static Delegate GetPerformanceMethod_Ljava_lang_Object_Handler ()
internal static IntPtr class_ref {
get { return global::jniabi.example.GenericType._class.JniPeerType.PeerReference.Handle; }
}

[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
get { return global::jniabi.example.GenericType._class; }
}

[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
protected override IntPtr ThresholdClass {
get { return global::jniabi.example.GenericType._class.JniPeerType.PeerReference.Handle; }
}

[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
protected override global::System.Type ThresholdType {
get { return typeof (GenericType); }
}

protected GenericType (IntPtr javaReference, JniHandleOwnership transfer)
: base (javaReference, transfer)
{
if (cb_PerformanceMethod_Ljava_lang_Object_ == null)
cb_PerformanceMethod_Ljava_lang_Object_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_PerformanceMethod_Ljava_lang_Object_);
return cb_PerformanceMethod_Ljava_lang_Object_;
}

static void n_PerformanceMethod_Ljava_lang_Object_ (IntPtr jnienv, IntPtr native__this, IntPtr native_p0)
// Metadata.xml XPath constructor reference: path="/api/package[@name='example']/class[@name='GenericType']/constructor[@name='GenericType' and count(parameter)=0]"
[Register (".ctor", "()V", "")]
public unsafe GenericType ()
: base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
{
var __this = global::Java.Lang.Object.GetObject<global::Example.IGenericTypeInvoker> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
var p0 = global::Java.Lang.Object.GetObject<global::Java.Lang.Object> (native_p0, JniHandleOwnership.DoNotTransfer);
__this.InvokePerformanceMethod (p0);
const string __id = "()V";

if (((global::Java.Lang.Object) this).Handle != IntPtr.Zero)
return;

try {
var __r = global::jniabi.example.GenericType._class.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), null);
SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef);
global::jniabi.example.GenericType._class.InstanceMethods.FinishCreateInstance (__id, this, null);
} finally {
}
}

// Metadata.xml XPath method reference: path="/api/package[@name='example']/class[@name='GenericType']/method[@name='TestPerformance' and count(parameter)=2 and parameter[1][@type='T'] and parameter[2][@type='int']]"
[Register ("TestPerformance", "(Ljava/lang/Object;I)V", "")]
public unsafe void TestPerformance (Java.Lang.Object? p0, int p1)
{
try {
global::jniabi.example.GenericType.TestPerformance (this, p0?.PeerReference ?? default, p1);
}
finally {
GC.KeepAlive (p0);
}
}
#pragma warning restore 0169
}

[global::Android.Runtime.Register ("example/GenericType", "", "Example.IGenericTypeInvoker", DoNotGenerateAcw = true)]
public partial class GenericType<T> : global::Java.Lang.Object, IGenericTypeInvoker where T : global::Java.Lang.Object
public partial class GenericType<T> : global::Java.Lang.Object, global::jniabi.example._GenericType
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) { }
[Register ("PerformanceMethod", "(Ljava/lang/Object;)V", "GetPerformanceMethod_Ljava_lang_Object_Handler:jniabi.example._GenericType, Generic-Binding-Lib")]
public virtual unsafe void PerformanceMethod (T? p0)
{
jniabi.example.GenericType.PerformanceMethod (this, p0?.PeerReference ?? default);
}

public void InvokePerformanceMethod (global::Java.Lang.Object obj) => PerformanceMethod (obj.JavaCast<T> ());
void global::jniabi.example._GenericType.PerformanceMethod (global::Java.Interop.JniObjectReference native_p0)
{
var p0 = global::Java.Lang.Object.GetObject<T> (native_p0.Handle, JniHandleOwnership.DoNotTransfer);
PerformanceMethod (p0);
}

internal static IntPtr class_ref {
get { return IGenericTypeInvoker._members.JniPeerType.PeerReference.Handle; }
get { return global::jniabi.example.GenericType._class.JniPeerType.PeerReference.Handle; }
}

[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
get { return IGenericTypeInvoker._members; }
get { return global::jniabi.example.GenericType._class; }
}

[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
protected override IntPtr ThresholdClass {
get { return IGenericTypeInvoker._members.JniPeerType.PeerReference.Handle; }
get { return global::jniabi.example.GenericType._class.JniPeerType.PeerReference.Handle; }
}

[global::System.Diagnostics.DebuggerBrowsable (global::System.Diagnostics.DebuggerBrowsableState.Never)]
[global::System.ComponentModel.EditorBrowsable (global::System.ComponentModel.EditorBrowsableState.Never)]
protected override global::System.Type ThresholdType {
get { return IGenericTypeInvoker._members.ManagedPeerType; }
get { return typeof (GenericType<T>); }
}

protected GenericType (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
protected GenericType (IntPtr javaReference, JniHandleOwnership transfer)
: base (javaReference, transfer)
{
}

// Metadata.xml XPath constructor reference: path="/api/package[@name='example']/class[@name='GenericType']/constructor[@name='GenericType' and count(parameter)=0]"
[Register (".ctor", "()V", "")]
public unsafe GenericType () : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
public unsafe GenericType ()
: base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
{
const string __id = "()V";

if (((global::Java.Lang.Object) this).Handle != IntPtr.Zero)
return;

try {
var __r = IGenericTypeInvoker._members.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), null);
var __r = global::jniabi.example.GenericType._class.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), null);
SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef);
IGenericTypeInvoker._members.InstanceMethods.FinishCreateInstance (__id, this, null);
global::jniabi.example.GenericType._class.InstanceMethods.FinishCreateInstance (__id, this, null);
} finally {
}
}

// Metadata.xml XPath method reference: path="/api/package[@name='example']/class[@name='GenericType']/method[@name='TestPerformance' and count(parameter)=2 and parameter[1][@type='T'] and parameter[2][@type='int']]"
[Register ("TestPerformance", "(Ljava/lang/Object;I)V", "")]
public unsafe void TestPerformance (T p0, int p1)
{
try {
global::jniabi.example.GenericType.TestPerformance (this, p0.PeerReference, p1);
}
finally {
GC.KeepAlive (p0);
}
}
}
}

namespace jniabi.example {
using Java.Interop;
public static partial class GenericType {

public static JniPeerMembers _class => _GenericType._class;

public static unsafe void PerformanceMethod (IJavaPeerable self, JniObjectReference obj)
{
const string __id = "PerformanceMethod.(Ljava/lang/Object;)V";
try {
Java.Interop.JniArgumentValue* __args = stackalloc Java.Interop.JniArgumentValue [1];
__args [0] = new Java.Interop.JniArgumentValue (obj);
_GenericType._class.InstanceMethods.InvokeVirtualVoidMethod (__id, self, __args);
} finally {
global::System.GC.KeepAlive (self);
}
}

public static unsafe void TestPerformance (IJavaPeerable self, JniObjectReference p0, int p1)
{
const string __id = "TestPerformance.(Ljava/lang/Object;I)V";
IntPtr native_p0 = JNIEnv.ToLocalJniHandle (p0);
try {
Java.Interop.JniArgumentValue* __args = stackalloc Java.Interop.JniArgumentValue [2];
__args [0] = new Java.Interop.JniArgumentValue (native_p0);
__args [0] = new Java.Interop.JniArgumentValue (p0);
__args [1] = new Java.Interop.JniArgumentValue (p1);
IGenericTypeInvoker._members.InstanceMethods.InvokeNonvirtualVoidMethod (__id, this, __args);
_GenericType._class.InstanceMethods.InvokeNonvirtualVoidMethod (__id, self, __args);
} finally {
JNIEnv.DeleteLocalRef (native_p0);
global::System.GC.KeepAlive (p0);
global::System.GC.KeepAlive (self);
}
}
}

[Register (JniTypeName)]
public interface _GenericType : IJavaObject, global::Java.Interop.IJavaPeerable {

public const string JniTypeName = "example/GenericType";
public static readonly global::Java.Interop.JniPeerMembers _class = new XAPeerMembers (JniTypeName, typeof (_GenericType));

void PerformanceMethod (JniObjectReference obj);

private static Delegate? cb_PerformanceMethod_Ljava_lang_Object_;
#pragma warning disable 0169
private static Delegate GetPerformanceMethod_Ljava_lang_Object_Handler ()
{
if (cb_PerformanceMethod_Ljava_lang_Object_ == null)
cb_PerformanceMethod_Ljava_lang_Object_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_PerformanceMethod_Ljava_lang_Object_);
return cb_PerformanceMethod_Ljava_lang_Object_;
}

private static void n_PerformanceMethod_Ljava_lang_Object_ (IntPtr jnienv, IntPtr native__this, IntPtr native_p0)
{
var __this = global::Java.Lang.Object.GetObject<_GenericType> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
var p0 = new JniObjectReference (native_p0);
__this!.PerformanceMethod (p0);
}
#pragma warning restore 0169
}
}
Loading

0 comments on commit ee23b1b

Please sign in to comment.