Commit 1adb796
authored
[generator]
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-L12C7generator --lang-features=emit-legacy-interface-invokers (#1145)1 parent 6bd7ae4 commit 1adb796
File tree
78 files changed
+1835
-825
lines changed- build-tools/automation/templates
- src
- Java.Base
- Java.Lang
- Transforms
- Java.Interop/Java.Interop
- tests
- Java.Base-Tests
- Java.Base
- java/com/microsoft/java_base_tests
- Java.Interop-PerformanceTests
- Java.Interop
- java/com/xamarin/interop/performance
- generator-Tests
- Integration-Tests
- SupportFiles
- Unit-Tests
- CodeGeneratorExpectedResults/JavaInterop1
- expected.ji
- AccessModifiers
- InterfaceMethodsConflict
- TestInterface
- expected.xaji
- AccessModifiers
- Adapters
- Core_Jar2Xml
- GenericArguments
- InterfaceMethodsConflict
- NestedTypes
- TestInterface
- java.lang.Enum
- tools/generator
- Java.Interop.Tools.Generator.ObjectModel
- Symbols
- SourceWriters
- Extensions
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
78 files changed
+1835
-825
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
| 135 | + | |
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
1 | 3 | | |
2 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
3 | 8 | | |
4 | 9 | | |
5 | 10 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
3 | | - | |
4 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
5 | 8 | | |
6 | 9 | | |
7 | 10 | | |
| |||
54 | 57 | | |
55 | 58 | | |
56 | 59 | | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| |||
Lines changed: 25 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
335 | 335 | | |
336 | 336 | | |
337 | 337 | | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | | - | |
343 | | - | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
344 | 347 | | |
345 | 348 | | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
346 | 365 | | |
347 | 366 | | |
348 | 367 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
21 | 33 | | |
22 | 34 | | |
23 | 35 | | |
| |||
31 | 43 | | |
32 | 44 | | |
33 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
34 | 54 | | |
35 | 55 | | |
36 | 56 | | |
| |||
48 | 68 | | |
49 | 69 | | |
50 | 70 | | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
51 | 87 | | |
Lines changed: 12 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
4 | 6 | | |
5 | 7 | | |
6 | 8 | | |
7 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
8 | 19 | | |
Lines changed: 5 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
276 | 276 | | |
277 | 277 | | |
278 | 278 | | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
279 | 284 | | |
280 | 285 | | |
281 | 286 | | |
| |||
Lines changed: 90 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
776 | 776 | | |
777 | 777 | | |
778 | 778 | | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
779 | 814 | | |
780 | 815 | | |
781 | 816 | | |
| |||
893 | 928 | | |
894 | 929 | | |
895 | 930 | | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
896 | 986 | | |
897 | 987 | | |
Lines changed: 9 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
80 | 89 | | |
81 | 90 | | |
0 commit comments