Skip to content

[generator] generator --lang-features=emit-legacy-interface-invokers #1145

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

Merged
merged 16 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build-tools/automation/templates/core-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ steps:
inputs:
command: test
testRunTitle: Java.Interop-Performance ($(DotNetTargetFramework) - ${{ parameters.platformName }})
arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-PerformanceTests.dll
arguments: --logger "console;verbosity=detailed" bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-PerformanceTests.dll
continueOnError: true
retryCountOnTaskFailure: 1

Expand Down
2 changes: 1 addition & 1 deletion src/Java.Base/Java.Base.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);8764</NoWarn>
<NoWarn>$(NoWarn);8764;0114</NoWarn>
</PropertyGroup>

<Import Project="..\..\TargetFrameworkDependentValues.props" />
Expand Down
5 changes: 5 additions & 0 deletions src/Java.Base/Java.Lang/ICharSequence.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
using System.Collections;

namespace Java.Lang {

partial class ICharSequenceInvoker : IEnumerable {
}

public static partial class ICharSequenceExtensions {

public static ICharSequence[]? ToCharSequenceArray (this string?[]? values)
Expand Down
12 changes: 9 additions & 3 deletions src/Java.Base/Transforms/Metadata.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<metadata>
<!-- For now, just bind java.lang.* -->
<remove-node path="//api/package[not(starts-with(@name, 'java.lang')
or starts-with(@name, 'java.io')
<!-- For now, just bind a few packages -->
<remove-node path="//api/package[
not(
starts-with(@name, 'java.lang')
or starts-with(@name, 'java.io')
or starts-with(@name, 'java.util.function')
)]" />

<!-- Type / Namespace conflicts -->
Expand Down Expand Up @@ -54,6 +57,9 @@
]/method[@name='write']"
name="explicitInterface">IDataOutput</attr>

<!-- CS0108 but for *static* members; TODO: how do we fix? -->
<remove-node path="/api/package[@name='java.util.function']/interface[@name='UnaryOperator']/method[@name='identity' and count(parameter)=0]" />

<!-- AbstractStringBuilder is package-private; fixity fix -->
<remove-node path="//api/package[@name='java.lang']/class[@name='AbstractStringBuilder']" />

Expand Down
31 changes: 25 additions & 6 deletions src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,14 +335,33 @@ static Type GetPeerType (Type type)

static ConstructorInfo? GetActivationConstructor (Type type)
{
return
(from c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
let p = c.GetParameters ()
where p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions)
select c)
.FirstOrDefault ();
if (type.IsAbstract || type.IsInterface) {
type = GetInvokerType (type) ?? type;
}
foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
var p = c.GetParameters ();
if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions))
return c;
}
return null;
}

static Type? GetInvokerType (Type type)
{
const string suffix = "Invoker";
Type[] arguments = type.GetGenericArguments ();
if (arguments.Length == 0)
return type.Assembly.GetType (type + suffix);
Type definition = type.GetGenericTypeDefinition ();
int bt = definition.FullName!.IndexOf ("`", StringComparison.Ordinal);
if (bt == -1)
throw new NotSupportedException ("Generic type doesn't follow generic type naming convention! " + type.FullName);
Type? suffixDefinition = definition.Assembly.GetType (
definition.FullName.Substring (0, bt) + suffix + definition.FullName.Substring (bt));
if (suffixDefinition == null)
return null;
return suffixDefinition.MakeGenericType (arguments);
}

public object? CreateValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType = null)
{
Expand Down
36 changes: 36 additions & 0 deletions tests/Java.Base-Tests/Java.Base/JavaToManagedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ public void InterfaceMethod ()
Assert.IsTrue (invoked);
r.Dispose ();
}

[Test]
public void InterfaceInvokerMethod ()
{
int value = 0;
using var c = new MyIntConsumer (v => value = v);
using var r = JavaInvoker.CreateRunnable (c);
r?.Run ();
Assert.AreEqual (0, value);
r?.Run ();
Assert.AreEqual (1, value);
}
}

class JavaInvoker : JavaObject {
Expand All @@ -31,6 +43,14 @@ public static unsafe void Run (Java.Lang.IRunnable r)
args [0] = new JniArgumentValue (r);
_members.StaticMethods.InvokeVoidMethod ("run.(Ljava/lang/Runnable;)V", args);
}

public static unsafe Java.Lang.IRunnable? CreateRunnable (Java.Util.Function.IIntConsumer c)
{
JniArgumentValue* args = stackalloc JniArgumentValue [1];
args [0] = new JniArgumentValue (c);
var _rm = _members.StaticMethods.InvokeObjectMethod ("createRunnable.(Ljava/util/function/IntConsumer;)Ljava/lang/Runnable;", args);
return Java.Interop.JniEnvironment.Runtime.ValueManager.GetValue<Java.Lang.IRunnable> (ref _rm, JniObjectReferenceOptions.CopyAndDispose);
}
}

[JniTypeSignature ("example/MyRunnable")]
Expand All @@ -48,4 +68,20 @@ public void Run ()
action ();
}
}

[JniTypeSignature ("example/MyIntConsumer")]
class MyIntConsumer : Java.Lang.Object, Java.Util.Function.IIntConsumer {

Action<int> action;

public MyIntConsumer (Action<int> action)
{
this.action = action;
}

public void Accept (int value)
{
action (value);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package com.microsoft.java_base_tests;

public class Invoker {
import java.util.function.IntConsumer;

public final class Invoker {

public static void run(Runnable r) {
r.run();
}

public static Runnable createRunnable(final IntConsumer consumer) {
return new Runnable() {
int value;
public void run() {
consumer.accept(value++);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ public unsafe JniObjectReference Timing_ToString_JniPeerMembers ()
const string id = toString_name + "." + toString_sig;
return _members.InstanceMethods.InvokeVirtualObjectMethod (id, this, null);
}

public static unsafe JniObjectReference CreateRunnable ()
{
return _members.StaticMethods.InvokeObjectMethod ("CreateRunnable.()Ljava/lang/Runnable;", null);
}
}

[JniTypeSignature (JniTypeName)]
Expand Down
90 changes: 90 additions & 0 deletions tests/Java.Interop-PerformanceTests/Java.Interop/TimingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,41 @@ public void GenericMarshalingOverhead_Int32ArrayArrayArray ()
total.Stop ();
Console.WriteLine ("## {0} Timing: {1}", nameof (GenericMarshalingOverhead_Int32ArrayArrayArray), total.Elapsed);
}

}

[TestFixture]
public class InterfaceInvokerTiming : Java.InteropTests.JavaVMFixture {

[Test]
public void InterfaceInvokers ()
{
const int JavaTiming_CreateRunnable_Invocations = 100;
const int Runnable_Run_Invocations = 100;

var instanceIds = Stopwatch.StartNew ();
for (int i = 0; i < JavaTiming_CreateRunnable_Invocations; ++i) {
var c = JavaTiming.CreateRunnable ();
IMyRunnable r = new LegacyRunnableInvoker (ref c, JniObjectReferenceOptions.CopyAndDispose);
for (int j = 0; j < Runnable_Run_Invocations; ++j) {
r.Run ();
}
r.Dispose ();
}
instanceIds.Stop ();
var peerMembers = Stopwatch.StartNew ();
for (int i = 0; i < JavaTiming_CreateRunnable_Invocations; ++i) {
var c = JavaTiming.CreateRunnable ();
IMyRunnable r = new JniPeerMembersRunnableInvoker (ref c, JniObjectReferenceOptions.CopyAndDispose);
for (int j = 0; j < Runnable_Run_Invocations; ++j) {
r.Run ();
}
r.Dispose ();
}
peerMembers.Stop ();
Console.WriteLine ("## {0} Timing: instanceIds: {1}", nameof (InterfaceInvokers), instanceIds.Elapsed);
Console.WriteLine ("## {0} Timing: peerMembers: {1}", nameof (InterfaceInvokers), peerMembers.Elapsed);
}
}

class ManagedTiming {
Expand Down Expand Up @@ -893,5 +928,60 @@ public override object GetValue ()
return null;
}
}

interface IMyRunnable : IJavaPeerable {
void Run();
}

class LegacyRunnableInvoker : JavaObject, IMyRunnable {
static readonly JniPeerMembers _members = new JniPeerMembers ("java/lang/Runnable", typeof (LegacyRunnableInvoker));
JniObjectReference class_ref;

public LegacyRunnableInvoker (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base (ref reference, options)
{
var r = JniEnvironment.Types.GetObjectClass (PeerReference);
class_ref = r.NewGlobalRef ();
JniObjectReference.Dispose (ref r);
}

public override JniPeerMembers JniPeerMembers {
get { return _members; }
}

protected override void Dispose (bool disposing)
{
JniObjectReference.Dispose (ref class_ref);
base.Dispose (disposing);
}

JniMethodInfo id_run;

public unsafe void Run ()
{
if (id_run == null) {
id_run = JniEnvironment.InstanceMethods.GetMethodID (class_ref, "run", "()V");
}
JniEnvironment.InstanceMethods.CallObjectMethod (PeerReference, id_run);
}
}

class JniPeerMembersRunnableInvoker : JavaObject, IMyRunnable {
public JniPeerMembersRunnableInvoker (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base (ref reference, options)
{
}

static readonly JniPeerMembers _members_IRunnable = new JniPeerMembers ("java/lang/Runnable", typeof (JniPeerMembersRunnableInvoker));

public unsafe void Run ()
{
const string __id = "run.()V";
try {
_members_IRunnable.InstanceMethods.InvokeAbstractVoidMethod (__id, this, null);
} finally {
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,14 @@ public static void StaticVoidMethod2IArgs (int obj1, int obj2)
public static void StaticVoidMethod3IArgs (int obj1, int obj2, int obj3)
{
}

public static Runnable CreateRunnable ()
{
return new Runnable () {
public void run ()
{
}
};
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ protected void Run (CodeGenerationTarget target, string outputPath, string apiDe
AdditionalSourceDirectories.Clear ();

Options.CodeGenerationTarget = target;
Options.EmitLegacyInterfaceInvokers = false;
Options.ApiDescriptionFile = FullPath (apiDescriptionFile);
Options.ManagedCallableWrapperSourceOutputDirectory = FullPath (outputPath);

Expand Down
9 changes: 8 additions & 1 deletion tests/generator-Tests/Integration-Tests/Interfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ namespace generatortests
[TestFixture]
public class Interfaces : BaseGeneratorTest
{
protected override bool TryJavaInterop1 => false;
public Interfaces ()
{
// warning CS0108: 'IDeque.Add(Object)' hides inherited member 'IQueue.Add(Object)'. Use the new keyword if hiding was intended.
// warning CS0108: 'IQueue.Add(Object)' hides inherited member 'ICollection.Add(Object)'. Use the new keyword if hiding was intended.
AllowWarnings = true;
}

protected override bool TryJavaInterop1 => true;

[Test]
public void Generated_OK ()
Expand Down
12 changes: 6 additions & 6 deletions tests/generator-Tests/SupportFiles/Java_Lang_ICharSequence.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
#if !JAVA_INTEROP1


using System;
using Android.Runtime;
using Java.Interop;

namespace Java.Lang {

public partial interface ICharSequence : IJavaObject
public partial interface ICharSequence : IJavaPeerable
#if !JAVA_INTEROP1
, Android.Runtime.IJavaObject
#endif // !JAVA_INTEROP1
{
char CharAt (int index);
int Length ();
Java.Lang.ICharSequence SubSequenceFormatted (int start, int end);
string ToString ();
}
}

#endif // !JAVA_INTEROP1
13 changes: 6 additions & 7 deletions tests/generator-Tests/SupportFiles/Java_Lang_String.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#if !JAVA_INTEROP1

using System;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Java.Lang {

public sealed partial class String : global::Java.Lang.Object, Java.Lang.ICharSequence
public sealed partial class String : global::Java.Lang.Object, Java.Lang.ICharSequence, IEnumerable
{
public String (string value)
public unsafe String (string value)
#if JAVA_INTEROP1
: base (ref *InvalidJniObjectReference, Java.Interop.JniObjectReferenceOptions.None)
#endif // JAVA_INTEROP1
{
}

Expand Down Expand Up @@ -43,5 +44,3 @@ IEnumerator IEnumerable.GetEnumerator ()
}
}
}

#endif // !JAVA_INTEROP1
Loading