Description
Begin binding Desktop JVM libraries.
We should try to see if we can use a 1 "module" per assembly binding strategy.
We should look into updating class-parse
to natively support the jmod
file format. Lacking that, we can get class-parse
to read all types in a Java module via e.g.
cd path/to/jdk-11
mkdir x
./bin/jmod extract jmods/java.base.jmod --dir x
mono path/to/class-parse.exe @classes.txt > api.xml
For me, with JDK 11, class-parse
is able to find 5609 classes and 553 interfaces.
class-parse
also emits 305 warnings:
class-parse: Unable to read file 'x/classes/module-info.class': Unknown constant type 0x00000013.
class-parse
needs to actually support Java modules.
Plus:
class-parse: method com/sun/java/util/jar/pack/Attribute$Layout.compareTo(Ljava/lang/Object;)I: Local variable type descriptor mismatch! Got 'Ljava/lang/Object;'; expected 'Lcom/sun/java/util/jar/pack/Attribute$Layout;'.
…
class-parse: method java/util/concurrent/ConcurrentHashMap$EntrySetView.add(Ljava/lang/Object;)Z: Local variable type descriptor mismatch! Got 'Ljava/lang/Object;'; expected 'Ljava/util/concurrent/ConcurrentHashMap$EntrySetView;'
…
Regardless, we can emit an api.xml
for the java.base.jmod
module. However, to make a binding, we need generator
support:
mono path/to/generator.exe -o bindings --codegen-target JavaInterop1 api.xml
This emits 35MB of C# bindings (yay!)
The problem is that the bindings are Xamarin.Android-style, and not what I had wanted for desktop use. For example, Java.Lang.Object
:
namespace Java.Lang {
// Metadata.xml XPath class reference: path="/api/package[@name='java.lang']/class[@name='Object']"
[global::Android.Runtime.Register ("java/lang/Object", DoNotGenerateAcw=true)]
public partial class Object {
static readonly JniPeerMembers _members = new JniPeerMembers ("java/lang/Object", typeof (Object));
// Metadata.xml XPath constructor reference: path="/api/package[@name='java.lang']/class[@name='Object']/constructor[@name='Object' and count(parameter)=0]"
[Register (".ctor", "()V", "")]
public unsafe Object () : this (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
This calls the Xamarin.Android-style (IntPtr, JniHandleOwnership)
constructor. Java.Interop doesn't have an Android.Runtime.JniHandleOwnership
enum; that's (currently) specific to Xamarin.Android. Instead, the desired Java.Interop approach is to instead have/use a (ref JniObjectReference, JniObjectReferenceOptions)
constructor, as used by TestType
:
Next:
public unsafe Java.Lang.Class Class {
// Metadata.xml XPath method reference: path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='getClass' and count(parameter)=0]"
[Register ("getClass", "()Ljava/lang/Class;", "")]
get {
const string __id = "getClass.()Ljava/lang/Class;";
try {
var __rm = _members.InstanceMethods.InvokeNonvirtualObjectMethod (__id, this, null);
return global::Java.Lang.Object.GetObject<Java.Lang.Class> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
} finally {
}
}
}
Java.Interop doesn't currently have a Java.Lang.Object
type, though that would be created/implicit to binding java.base.jmod
. Instead, I would prefer investigating: https://github.com/xamarin/java.interop/blob/main/Documentation/Architecture.md#proposed-javainterop-architecture; that is, instead of Object.GetObject<T>()
, use JniEnvironment.Runtime.ValueManager.GetValue<T>()
.
Then there's marshal methods:
static Delegate cb_clone;
#pragma warning disable 0169
static Delegate GetCloneHandler ()
{
if (cb_clone == null)
cb_clone = JNINativeWrapper.CreateDelegate ((_JniMarshal_PP_L) n_Clone);
return cb_clone;
}
I would prefer that JNINativeWrapper
not exist on Desktop.
Creating an api.xml
is the easy part (but could certainly be made easier!). The hard part is figuring out what we want for a "nice" Desktop ABI, one that doesn't have "Android-Isms" everywhere. (For example, JniHandleOwnership
is Android.Runtime.JniHandleOwnership
; a Desktop JVM binding assembly shouldn't have any types in an Android.*
namespace!)