Skip to content

Commit 1adb796

Browse files
authored
[generator] generator --lang-features=emit-legacy-interface-invokers (#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
1 parent 6bd7ae4 commit 1adb796

File tree

78 files changed

+1835
-825
lines changed

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

build-tools/automation/templates/core-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ steps:
132132
inputs:
133133
command: test
134134
testRunTitle: Java.Interop-Performance ($(DotNetTargetFramework) - ${{ parameters.platformName }})
135-
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-PerformanceTests.dll
135+
arguments: --logger "console;verbosity=detailed" bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-PerformanceTests.dll
136136
continueOnError: true
137137
retryCountOnTaskFailure: 1
138138

src/Java.Base/Java.Base.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
55
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
66
<Nullable>enable</Nullable>
7-
<NoWarn>$(NoWarn);8764</NoWarn>
7+
<NoWarn>$(NoWarn);8764;0114</NoWarn>
88
</PropertyGroup>
99

1010
<Import Project="..\..\TargetFrameworkDependentValues.props" />

src/Java.Base/Java.Lang/ICharSequence.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
using System.Collections;
2+
13
namespace Java.Lang {
24

5+
partial class ICharSequenceInvoker : IEnumerable {
6+
}
7+
38
public static partial class ICharSequenceExtensions {
49

510
public static ICharSequence[]? ToCharSequenceArray (this string?[]? values)

src/Java.Base/Transforms/Metadata.xml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<metadata>
2-
<!-- For now, just bind java.lang.* -->
3-
<remove-node path="//api/package[not(starts-with(@name, 'java.lang')
4-
or starts-with(@name, 'java.io')
2+
<!-- For now, just bind a few packages -->
3+
<remove-node path="//api/package[
4+
not(
5+
starts-with(@name, 'java.lang')
6+
or starts-with(@name, 'java.io')
7+
or starts-with(@name, 'java.util.function')
58
)]" />
69

710
<!-- Type / Namespace conflicts -->
@@ -54,6 +57,9 @@
5457
]/method[@name='write']"
5558
name="explicitInterface">IDataOutput</attr>
5659

60+
<!-- CS0108 but for *static* members; TODO: how do we fix? -->
61+
<remove-node path="/api/package[@name='java.util.function']/interface[@name='UnaryOperator']/method[@name='identity' and count(parameter)=0]" />
62+
5763
<!-- AbstractStringBuilder is package-private; fixity fix -->
5864
<remove-node path="//api/package[@name='java.lang']/class[@name='AbstractStringBuilder']" />
5965

src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -335,14 +335,33 @@ static Type GetPeerType (Type type)
335335

336336
static ConstructorInfo? GetActivationConstructor (Type type)
337337
{
338-
return
339-
(from c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
340-
let p = c.GetParameters ()
341-
where p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions)
342-
select c)
343-
.FirstOrDefault ();
338+
if (type.IsAbstract || type.IsInterface) {
339+
type = GetInvokerType (type) ?? type;
340+
}
341+
foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
342+
var p = c.GetParameters ();
343+
if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions))
344+
return c;
345+
}
346+
return null;
344347
}
345348

349+
static Type? GetInvokerType (Type type)
350+
{
351+
const string suffix = "Invoker";
352+
Type[] arguments = type.GetGenericArguments ();
353+
if (arguments.Length == 0)
354+
return type.Assembly.GetType (type + suffix);
355+
Type definition = type.GetGenericTypeDefinition ();
356+
int bt = definition.FullName!.IndexOf ("`", StringComparison.Ordinal);
357+
if (bt == -1)
358+
throw new NotSupportedException ("Generic type doesn't follow generic type naming convention! " + type.FullName);
359+
Type? suffixDefinition = definition.Assembly.GetType (
360+
definition.FullName.Substring (0, bt) + suffix + definition.FullName.Substring (bt));
361+
if (suffixDefinition == null)
362+
return null;
363+
return suffixDefinition.MakeGenericType (arguments);
364+
}
346365

347366
public object? CreateValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType = null)
348367
{

tests/Java.Base-Tests/Java.Base/JavaToManagedTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ public void InterfaceMethod ()
1818
Assert.IsTrue (invoked);
1919
r.Dispose ();
2020
}
21+
22+
[Test]
23+
public void InterfaceInvokerMethod ()
24+
{
25+
int value = 0;
26+
using var c = new MyIntConsumer (v => value = v);
27+
using var r = JavaInvoker.CreateRunnable (c);
28+
r?.Run ();
29+
Assert.AreEqual (0, value);
30+
r?.Run ();
31+
Assert.AreEqual (1, value);
32+
}
2133
}
2234

2335
class JavaInvoker : JavaObject {
@@ -31,6 +43,14 @@ public static unsafe void Run (Java.Lang.IRunnable r)
3143
args [0] = new JniArgumentValue (r);
3244
_members.StaticMethods.InvokeVoidMethod ("run.(Ljava/lang/Runnable;)V", args);
3345
}
46+
47+
public static unsafe Java.Lang.IRunnable? CreateRunnable (Java.Util.Function.IIntConsumer c)
48+
{
49+
JniArgumentValue* args = stackalloc JniArgumentValue [1];
50+
args [0] = new JniArgumentValue (c);
51+
var _rm = _members.StaticMethods.InvokeObjectMethod ("createRunnable.(Ljava/util/function/IntConsumer;)Ljava/lang/Runnable;", args);
52+
return Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<Java.Lang.IRunnable> (ref _rm, JniObjectReferenceOptions.CopyAndDispose);
53+
}
3454
}
3555

3656
[JniTypeSignature ("example/MyRunnable")]
@@ -48,4 +68,20 @@ public void Run ()
4868
action ();
4969
}
5070
}
71+
72+
[JniTypeSignature ("example/MyIntConsumer")]
73+
class MyIntConsumer : Java.Lang.Object, Java.Util.Function.IIntConsumer {
74+
75+
Action<int> action;
76+
77+
public MyIntConsumer (Action<int> action)
78+
{
79+
this.action = action;
80+
}
81+
82+
public void Accept (int value)
83+
{
84+
action (value);
85+
}
86+
}
5187
}
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
package com.microsoft.java_base_tests;
22

3-
public class Invoker {
3+
import java.util.function.IntConsumer;
4+
5+
public final class Invoker {
46

57
public static void run(Runnable r) {
68
r.run();
79
}
10+
11+
public static Runnable createRunnable(final IntConsumer consumer) {
12+
return new Runnable() {
13+
int value;
14+
public void run() {
15+
consumer.accept(value++);
16+
}
17+
};
18+
}
819
}

tests/Java.Interop-PerformanceTests/Java.Interop/JavaTiming.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ public unsafe JniObjectReference Timing_ToString_JniPeerMembers ()
276276
const string id = toString_name + "." + toString_sig;
277277
return _members.InstanceMethods.InvokeVirtualObjectMethod (id, this, null);
278278
}
279+
280+
public static unsafe JniObjectReference CreateRunnable ()
281+
{
282+
return _members.StaticMethods.InvokeObjectMethod ("CreateRunnable.()Ljava/lang/Runnable;", null);
283+
}
279284
}
280285

281286
[JniTypeSignature (JniTypeName)]

tests/Java.Interop-PerformanceTests/Java.Interop/TimingTests.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,41 @@ public void GenericMarshalingOverhead_Int32ArrayArrayArray ()
776776
total.Stop ();
777777
Console.WriteLine ("## {0} Timing: {1}", nameof (GenericMarshalingOverhead_Int32ArrayArrayArray), total.Elapsed);
778778
}
779+
780+
}
781+
782+
[TestFixture]
783+
public class InterfaceInvokerTiming : Java.InteropTests.JavaVMFixture {
784+
785+
[Test]
786+
public void InterfaceInvokers ()
787+
{
788+
const int JavaTiming_CreateRunnable_Invocations = 100;
789+
const int Runnable_Run_Invocations = 100;
790+
791+
var instanceIds = Stopwatch.StartNew ();
792+
for (int i = 0; i < JavaTiming_CreateRunnable_Invocations; ++i) {
793+
var c = JavaTiming.CreateRunnable ();
794+
IMyRunnable r = new LegacyRunnableInvoker (ref c, JniObjectReferenceOptions.CopyAndDispose);
795+
for (int j = 0; j < Runnable_Run_Invocations; ++j) {
796+
r.Run ();
797+
}
798+
r.Dispose ();
799+
}
800+
instanceIds.Stop ();
801+
var peerMembers = Stopwatch.StartNew ();
802+
for (int i = 0; i < JavaTiming_CreateRunnable_Invocations; ++i) {
803+
var c = JavaTiming.CreateRunnable ();
804+
IMyRunnable r = new JniPeerMembersRunnableInvoker (ref c, JniObjectReferenceOptions.CopyAndDispose);
805+
for (int j = 0; j < Runnable_Run_Invocations; ++j) {
806+
r.Run ();
807+
}
808+
r.Dispose ();
809+
}
810+
peerMembers.Stop ();
811+
Console.WriteLine ("## {0} Timing: instanceIds: {1}", nameof (InterfaceInvokers), instanceIds.Elapsed);
812+
Console.WriteLine ("## {0} Timing: peerMembers: {1}", nameof (InterfaceInvokers), peerMembers.Elapsed);
813+
}
779814
}
780815

781816
class ManagedTiming {
@@ -893,5 +928,60 @@ public override object GetValue ()
893928
return null;
894929
}
895930
}
931+
932+
interface IMyRunnable : IJavaPeerable {
933+
void Run();
934+
}
935+
936+
class LegacyRunnableInvoker : JavaObject, IMyRunnable {
937+
static readonly JniPeerMembers _members = new JniPeerMembers ("java/lang/Runnable", typeof (LegacyRunnableInvoker));
938+
JniObjectReference class_ref;
939+
940+
public LegacyRunnableInvoker (ref JniObjectReference reference, JniObjectReferenceOptions options)
941+
: base (ref reference, options)
942+
{
943+
var r = JniEnvironment.Types.GetObjectClass (PeerReference);
944+
class_ref = r.NewGlobalRef ();
945+
JniObjectReference.Dispose (ref r);
946+
}
947+
948+
public override JniPeerMembers JniPeerMembers {
949+
get { return _members; }
950+
}
951+
952+
protected override void Dispose (bool disposing)
953+
{
954+
JniObjectReference.Dispose (ref class_ref);
955+
base.Dispose (disposing);
956+
}
957+
958+
JniMethodInfo id_run;
959+
960+
public unsafe void Run ()
961+
{
962+
if (id_run == null) {
963+
id_run = JniEnvironment.InstanceMethods.GetMethodID (class_ref, "run", "()V");
964+
}
965+
JniEnvironment.InstanceMethods.CallObjectMethod (PeerReference, id_run);
966+
}
967+
}
968+
969+
class JniPeerMembersRunnableInvoker : JavaObject, IMyRunnable {
970+
public JniPeerMembersRunnableInvoker (ref JniObjectReference reference, JniObjectReferenceOptions options)
971+
: base (ref reference, options)
972+
{
973+
}
974+
975+
static readonly JniPeerMembers _members_IRunnable = new JniPeerMembers ("java/lang/Runnable", typeof (JniPeerMembersRunnableInvoker));
976+
977+
public unsafe void Run ()
978+
{
979+
const string __id = "run.()V";
980+
try {
981+
_members_IRunnable.InstanceMethods.InvokeAbstractVoidMethod (__id, this, null);
982+
} finally {
983+
}
984+
}
985+
}
896986
}
897987

tests/Java.Interop-PerformanceTests/java/com/xamarin/interop/performance/JavaTiming.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,14 @@ public static void StaticVoidMethod2IArgs (int obj1, int obj2)
7777
public static void StaticVoidMethod3IArgs (int obj1, int obj2, int obj3)
7878
{
7979
}
80+
81+
public static Runnable CreateRunnable ()
82+
{
83+
return new Runnable () {
84+
public void run ()
85+
{
86+
}
87+
};
88+
}
8089
}
8190

tests/generator-Tests/Integration-Tests/BaseGeneratorTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ protected void Run (CodeGenerationTarget target, string outputPath, string apiDe
143143
AdditionalSourceDirectories.Clear ();
144144

145145
Options.CodeGenerationTarget = target;
146+
Options.EmitLegacyInterfaceInvokers = false;
146147
Options.ApiDescriptionFile = FullPath (apiDescriptionFile);
147148
Options.ManagedCallableWrapperSourceOutputDirectory = FullPath (outputPath);
148149

tests/generator-Tests/Integration-Tests/Interfaces.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ namespace generatortests
66
[TestFixture]
77
public class Interfaces : BaseGeneratorTest
88
{
9-
protected override bool TryJavaInterop1 => false;
9+
public Interfaces ()
10+
{
11+
// warning CS0108: 'IDeque.Add(Object)' hides inherited member 'IQueue.Add(Object)'. Use the new keyword if hiding was intended.
12+
// warning CS0108: 'IQueue.Add(Object)' hides inherited member 'ICollection.Add(Object)'. Use the new keyword if hiding was intended.
13+
AllowWarnings = true;
14+
}
15+
16+
protected override bool TryJavaInterop1 => true;
1017

1118
[Test]
1219
public void Generated_OK ()
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
#if !JAVA_INTEROP1
2-
1+

32
using System;
4-
using Android.Runtime;
3+
using Java.Interop;
54

65
namespace Java.Lang {
76

8-
public partial interface ICharSequence : IJavaObject
7+
public partial interface ICharSequence : IJavaPeerable
8+
#if !JAVA_INTEROP1
9+
, Android.Runtime.IJavaObject
10+
#endif // !JAVA_INTEROP1
911
{
1012
char CharAt (int index);
1113
int Length ();
1214
Java.Lang.ICharSequence SubSequenceFormatted (int start, int end);
1315
string ToString ();
1416
}
1517
}
16-
17-
#endif // !JAVA_INTEROP1

tests/generator-Tests/SupportFiles/Java_Lang_String.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
#if !JAVA_INTEROP1
2-
3-
using System;
1+
using System;
42
using System.Collections;
53
using System.Collections.Generic;
64

75
namespace Java.Lang {
86

9-
public sealed partial class String : global::Java.Lang.Object, Java.Lang.ICharSequence
7+
public sealed partial class String : global::Java.Lang.Object, Java.Lang.ICharSequence, IEnumerable
108
{
11-
public String (string value)
9+
public unsafe String (string value)
10+
#if JAVA_INTEROP1
11+
: base (ref *InvalidJniObjectReference, Java.Interop.JniObjectReferenceOptions.None)
12+
#endif // JAVA_INTEROP1
1213
{
1314
}
1415

@@ -43,5 +44,3 @@ IEnumerator IEnumerable.GetEnumerator ()
4344
}
4445
}
4546
}
46-
47-
#endif // !JAVA_INTEROP1

0 commit comments

Comments
 (0)