You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[jcw-gen] Do not register static methods on an interface. (#745)
Context: 855b7e9
Context: dotnet/android#5251
Context: dotnet/android#5262
When an Java interface contains a `static` method in API-30+, we use
C# 8.0's Default Interface Methods support to place corresponding
`static` methods in the C# interface binding:
// Java
public /* partial */ interface Comparator<T> {}
int compare(T o1, T o2);
static <T>
Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {…}
// …
}
// C# binding
public partial interface IComparator {
[Register ("compare", "(Ljava/lang/Object;Ljava/lang/Object;)I", "GetCompare_Ljava_lang_Object_Ljava_lang_Object_Handler:Java.Util.IComparatorInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")]
int Compare (Java.Lang.Object? o1, Java.Lang.Object? o2);
[Register ("comparingDouble", "(Ljava/util/function/ToDoubleFunction;)Ljava/util/Comparator;", "", ApiSince = 24)]
[global::Java.Interop.JavaTypeParameters (new string [] {"T"})]
public static unsafe Java.Util.IComparator? ComparingDouble (Java.Util.Functions.IToDoubleFunction? keyExtractor) {…}
}
This works until you (1) attempt to implement the C# interface:
public partial class CompareSizesByArea : Java.Lang.Object, Java.Util.IComparator
{
public int Compare (Java.Lang.Object lhs, Java.Lang.Object rhs) {…}
}
and then (2) *instantiate* that class:
var comparator = new CompareSizesByArea();
The result is an `ArgumentException`:
System.ArgumentException: Couldn't bind to method ''.
at System.Delegate.GetCandidateMethod (System.Type type, System.Type target, System.String method, System.Reflection.BindingFlags bflags, System.Boolean ignoreCase, System.Boolean throwOnBindFailure)
at System.Delegate.CreateDelegate (System.Type type, System.Type target, System.String method, System.Boolean ignoreCase, System.Boolean throwOnBindFailure)
at System.Delegate.CreateDelegate (System.Type type, System.Type target, System.String method)
at Android.Runtime.AndroidTypeManager.RegisterNativeMembers (Java.Interop.JniType nativeClass, System.Type type, System.String methods)
at Android.Runtime.JNIEnv.RegisterJniNatives (System.IntPtr typeName_ptr, System.Int32 typeName_len, System.IntPtr jniClass, System.IntPtr methods_ptr, System.Int32 methods_len)
/* missing stack entries */
at (wrapper managed-to-native) Java.Interop.NativeMethods.java_interop_jnienv_alloc_object(intptr,intptr&,intptr)
at Java.Interop.JniEnvironment+Object.AllocObject (Java.Interop.JniObjectReference type)
at Java.Interop.JniType.AllocObject ()
at Java.Interop.JniPeerMembers+JniInstanceMethods.StartCreateInstance (System.String constructorSignature, System.Type declaringType, Java.Interop.JniArgumentValue* parameters)
at Java.Lang.Object..ctor ()
at App16.MainActivity+CompareSizesByArea..ctor ()
Why this fails is because of the intermediate Java Callable Wrapper,
which emits a method override for every method in the interface,
*including `static` methods*:
// Java callable wrapper
public /* partial */ class CompareSizesByArea extends java.lang.Object, implements java.util.Comparator {
public static final String __md_methods;
static {
__md_methods =
"n_compare:(Ljava/lang/Object;Ljava/lang/Object;)I:GetCompare_Ljava_lang_Object_Ljava_lang_Object_Handler:Java.Util.IComparatorInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" +
… +
"n_comparingDouble:(Ljava/util/function/ToDoubleFunction;)Ljava/util/Comparator;:\n" +
…
mono.android.Runtime.register ("CompareSizesByArea, …", CompareSizesByArea.class, __md_methods);
}
public int compare (java.lang.Object p0, java.lang.Object p1)
{
return n_compare (p0, p1);
}
private native int n_compare (java.lang.Object p0, java.lang.Object p1);
public java.util.Comparator comparingDouble (java.util.function.ToDoubleFunction p0)
{
return n_comparingDouble (p0);
}
private native java.util.Comparator n_comparingDouble (java.util.function.ToDoubleFunction p0);
}
(Note: The `/* missing stack entries */` line (above) is from the
Java-side `CompareSizesByArea` static constructor execution, which
calls `Runtime.register()`, which in turn calls
`JNIEnv.RegisterJniNatives()`.)
The `Runtime.register()` invocation eventually hits
[`AndroidTypeManager.RegisterNativeMembers()`][0], which "splits"
`__md_methods` into lines, and each line into `:`-separated values.
One of those values is expected to be a "connector method", obtainable
via Reflection. In the case of `n_comparingDouble`, the connector
method *is not specified*: note that the line ends with `:`, unlike
the entry for `n_compare`, which lists `GetCompare_…`.
This "missing" connector method is the cause of the
`ArgumentException`.
Fix the problem by *skipping* `static` methods found from interfaces
when emitting Java Callable Wrappers. This ensures that `__md_methods`
won't contain entries with missing "connector" methods.
An integration test is part of dotnet/android#5262, as we're
not currently able to use C#8 Default Interface Methods in a `net472`
target framework for unit testing within the xamarin/Java.Interop repo.
[0]: https://github.com/xamarin/xamarin-android/blob/15b37758e9d65c82aebe1a327747b285f38ce791/src/Mono.Android/Android.Runtime/AndroidRuntime.cs#L394-L413
Copy file name to clipboardExpand all lines: src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs
Copy file name to clipboardExpand all lines: tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGeneratorTests.cs
Copy file name to clipboardExpand all lines: tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/SupportDeclarations.cs
0 commit comments