-
Notifications
You must be signed in to change notification settings - Fork 52
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
Desktop JDK-11 Java.Base Binding #858
Labels
enhancement
Proposed change to current functionality
Comments
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Oct 13, 2021
Context: dotnet#858 Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720 Context: https://openjdk.java.net/projects/jigsaw/ Context: xamarin/monodroid@c9e5cbd JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar` with a set of `.jmod` files. Thus, as of JDK 9, there is no `.jar` file to try to parse with `class-parse`, only `.jmod` files! A `.jmod` file, in turn, is still a ZIP container, much like `.jar` files, but: 1. With a different internal directory structure, and 2. With a custom file header. The result of (2) is that while `unzip -l` can show the contents of a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive` cannot process the file: % mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. <api api-source="class-parse" /> Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod` files by *copying* the file to a new file, trimming off the 4 byte { 0x4a, 0x4d, 0x01, 0x00 } header. Copying to a new file is necessary because I couldn't find an easy way to avoid it; I tried using a "wrapper" `System.IO.Stream` which would skip the firsta 4 bytes, that but that didn't work as desired. Once able to read a `.jmod` file, *warnings* appeared, a'la class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Local variables array has 2 entries ('LocalVariableTableAttribute( LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0), LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1), LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2), LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))' ); descriptor has 3 entries! class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries! This was a variation on the "JDK 8?" block that previously didn't have much detail, in part because it didn't have a repro. Now we have a repro, based on [JDK code][0] which contains a class declaration within a method declaration public List<String> func (StringBuilder value) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } public String get(int index) { return value.toString(); } } return new MyStringList("a", 0xb); } The deal is that `func()` contains a `MyStringList` class, which in turn contains a constructor with two parameters. *However*, as far as Java bytecode is concerned, the constructor contains *4* local variables with StartPC==0, which is what we use to suggest parameters. The `this` parameter is skipped because it's a non-static inner class, and `this$0` was skipped because previously not-fully-understood "JDK 8?" condition. Now we know what it is: it's the 'outer `this`" of the inner class. `a` and `b` are the actually declared params. However, then we look at the descriptor value, which has 4 entries: 1. `Lcom/xamarin/JavaType;`: declaring/outer class 2. `Ljava/lang/String;`: parameter `a` 3. `I`: parameter `b` 4. `Ljava/lang/StringBuilder`: Closure variable from containing method. (1) is skipped, as it's a non-`static` inner class, leaving 3 parameters which are inferred from the Descriptor. What causes the warning is the 4th `StringBuilder` parameter. This comes from declaring scope, from `func(StringBuilder value)`. Update the comparison logic so that we skip "closure variables". Unfortunately, we still *infer* that a parameter comes from a closure by seeing if there is a declared field with the same type which: 1. Has a `val$` prefix (*heavy sigh*), and 2. Has the `.Final` and `.Synthetic` flags. This (and other) fixes allows us to parse `JavaType$1MyStringList.class` without emitting warnings: % mono bin/Debug/class-parse.exe 'tests/Xamarin.Android.Tools.Bytecode-Tests/obj/Debug/classes/com/xamarin/JavaType$1MyStringList.class' >/dev/null # no warnings Unfortunately, while this is a *start*, processing `java.base.jmod` results in 28 warnings: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null 2>e.txt && wc -l e.txt 28 e.txt Processing Android API-31 results in no warnings: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null 2>e.txt % wc -l e.txt 0 e.txt [0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Oct 14, 2021
Context: dotnet#858 Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720 Context: https://openjdk.java.net/projects/jigsaw/ Context: xamarin/monodroid@c9e5cbd JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar` with a set of `.jmod` files. Thus, as of JDK 9, there is no `.jar` file to try to parse with `class-parse`, only `.jmod` files! A `.jmod` file, in turn, is still a ZIP container, much like `.jar` files, but: 1. With a different internal directory structure, and 2. With a custom file header. The result of (2) is that while `unzip -l` can show the contents of a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive` cannot process the file: % mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. <api api-source="class-parse" /> Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod` files by using `PartialStream` (73096d9) to skip the first 4 bytes. Once able to read a `.jmod` file, *warnings* appeared, a'la class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Local variables array has 2 entries ('LocalVariableTableAttribute( LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0), LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1), LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2), LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))' ); descriptor has 3 entries! class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries! This was a variation on the "JDK 8?" block that previously didn't have much detail, in part because it didn't have a repro. Now we have a repro, based on [JDK code][0] which contains a class declaration within a method declaration public List<String> staticActionWithGenerics(…) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } public String get(int index) { return value.toString(); } } } The deal is that `staticActionWithGenerics()` contains a `MyStringList` class, which in turn contains a constructor with two parameters. *However*, as far as Java bytecode is concerned, the constructor contains *4* local variables with StartPC==0, which is what we use to infer parameter names. Refactor, cleanup, and otherwise rewrite huge swaths of `Methods.cs` to get to a "happy medium" of: * No warnings from our unit tests, ensured by updating `ClassFileFixture` to have a `[SetUp]` method which sets the `Log.OnLog` field to a delegate which may call `Assert.Fail()` when invoked. (This asserts for all non-Kotlin messages.) * No warnings when processing `java.base.jmod`: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null # no error messages * No warnings when processing Android API-31: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null # no error messages Aside: closures are *weird* and finicky. Consider the following Java code: class ClosureDemo { public void m(String a) { class Example { public Example(int x) { System.out.println (a); } } } } It looks like the JNI signature for the `Example` constructor might be `(I)V`, but isn't. It is instead: (LClosureDemo;ILjava/lang/String;)V Breaking that down: * `LClosureDemo;`: `Example` is an inner class, and thus has an implicit reference to the containing type. OK, easy to forget. * `I`: the `int x` parameter. Expected. * `Ljava/lang/String`: the `String a` parameter from the enclosing scope! This is the closure parameter. This does make sense. The problem is that it's *selective*: only variables used within `Example` become extra parameters. If the `Example` constructor is updated to remove the `System.out.println(a)` statement, then `a` is no longer used, and is no longer present as a constructor parameter. The only way I found to "reasonably" determine if a constructor parameter was a closure parameter was by checking all fields in the class with names starting with `val$`, and then comparing the types of those fields to types within the enclosing method's descriptor. I can't think of a way to avoid using `val$`. :-() [0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Oct 14, 2021
Context: dotnet#858 Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720 Context: https://openjdk.java.net/projects/jigsaw/ Context: xamarin/monodroid@c9e5cbd JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar` with a set of `.jmod` files. Thus, as of JDK 9, there is no `.jar` file to try to parse with `class-parse`, only `.jmod` files! A `.jmod` file, in turn, is still a ZIP container, much like `.jar` files, but: 1. With a different internal directory structure, and 2. With a custom file header. The result of (2) is that while `unzip -l` can show the contents of a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive` cannot process the file: % mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. <api api-source="class-parse" /> Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod` files by using `PartialStream` (73096d9) to skip the first 4 bytes. Once able to read a `.jmod` file, *warnings* appeared, a'la class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Local variables array has 2 entries ('LocalVariableTableAttribute( LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0), LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1), LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2), LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))' ); descriptor has 3 entries! class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries! This was a variation on the "JDK 8?" block that previously didn't have much detail, in part because it didn't have a repro. Now we have a repro, based on [JDK code][0] which contains a class declaration within a method declaration public List<String> staticActionWithGenerics(…) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } public String get(int index) { return value.toString(); } } } The deal is that `staticActionWithGenerics()` contains a `MyStringList` class, which in turn contains a constructor with two parameters. *However*, as far as Java bytecode is concerned, the constructor contains *4* local variables with StartPC==0, which is what we use to infer parameter names. Refactor, cleanup, and otherwise rewrite huge swaths of `Methods.cs` to get to a "happy medium" of: * No warnings from our unit tests, ensured by updating `ClassFileFixture` to have a `[SetUp]` method which sets the `Log.OnLog` field to a delegate which may call `Assert.Fail()` when invoked. (This asserts for all non-Kotlin messages.) * No warnings when processing `java.base.jmod`: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null # no error messages * No warnings when processing Android API-31: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null # no error messages Aside: closures are *weird* and finicky. Consider the following Java code: class ClosureDemo { public void m(String a) { class Example { public Example(int x) { System.out.println (a); } } } } It looks like the JNI signature for the `Example` constructor might be `(I)V`, but isn't. It is instead: (LClosureDemo;ILjava/lang/String;)V Breaking that down: * `LClosureDemo;`: `Example` is an inner class, and thus has an implicit reference to the containing type. OK, easy to forget. * `I`: the `int x` parameter. Expected. * `Ljava/lang/String`: the `String a` parameter from the enclosing scope! This is the closure parameter. This does make sense. The problem is that it's *selective*: only variables used within `Example` become extra parameters. If the `Example` constructor is updated to remove the `System.out.println(a)` statement, then `a` is no longer used, and is no longer present as a constructor parameter. The only way I found to "reasonably" determine if a constructor parameter was a closure parameter was by checking all fields in the class with names starting with `val$`, and then comparing the types of those fields to types within the enclosing method's descriptor. I can't think of a way to avoid using `val$`. :-( [0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Oct 14, 2021
Context: dotnet#858 Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720 Context: https://openjdk.java.net/projects/jigsaw/ Context: xamarin/monodroid@c9e5cbd JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar` with a set of `.jmod` files. Thus, as of JDK 9, there is no `.jar` file to try to parse with `class-parse`, only `.jmod` files! A `.jmod` file, in turn, is still a ZIP container, much like `.jar` files, but: 1. With a different internal directory structure, and 2. With a custom file header. The result of (2) is that while `unzip -l` can show and extract the contents of a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive` cannot process the file: % mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. <api api-source="class-parse" /> Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod` files by using `PartialStream` (73096d9) to skip the first 4 bytes. Once able to read a `.jmod` file, *warnings* appeared, a'la class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Local variables array has 2 entries ('LocalVariableTableAttribute( LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0), LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1), LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2), LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))' ); descriptor has 3 entries! class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries! This was a variation on the "JDK 8?" block that previously didn't have much detail, in part because it didn't have a repro. Now we have a repro, based on [JDK code][0] which contains a class declaration within a method declaration public List<String> staticActionWithGenerics(…) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } public String get(int index) { return unboundedList.toString() + value1.toString(); } } } The deal is that `staticActionWithGenerics()` contains a `MyStringList` class, which in turn contains a constructor with two parameters. *However*, as far as Java bytecode is concerned, the constructor contains *3* local variables with StartPC==0, which is what we use to infer parameter names. Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs` to get to a "happy medium" of: * No warnings from our unit tests, ensured by updating `ClassFileFixture` to have a `[SetUp]` method which sets the `Log.OnLog` field to a delegate which may call `Assert.Fail()` when invoked. (This asserts for all non-Kotlin messages.) * No warnings when processing `java.base.jmod`: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null # no error messages * No warnings when processing Android API-31: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null # no error messages Aside: closures are *weird* and finicky. Consider the following Java code: class ClosureDemo { public void m(String a) { class Example { public Example(int x) { System.out.println (a); } } } } It looks like the JNI signature for the `Example` constructor might be `(I)V`, but isn't. It is instead: (LClosureDemo;ILjava/lang/String;)V Breaking that down: * `LClosureDemo;`: `Example` is an inner class, and thus has an implicit reference to the containing type. OK, easy to forget. * `I`: the `int x` parameter. Expected. * `Ljava/lang/String`: the `String a` parameter from the enclosing scope! This is the closure parameter. This does make sense. The problem is that it's *selective*: only variables used within `Example` become extra parameters. If the `Example` constructor is updated to remove the `System.out.println(a)` statement, then `a` is no longer used, and is no longer present as a constructor parameter. The only way I found to "reasonably" determine if a constructor parameter was a closure parameter was by checking all fields in the class with names starting with `val$`, and then comparing the types of those fields to types within the enclosing method's descriptor. I can't think of a way to avoid using `val$`. :-( Another aside: closure parameter behavior *differs* between JDK 1.8 and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it assigns the *wrong* parameter names. Consider `MyStringList`: The Java constructor declaration is: public static <T, …> void staticActionWithGenerics ( T value1, … List<?> unboundedList, …) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } // … } } The JNI signature for the `MyStringList` constructor is: (Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V which is: * `String`: parameter `a` * `I`: parameter `b` * `List`: closure parameter for `unboundedList` * `Object`: closure parameter for `value1`. If we build with JDK 1.8 `javac -parameters`, the `MethodParameters` annotation states that the closure parameters are: MyStringList(String a, int b, List val$value1, Object val$unboundedList); which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`! This was fixed in JDK-11, with the `MethodParameters` annotations specifying: MyStringList(String a, int b, List val$unboundedList, Object val$value1); This means that the unit tests need to take this into consideration. Add a new `ConfiguredJdkInfo` class, which contains code similar to `tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props` to find the path to the JDK found in `make prepare`, and then determine the JDK version from that directory's `release` file. [0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Oct 14, 2021
Context: dotnet#858 Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720 Context: https://openjdk.java.net/projects/jigsaw/ Context: xamarin/monodroid@c9e5cbd JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar` with a set of `.jmod` files. Thus, as of JDK 9, there is no `.jar` file to try to parse with `class-parse`, only `.jmod` files! A `.jmod` file, in turn, is still a ZIP container, much like `.jar` files, but: 1. With a different internal directory structure, and 2. With a custom file header. The result of (2) is that while `unzip -l` can show and extract the contents of a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive` cannot process the file: % mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. <api api-source="class-parse" /> Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod` files by using `PartialStream` (73096d9) to skip the first 4 bytes. Once able to read a `.jmod` file, *warnings* appeared, a'la class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Local variables array has 2 entries ('LocalVariableTableAttribute( LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0), LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1), LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2), LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))' ); descriptor has 3 entries! class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries! This was a variation on the "JDK 8?" block that previously didn't have much detail, in part because it didn't have a repro. Now we have a repro, based on [JDK code][0] which contains a class declaration within a method declaration public List<String> staticActionWithGenerics(…) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } public String get(int index) { return unboundedList.toString() + value1.toString(); } } } The deal is that `staticActionWithGenerics()` contains a `MyStringList` class, which in turn contains a constructor with two parameters. *However*, as far as Java bytecode is concerned, the constructor contains *3* local variables with StartPC==0, which is what we use to infer parameter names. Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs` to get to a "happy medium" of: * No warnings from our unit tests, ensured by updating `ClassFileFixture` to have a `[SetUp]` method which sets the `Log.OnLog` field to a delegate which may call `Assert.Fail()` when invoked. (This asserts for all non-Kotlin messages.) * No warnings when processing `java.base.jmod`: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null # no error messages * No warnings when processing Android API-31: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null # no error messages Aside: closures are *weird* and finicky. Consider the following Java code: class ClosureDemo { public void m(String a) { class Example { public Example(int x) { System.out.println (a); } } } } It looks like the JNI signature for the `Example` constructor might be `(I)V`, but isn't. It is instead: (LClosureDemo;ILjava/lang/String;)V Breaking that down: * `LClosureDemo;`: `Example` is an inner class, and thus has an implicit reference to the containing type. OK, easy to forget. * `I`: the `int x` parameter. Expected. * `Ljava/lang/String`: the `String a` parameter from the enclosing scope! This is the closure parameter. This does make sense. The problem is that it's *selective*: only variables used within `Example` become extra parameters. If the `Example` constructor is updated to remove the `System.out.println(a)` statement, then `a` is no longer used, and is no longer present as a constructor parameter. The only way I found to "reasonably" determine if a constructor parameter was a closure parameter was by checking all fields in the class with names starting with `val$`, and then comparing the types of those fields to types within the enclosing method's descriptor. I can't think of a way to avoid using `val$`. :-( Another aside: closure parameter behavior *differs* between JDK 1.8 and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it assigns the *wrong* parameter names. Consider `MyStringList`: The Java constructor declaration is: public static <T, …> void staticActionWithGenerics ( T value1, … List<?> unboundedList, …) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } // … } } The JNI signature for the `MyStringList` constructor is: (Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V which is: * `String`: parameter `a` * `I`: parameter `b` * `List`: closure parameter for `unboundedList` * `Object`: closure parameter for `value1`. If we build with JDK 1.8 `javac -parameters`, the `MethodParameters` annotation states that the closure parameters are: MyStringList(String a, int b, List val$value1, Object val$unboundedList); which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`! This was fixed in JDK-11, with the `MethodParameters` annotations specifying: MyStringList(String a, int b, List val$unboundedList, Object val$value1); This means that the unit tests need to take this into consideration. Add a new `ConfiguredJdkInfo` class, which contains code similar to `tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props` to find the path to the JDK found in `make prepare`, and then determine the JDK version from that directory's `release` file. [0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Oct 15, 2021
Context: dotnet#858 Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720 Context: https://openjdk.java.net/projects/jigsaw/ Context: xamarin/monodroid@c9e5cbd JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar` with a set of `.jmod` files. Thus, as of JDK 9, there is no `.jar` file to try to parse with `class-parse`, only `.jmod` files! A `.jmod` file, in turn, is still a ZIP container, much like `.jar` files, but: 1. With a different internal directory structure, and 2. With a custom file header. The result of (2) is that while `unzip -l` can show and extract the contents of a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive` cannot process the file: % mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. <api api-source="class-parse" /> Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod` files by using `PartialStream` (73096d9) to skip the first 4 bytes. Once able to read a `.jmod` file, lots of debug messages appeared while parsing `java.base.jmod`, a'la: class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Local variables array has 2 entries ('LocalVariableTableAttribute( LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0), LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1), LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2), LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))' ); descriptor has 3 entries! class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries! This was a variation on the "JDK 8?" block that previously didn't have much detail, in part because it didn't have a repro. Now we have a repro, based on [JDK code][0] which contains a class declaration within a method declaration // Java public List<String> staticActionWithGenerics(…) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } public String get(int index) { return unboundedList.toString() + value1.toString(); } } } The deal is that `staticActionWithGenerics()` contains a `MyStringList` class, which in turn contains a constructor with two parameters. *However*, as far as Java bytecode is concerned, the constructor contains *3* local variables with StartPC==0, which is what we use to infer parameter names. Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs` to get to a "happy medium" of: * No warnings from our unit tests, ensured by updating `ClassFileFixture` to have a `[SetUp]` method which sets the `Log.OnLog` field to a delegate which may call `Assert.Fail()` when invoked. This asserts for all messages starting with `class-parse: methods`, which are produced by `Methods.cs`. * No warnings when processing `java.base.jmod`: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null # no error messages * No warnings when processing Android API-31: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null # no error messages Additionally, improve `Log.cs` so that there are `M(string)` overloads for the existing `M(string, params object[])` methods. This is a "sanity-preserving" change, as "innocuous-looking" code such as `Log.Debug("{foo}")` will throw `FormatException` when the `(string, params object[])` overload is used. Aside: closures are *weird* and finicky. Consider the following Java code: class ClosureDemo { public void m(String a) { class Example { public Example(int x) { System.out.println (a); } } } } It looks like the JNI signature for the `Example` constructor might be `(I)V`, but isn't. It is instead: (LClosureDemo;ILjava/lang/String;)V Breaking that down: * `LClosureDemo;`: `Example` is an inner class, and thus has an implicit reference to the containing type. OK, easy to forget. * `I`: the `int x` parameter. Expected. * `Ljava/lang/String`: the `String a` parameter from the enclosing scope! This is the closure parameter. This does make sense. The problem is that it's *selective*: only variables used within `Example` become extra parameters. If the `Example` constructor is updated to remove the `System.out.println(a)` statement, then `a` is no longer used, and is no longer present as a constructor parameter. The only way I found to "reasonably" determine if a constructor parameter was a closure parameter was by checking all fields in the class with names starting with `val$`, and then comparing the types of those fields to types within the enclosing method's descriptor. I can't think of a way to avoid using `val$`. :-( Another aside: closure parameter behavior *differs* between JDK 1.8 and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it assigns the *wrong* parameter names. Consider `MyStringList`: The Java constructor declaration is: public static <T, …> void staticActionWithGenerics ( T value1, … List<?> unboundedList, …) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } // … } } The JNI signature for the `MyStringList` constructor is: (Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V which is: * `String`: parameter `a` * `I`: parameter `b` * `List`: closure parameter for `unboundedList` * `Object`: closure parameter for `value1`. If we build with JDK 1.8 `javac -parameters`, the `MethodParameters` annotation states that the closure parameters are: MyStringList(String a, int b, List val$value1, Object val$unboundedList); which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`! This was fixed in JDK-11, with the `MethodParameters` annotations specifying: MyStringList(String a, int b, List val$unboundedList, Object val$value1); This means that the unit tests need to take this into consideration. Add a new `ConfiguredJdkInfo` class, which contains code similar to `tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props` to find the path to the JDK found in `make prepare`, and then determine the JDK version from that directory's `release` file. [0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor
added a commit
that referenced
this issue
Oct 15, 2021
Context: #858 Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720 Context: https://openjdk.java.net/projects/jigsaw/ Context: xamarin/monodroid@c9e5cbd JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar` with a set of `.jmod` files. Thus, as of JDK 9, there is no `.jar` file to try to parse with `class-parse`, only `.jmod` files! A `.jmod` file, in turn, is still a ZIP container, much like `.jar` files, but: 1. With a different internal directory structure, and 2. With a custom file header. The result of (2) is that while `unzip -l` can show and extract the contents of a `.jmod` file -- with a warning -- `System.IO.Compression.ZipArchive` cannot process the file: % mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. <api api-source="class-parse" /> Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod` files by using `PartialStream` (73096d9) to skip the first 4 bytes. Once able to read a `.jmod` file, lots of debug messages appeared while parsing `java.base.jmod`, a'la: class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Local variables array has 2 entries ('LocalVariableTableAttribute( LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0), LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1), LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2), LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))' ); descriptor has 3 entries! class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V: Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries! This was a variation on the "JDK 8?" block that previously didn't have much detail, in part because it didn't have a repro. Now we have a repro, based on [JDK code][0] which contains a class declaration within a method declaration // Java public List<String> staticActionWithGenerics(…) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } public String get(int index) { return unboundedList.toString() + value1.toString(); } } } The deal is that `staticActionWithGenerics()` contains a `MyStringList` class, which in turn contains a constructor with two parameters. *However*, as far as Java bytecode is concerned, the constructor contains *3* local variables with StartPC==0, which is what we use to infer parameter names. Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs` to get to a "happy medium" of: * No warnings from our unit tests, ensured by updating `ClassFileFixture` to have a `[SetUp]` method which sets the `Log.OnLog` field to a delegate which may call `Assert.Fail()` when invoked. This asserts for all messages starting with `class-parse: methods`, which are produced by `Methods.cs`. * No warnings when processing `java.base.jmod`: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null # no error messages * No warnings when processing Android API-31: % mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null # no error messages Additionally, improve `Log.cs` so that there are `M(string)` overloads for the existing `M(string, params object[])` methods. This is a "sanity-preserving" change, as "innocuous-looking" code such as `Log.Debug("{foo}")` will throw `FormatException` when the `(string, params object[])` overload is used. Aside: closures are *weird* and finicky. Consider the following Java code: class ClosureDemo { public void m(String a) { class Example { public Example(int x) { System.out.println (a); } } } } It looks like the JNI signature for the `Example` constructor might be `(I)V`, but isn't. It is instead: (LClosureDemo;ILjava/lang/String;)V Breaking that down: * `LClosureDemo;`: `Example` is an inner class, and thus has an implicit reference to the containing type. OK, easy to forget. * `I`: the `int x` parameter. Expected. * `Ljava/lang/String`: the `String a` parameter from the enclosing scope! This is the closure parameter. This does make sense. The problem is that it's *selective*: only variables used within `Example` become extra parameters. If the `Example` constructor is updated to remove the `System.out.println(a)` statement, then `a` is no longer used, and is no longer present as a constructor parameter. The only way I found to "reasonably" determine if a constructor parameter was a closure parameter was by checking all fields in the class with names starting with `val$`, and then comparing the types of those fields to types within the enclosing method's descriptor. I can't think of a way to avoid using `val$`. :-( Another aside: closure parameter behavior *differs* between JDK 1.8 and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it assigns the *wrong* parameter names. Consider `MyStringList`: The Java constructor declaration is: public static <T, …> void staticActionWithGenerics ( T value1, … List<?> unboundedList, …) { class MyStringList extends ArrayList<String> { public MyStringList(String a, int b) { } // … } } The JNI signature for the `MyStringList` constructor is: (Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V which is: * `String`: parameter `a` * `I`: parameter `b` * `List`: closure parameter for `unboundedList` * `Object`: closure parameter for `value1`. If we build with JDK 1.8 `javac -parameters`, the `MethodParameters` annotation states that the closure parameters are: MyStringList(String a, int b, List val$value1, Object val$unboundedList); which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`! This was fixed in JDK-11, with the `MethodParameters` annotations specifying: MyStringList(String a, int b, List val$unboundedList, Object val$value1); This means that the unit tests need to take this into consideration. Add a new `ConfiguredJdkInfo` class, which contains code similar to `tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props` to find the path to the JDK found in `make prepare`, and then determine the JDK version from that directory's `release` file. [0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 6, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `Java.Interop.Java*Array` types. This should help reduce marshaling logic & overhead, as arrays don't need to be "deep copied". `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is currently: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 8, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `Java.Interop.Java*Array` types. This should help reduce marshaling logic & overhead, as arrays don't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 8, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `Java.Interop.Java*Array` types. This should help reduce marshaling logic & overhead, as arrays don't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 10, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `Java.Interop.Java*Array` types. This should help reduce marshaling logic & overhead, as arrays don't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 10, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `Java.Interop.Java*Array` types. This should help reduce marshaling logic & overhead, as arrays don't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 11, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 16, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 16, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 16, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 17, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 17, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 18, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Nov 19, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Dec 5, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Dec 5, 2021
Context: dotnet#858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Object` * `java.lang.Throwable` * `java.lang.Class` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello ~~ Binding changes vs. Xamarin.Android ~~ Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. ~~ TODO: Marshal Methods? ~~ Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. For Desktop, JCW's shouldn't have all the methods to register. Instead, use the `jnimarshalmethod-gen`-originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with [`JavaCallable`], which in turn will require an equivalent to `Android.Runtime.RegisterAttribute(…)`. Perhaps `JniMethodSignatureAttribute(string name, string sig)`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other API Changes? ~~ We should "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public object[] GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? Why? How do we fix that to instead bind it as `JavaObjectArray<Java.Lang.Annotations.Annotation>`? Currently, it's differently *worse*; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; Fixing unit tests...
jonpryor
added a commit
that referenced
this issue
Dec 7, 2021
Context: #858 What do *I* want? To be able to use our wonderful Java binding infrastructure against *Desktop Java*, not just Android. At the same time, I don't want "Android-isms" "leaking" into such a binding. *Just* Java.Interop, no xamarin-android. "Take over" the `generator --codegen-target=JavaInterop1` format so that it *isn't* useful for Xamarin.Android, and is instead usable for non-Android usage. This is a work-in-progress, and *far* from complete. For prototype purposes, this *only* binds: * `java.lang.Class` * `java.lang.Math` * `java.lang.Number` * `java.lang.Object` * `java.lang.Throwable` The `Java.Base` binding is only for .NET 6 and above. I'm not interested in .NET Standard support at this point in time. ~~ Build Changes ~~ Additionally, this `Java.Base` binding is only for the `java.base.jmod` file provided by JDK-11. `make prepare` is updated to look for a JDK-11 installation path. ~~ Test Changes ~~ Update `samples/Hello` so that it (1) works, and (2) instantiates the `Java.Lang.Object` binding: dotnet run --project samples/Hello Update `tests/generator-Tests` so that `tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1` is now used *exclusively* for `generator --codegen-target=JavaInterop1` output; `generator --codegen-target=XAJavaInterop1` now has its own separate files in `tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1`. (This contributes to much of the commit diff.) Update `tests/generator-Tests/SupportFiles` so that types in `Android.*` are excluded when `JAVA_INTEROP1` is `#define`d, and update the unit test infrastructure so that building for `JavaInterop1` causes `JAVA_INTEROP1` to be `#define`d. This allows many of the unit tests to ensure that *some* `JavaInterop1` constructs *compile*. However, not all unit tests compile under `JavaInterop1`. The new `BaseGeneratorTest.TryJavaInterop1` property can be set to false to prevent compiling a test suite using `JavaInterop1`. This will allow us to slowly implement `JavaInterop1` support over time. The build logs will also now emit a command-line that can be used to manually compile unit test code. See e.g. `bin/TestDebug/TestOutput-generator-Tests.txt`. ~~ API Changes ~~ Update `Java.Interop.JavaObject` so that `JavaObject.DisposeUnlessReferenced()` is now `virtual`. Override `DisposeUnlessReferenced()` from the `Java*Array` types so that if the instance was created via the new `JniEnvironment.Arrays.CreateMarshal*Array()` methods, the array instance will be disposed. This is intended for marshaling array parameters: public void WithArray(int[] array) { const string __id = "withArray.[I"; var native_array = JniEnvironment.Arrays.CreateMarshalInt32Array (array); try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (native_array); _members.StaticMethods.InvokeVoidMethod (__id, __args); } finally { if (array != null) native_array.DisposeUnlessReferenced (); } } Add `Java.Interop.JavaTypeParametersAttribute(string[] typeParameters)` from Xamarin.Android. ~~ Bindings vs. Xamarin.Android ~~ Pull in `src/Java.Base/Transforms/map.csv` [from xamarin-android][0], removing the `android*` types. Instead of `[Android.Runtime.RegisterAttribute]` on types, use `[Java.Interop.JniTypeSignatureAttribute]`. Java arrays are bound as appropriate `IList<T>`, using the `Java.Interop.Java*Array` types as an intermediary. This should help reduce marshaling logic & overhead, as if the "source" array is a `Java*Array`, it doesn't need to be "deep copied". The exception is C# `params` arrays, which continue to be bound as arrays, and are marshaled via an appropriate `Java*Array` type. `java.io.InputStream` isn't bound as `System.IO.Stream`, etc. "Java.Interop-style" constructors are used (25de1f3), e.g. // This DeclaringType (ref JniObjectReference reference, JniObjectReferenceOptions options); // Not Xamarin.Android-style DeclaringType (IntPtr handle, JniHandleOwnership transfer); "Java.Interop-style" wrapper construction is used, e.g. // This var wrapper = JniEnvironment.Runtime.ValueManager.GetValue<DeclaringType>(ref h, JniObjectReferenceOptions.CopyAndDispose); // Not this var wrapper = Java.Lang.Object.GetObject<DeclaringType>(handle); ~~ TODO: Marshal Methods ~~ Marshal methods are currently skipped. Java-to-managed invocations are not currently supported. Xamarin.Android uses Java Callable Wrappers + `Runtime.register()` to specify which methods to register, via lots of reflection, etc. Proposal: For Desktop, JCW's shouldn't need to specify all the methods to register. Instead, use the `jnimarshalmethod-gen`- originated strategy of `[JniAddNativeMethodRegistrationAttribute]` within the binding, and then have it use `MarshalMethodBuilder` to generate the marshal methods. Need to update `MarshalMethodBuilder` to look for overrides in addition to methods with `[JavaCallable]`, which in turn may require an equivalent to `[Android.Runtime.RegisterAttribute(…)]`. Perhaps `[JniMethodSignatureAttribute(string name, string sig)]`? In the meantime, `Java.Base` will skip all marshal-method logic plus runtime method generation. Leave that for later. ~~ TODO: Other Binding Changes? ~~ We should eventually "unify" `java.lang.Object` and `System.Object`. Consider `java.lang.Class`: /* partial */ class Class<T> { public boolean isInstance(java.lang.Object); public java.lang.Object[] getSigners(); } If we unify `java.lang.Object` and `System.Object`, we'd have a binding of: partial class Class { public bool IsInstance (object value); public IList<object> GetSigners(); } ~~ Open Questions ~~ What's up with `java.lang.Class.getAnnotationsByType()`? During an iteration of this PR, I got: public unsafe Java.Interop.JavaObjectArray<Java.Lang.Object>? GetAnnotationsByType (Java.Lang.Class? annotationClass) { const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation;"; From `__id` we see that the Java return type is `Annotation[]`, yet we bind it as an `Object` array? This is because of [#669][1]. That said, it's currently "differently *worse*"; I don't know why, but `__id` is now: const string __id = "getAnnotationsByType.(Ljava/lang/Class;)[Ljava/lang/Object;"; i.e. the return type is an `Object` array instead of an `Annotation` array, which is wrong, as per `javap`: % javap -s java.lang.Class … public <A extends java.lang.annotation.Annotation> A getAnnotation(java.lang.Class<A>); descriptor: (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; This needs additional investigation. [0]: https://github.com/xamarin/xamarin-android/blob/99523feab02e8622a3357e9e6a025f5afc44c970/src/Mono.Android/map.csv [1]: #669
jonpryor
added a commit
that referenced
this issue
Sep 14, 2023
Fixes: #910 Context: bc5bcf4 Context: #858 Consider the Java `java.lang.Runnable` interface: package java.lang; public interface Runnable { void run (); } This is bound as: package Java.Lang; public interface IRunnable : IJavaPeerable { void Run (); } with some slight differences depending on whether we're dealing with .NET Android (`generator --codegen-target=xajavainterop1`) or `src/Java.Base` (`generator --codegen-target=javainterop1`). Now, assume a Java API + corresponding binding which returns a `Runnable` instance: package example; public class Whatever { public static Runnable createRunnable(); } You can invoke `IRunnable.Run()` on the return value: IRunnable r = Whatever.CreateRunnable(); r.Run(); but how does that work? This works via an "interface Invoker", which is a class emitted by `generator` which implements the interface and invokes the interface methods through JNI: internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable { public void Run() => … } Once Upon A Time™, the interface invoker implementation mirrored that of classes: a static `IntPtr` field held the `jmethodID` value, which would be looked up on first-use and cached for subsequent invocations: partial class IRunnableInvoker { static IntPtr id_run; public unsafe void Run() { if (id_run == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "run", "()V"); JNIEnv.CallVoidMethod (Handle, id_run, …); } } This approach works until you have interface inheritance and methods which come from different interfaces: package android.view; public /* partial */ interface ViewManager { void addView(View view, ViewGroup.LayoutParams params); } public /* partial */ interface WindowManager extends ViewManager { void removeViewImmediate(View view); } This would be bound as: namespace Android.Views; public partial interface IViewManager : IJavaPeerable { void AddView (View view, ViewGroup.LayoutParams @params); } public partial IWindowManager : IViewManager { void RemoveViewImmediate (View view); } internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Unfortunately, *invoking* `IViewManager.AddView()` through an `IWindowManagerInvoker` would crash! D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown. I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string) I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams) I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle) I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr) Interfaces are not classes, and this is one of the places that this is most apparent. Because of this crash, we had to use *instance* `jmethodID` caches: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Pro: no more crash! Con: *every different instance* of `IWindowManagerInvoker` needs to separately lookup whatever methods are required. There is *some* caching, so repeated calls to `AddView()` on the same instance will hit the cache, but if you obtain a different `IWindowManager` instance, `jmethodID` values will need to be looked up again. This was "fine", until #858 enters the picture: interface invokers were full of Android-isms -- `Android.Runtime.JNIEnv.GetMethodID()`! -- and thus ***not*** APIs that @jonpryor wished to expose within desktop Java.Base bindings. Enter `generator --lang-features=emit-legacy-interface-invokers`: when *not* specified, interface invokers will now use `JniPeerMembers` for method lookup and invocation, allowing `jmethodID` values to be cached *across* instances. In order to prevent the runtime crash, an interface may have *multiple* `JniPeerMembers` values, one per inherited interface, which is used to invoke methods from that interface. `IWindowManagerInvoker` now becomes: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static readonly JniPeerMembers _members_ViewManager = …; static readonly JniPeerMembers _members_WindowManager = …; public void AddView(View view, ViewGroup.LayoutParams @params) { const string __id = "addView.…"; _members_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } public void RemoveViewImmediate(View view) { const string __id = "removeViewImmediate.…"; _members_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } } This has two advantages: 1. More caching! 2. Desktop `Java.Base` binding can now have interface invokers. TODO: Performance? How much better is this? Update tests/invocation-overhead. Update `tests/generator-Tests` expected output. Note: to keep this patch small, only JavaInterop1 output uses the new pattern. XAJavaInterop1 output is unchanged, *even though* it will use the new output *by default*. Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore warnings such as: …/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114: 'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. Ignoring CS0114 is also done in `Mono.Android.dll` as well, so this is not a new or unique requirement. Update `Java.Interop.dll` so that `JniRuntime.JniValueManager.GetActivationConstructor()` now knows about and looks for `*Invoker` types, then uses the activation constructor from the `*Invoker` type when the source type is an abstract `class` or `interface`. Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup and invocation support. [0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114
jonpryor
added a commit
that referenced
this issue
Oct 26, 2023
#1145) Fixes: #910 Context: bc5bcf4 Context: #858 Consider the Java `java.lang.Runnable` interface: package java.lang; public interface Runnable { void run (); } This is bound as: package Java.Lang; public interface IRunnable : IJavaPeerable { void Run (); } with some slight differences depending on whether we're dealing with .NET Android (`generator --codegen-target=xajavainterop1`) or `src/Java.Base` (`generator --codegen-target=javainterop1`). Now, assume a Java API + corresponding binding which returns a `Runnable` instance: package example; public class Whatever { public static Runnable createRunnable(); } You can invoke `IRunnable.Run()` on the return value: IRunnable r = Whatever.CreateRunnable(); r.Run(); but how does that work? This works via an "interface Invoker", which is a class emitted by `generator` which implements the interface and invokes the interface methods through JNI: internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable { public void Run() => … } Once Upon A Time™, the interface invoker implementation mirrored that of classes: a static `IntPtr` field held the `jmethodID` value, which would be looked up on first-use and cached for subsequent invocations: partial class IRunnableInvoker { static IntPtr id_run; public unsafe void Run() { if (id_run == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "run", "()V"); JNIEnv.CallVoidMethod (Handle, id_run, …); } } This approach works until you have interface inheritance and methods which come from inherited interfaces: package android.view; public /* partial */ interface ViewManager { void addView(View view, ViewGroup.LayoutParams params); } public /* partial */ interface WindowManager extends ViewManager { void removeViewImmediate(View view); } This would be bound as: namespace Android.Views; public partial interface IViewManager : IJavaPeerable { void AddView (View view, ViewGroup.LayoutParams @params); } public partial IWindowManager : IViewManager { void RemoveViewImmediate (View view); } internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Unfortunately, *invoking* `IViewManager.AddView()` through an `IWindowManagerInvoker` would crash! D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown. I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string) I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams) I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle) I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr) I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr) Interfaces are not classes, and this is one of the places that this is most apparent. Because of this crash, we had to use *instance* `jmethodID` caches: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { IntPtr id_addView; public void AddView(View view, ViewGroup.LayoutParams @params) { if (id_addView == IntPtr.Zero) id_run = JNIEnv.GetMethodID (class_ref, "addView", "…"); JNIEnv.CallVoidMethod (Handle, id_addView, …); } } Pro: no more crash! Con: *every different instance* of `IWindowManagerInvoker` needs to separately lookup whatever methods are invoked. There is *some* caching, so repeated calls to `AddView()` on the same instance will hit the cache, but if you obtain a different `IWindowManager` instance, `jmethodID` values will need to be looked up again. This was "fine", until #858 enters the picture: interface invokers were full of Android-isms -- `Android.Runtime.JNIEnv.GetMethodID()`! `JNIEnv.CallVoidMethod()`! -- and thus ***not*** APIs that @jonpryor wished to expose within desktop Java.Base bindings. Enter `generator --lang-features=emit-legacy-interface-invokers`: when *not* specified, interface invokers will now use `JniPeerMembers` for method lookup and invocation, allowing `jmethodID` values to be cached *across* instances. In order to prevent the runtime crash, an interface may have *multiple* `JniPeerMembers` values, one per implemented interface, which is used to invoke methods from that interface. `IWindowManagerInvoker` now becomes: internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager { static readonly JniPeerMembers _members_android_view_ViewManager = …; static readonly JniPeerMembers _members_android_view_WindowManager = …; public void AddView(View view, ViewGroup.LayoutParams @params) { const string __id = "addView.…"; _members_android_view_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } public void RemoveViewImmediate(View view) { const string __id = "removeViewImmediate.…"; _members_android_view_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …); } } This has two advantages: 1. More caching! 2. Desktop `Java.Base` binding can now have interface invokers. Update `tests/generator-Tests` expected output. Note: to keep this patch smaller, JavaInterop1 output uses the new pattern, and only *some* XAJavaInterop1 tests use the new pattern. Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore warnings such as: …/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114: 'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. [Ignoring CS0114 is also done in `Mono.Android.dll` as well][1], so this is not a new or unique requirement. Update `Java.Interop.dll` so that `JniRuntime.JniValueManager.GetActivationConstructor()` now knows about and looks for `*Invoker` types, then uses the activation constructor from the `*Invoker` type when the source type is an abstract `class` or `interface`. Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup and invocation support. ~~ Property Setters ~~ While testing on dotnet/android#8339, we hit this error (among others, to be addressed later): src/Mono.Android/obj/Debug/net8.0/android-34/mcw/Android.Views.IWindowInsetsController.cs(304,41): error CS0103: The name 'behavior' does not exist in the current context This was caused because of code such as: public partial interface IWindowInsetsController { public unsafe int SystemBarsBehavior { get { const string __id = "getSystemBarsBehavior.()I"; try { var __rm = _members_IWindowInsetsController.InstanceMethods.InvokeAbstractInt32Method (__id, this, null); return __rm; } finally { } } set { const string __id = "setSystemBarsBehavior.(I)V"; try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (behavior); _members_IWindowInsetsController.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); } finally { } } } } This happened because when emitting the property setter, we need to update the `set*` method's parameter name to be `value` so that the normal property setter body is emitted properly. Update `InterfaceInvokerProperty.cs` so that the parameter name is set to `value`. ~~ Performance ~~ What does this do for performance? Add a new `InterfaceInvokerTiming` test fixture to `Java.Interop-PerformanceTests.dll`, which: 1. "Reimplements" the "legacy" and "JniPeerMembers" Invoker strategies 2. For each Invoker strategy: a. Invokes a Java method which returns a `java.lang.Runnable` instance b. Invokes `Runnable.run()` on the instance returned by (2.a) …100 times. c. Repeat (2.a) and (2.b) 100 times. The result is that using `JniPeerMembers` is *much* faster: % dotnet build tests/Java.Interop-PerformanceTests/*.csproj && \ dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming" … Passed InterfaceInvokers [1 s] Standard Output Messages: ## InterfaceInvokers Timing: instanceIds: 00:00:01.1095502 ## InterfaceInvokers Timing: peerMembers: 00:00:00.1400427 Using `JniPeerMembers` takes ~1/8th the time as using `jmethodID`s. TODO: something is *probably* wrong with my test -- reviews welcome! -- as when I increase the (2.b) iteration count, the `peerMembers` time is largely unchanged (~0.14s), while the `instanceIds` time increases linearly. *Something* is wrong there. I'm not sure what. (Or *nothing* is wrong, and instance `jmethodID` are just *that* bad.) [0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114 [1]: https://github.com/xamarin/xamarin-android/blob/d5c4ec09f7658428a10bbe49c8a7a3eb2f71cb86/src/Mono.Android/Mono.Android.csproj#L12C7-L12C7
Open
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 thejmod
file format. Lacking that, we can getclass-parse
to read all types in a Java module via e.g.For me, with JDK 11,
class-parse
is able to find 5609 classes and 553 interfaces.class-parse
also emits 305 warnings:class-parse
needs to actually support Java modules.Plus:
Regardless, we can emit an
api.xml
for thejava.base.jmod
module. However, to make a binding, we needgenerator
support: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
:This calls the Xamarin.Android-style
(IntPtr, JniHandleOwnership)
constructor. Java.Interop doesn't have anAndroid.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 byTestType
:https://github.com/xamarin/java.interop/blob/855ecfa3a85ce74f40a1cc47e67b70aedd697dc3/tests/Java.Interop-Tests/Java.Interop/TestType.cs#L47-L51
Next:
Java.Interop doesn't currently have a
Java.Lang.Object
type, though that would be created/implicit to bindingjava.base.jmod
. Instead, I would prefer investigating: https://github.com/xamarin/java.interop/blob/main/Documentation/Architecture.md#proposed-javainterop-architecture; that is, instead ofObject.GetObject<T>()
, useJniEnvironment.Runtime.ValueManager.GetValue<T>()
.Then there's marshal methods:
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
isAndroid.Runtime.JniHandleOwnership
; a Desktop JVM binding assembly shouldn't have any types in anAndroid.*
namespace!)The text was updated successfully, but these errors were encountered: