Skip to content

[Java.Interop] Add JniMemberInfoLookup #1208

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions src/Java.Interop/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -19,6 +19,10 @@

// See: 045b8af7, 6a42bb89, f60906cf, e10f7cb0, etc.
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniEnvironment.Exceptions")]
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniEnvironment.InstanceFields")]
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniEnvironment.InstanceMethods")]
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniEnvironment.StaticFields")]
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniEnvironment.StaticMethods")]
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniPeerMembers.JniStaticMethods")]
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniRuntime.JniMarshalMemberBuilder")]
[assembly: SuppressMessage ("Design", "CA1034:Nested types should not be visible", Justification = "Deliberate choice to 'hide' these types from code completion for `Java.Interop.`.", Scope = "type", Target = "~T:Java.Interop.JniPeerMembers.JniStaticFields")]
2 changes: 1 addition & 1 deletion src/Java.Interop/Java.Interop.csproj
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@
<OutputPath>$(ToolOutputFullPath)</OutputPath>
<DocumentationFile>$(ToolOutputFullPath)Java.Interop.xml</DocumentationFile>
<JNIEnvGenPath>$(BuildToolOutputFullPath)</JNIEnvGenPath>
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">9.0</LangVersion>
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">12.0</LangVersion>
<LangVersion Condition=" '$(LangVersion)' == '' ">8.0</LangVersion>
<Version>$(JICoreLibVersion)</Version>
<Standalone Condition=" '$(Standalone)' == '' ">true</Standalone>
37 changes: 37 additions & 0 deletions src/Java.Interop/Java.Interop/JniEnvironment.InstanceFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;

namespace Java.Interop;

partial class JniEnvironment {
partial class InstanceFields {
public static unsafe JniFieldInfo GetFieldID (JniObjectReference type, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
if (!type.IsValid)
throw new ArgumentException ("Handle must be valid.", "type");

IntPtr env = JniEnvironment.EnvironmentPointer;
IntPtr field;
IntPtr thrown;
fixed (void* name_ptr = &MemoryMarshal.GetReference (name))
fixed (void* signature_ptr = &MemoryMarshal.GetReference (signature)) {
field = JniNativeMethods.GetFieldID (env, type.Handle, (IntPtr) name_ptr, (IntPtr) signature_ptr);
thrown = JniNativeMethods.ExceptionOccurred (env);
}

Exception? __e = JniEnvironment.GetExceptionForLastThrowable (thrown);
if (__e != null)
ExceptionDispatchInfo.Capture (__e).Throw ();

if (field == IntPtr.Zero)
throw new InvalidOperationException ("Should not be reached; `GetFieldID` should have thrown!");

#if DEBUG
return new JniFieldInfo (name.ToString (), signature.ToString (), field, isStatic: false);
#else // DEBUG
return new JniFieldInfo (null!, null!, field, isStatic: false);
#endif // DEBUG
}
}
}
37 changes: 37 additions & 0 deletions src/Java.Interop/Java.Interop/JniEnvironment.InstanceMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;

namespace Java.Interop;

partial class JniEnvironment {
partial class InstanceMethods {
public static unsafe JniMethodInfo GetMethodID (JniObjectReference type, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
if (!type.IsValid)
throw new ArgumentException ("Handle must be valid.", "type");

IntPtr env = JniEnvironment.EnvironmentPointer;
IntPtr method;
IntPtr thrown;
fixed (void* name_ptr = &MemoryMarshal.GetReference (name))
fixed (void* signature_ptr = &MemoryMarshal.GetReference (signature)) {
method = JniNativeMethods.GetMethodID (env, type.Handle, (IntPtr) name_ptr, (IntPtr) signature_ptr);
thrown = JniNativeMethods.ExceptionOccurred (env);
}

Exception? __e = JniEnvironment.GetExceptionForLastThrowable (thrown);
if (__e != null)
ExceptionDispatchInfo.Capture (__e).Throw ();

if (method == IntPtr.Zero)
throw new InvalidOperationException ("Should not be reached; `GetMethodID` should have thrown!");

#if DEBUG
return new JniMethodInfo (name.ToString (), signature.ToString (), method, isStatic: false);
#else // DEBUG
return new JniMethodInfo (null!, null!, method, isStatic: false);
#endif // DEBUG
}
}
}
38 changes: 38 additions & 0 deletions src/Java.Interop/Java.Interop/JniEnvironment.StaticFields.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;

namespace Java.Interop;

partial class JniEnvironment {
partial class StaticFields {

public static unsafe JniFieldInfo GetStaticFieldID (JniObjectReference type, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
if (!type.IsValid)
throw new ArgumentException ("Handle must be valid.", "type");

IntPtr env = JniEnvironment.EnvironmentPointer;
IntPtr field;
IntPtr thrown;
fixed (void* name_ptr = &MemoryMarshal.GetReference (name))
fixed (void* signature_ptr = &MemoryMarshal.GetReference (signature)) {
field = JniNativeMethods.GetStaticFieldID (env, type.Handle, (IntPtr) name_ptr, (IntPtr) signature_ptr);
thrown = JniNativeMethods.ExceptionOccurred (env);
}

Exception? __e = JniEnvironment.GetExceptionForLastThrowable (thrown);
if (__e != null)
ExceptionDispatchInfo.Capture (__e).Throw ();

if (field == IntPtr.Zero)
throw new InvalidOperationException ("Should not be reached; `GetFieldID` should have thrown!");

#if DEBUG
return new JniFieldInfo (name.ToString (), signature.ToString (), field, isStatic: false);
#else // DEBUG
return new JniFieldInfo (null!, null!, field, isStatic: false);
#endif // DEBUG
}
}
}
82 changes: 82 additions & 0 deletions src/Java.Interop/Java.Interop/JniEnvironment.StaticMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;

namespace Java.Interop;

partial class JniEnvironment {
partial class StaticMethods {

public static unsafe JniMethodInfo GetStaticMethodID (JniObjectReference type, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
if (!type.IsValid)
throw new ArgumentException ("Handle must be valid.", "type");

IntPtr env = JniEnvironment.EnvironmentPointer;
IntPtr method;
IntPtr thrown;
fixed (void* name_ptr = &MemoryMarshal.GetReference (name))
fixed (void* signature_ptr = &MemoryMarshal.GetReference (signature)) {
method = JniNativeMethods.GetStaticMethodID (env, type.Handle, (IntPtr) name_ptr, (IntPtr) signature_ptr);
thrown = JniNativeMethods.ExceptionOccurred (env);
}

Exception? __e = JniEnvironment.GetExceptionForLastThrowable (thrown);
if (__e != null)
ExceptionDispatchInfo.Capture (__e).Throw ();

if (method == IntPtr.Zero)
throw new InvalidOperationException ("Should not be reached; `GetStaticMethodID` should have thrown!");

#if DEBUG
return new JniMethodInfo (name.ToString (), signature.ToString (), method, isStatic: true);
#else // DEBUG
return new JniMethodInfo (null!, null!, method, isStatic: true);
#endif // DEBUG
}

internal static unsafe bool TryGetStaticMethod (
JniObjectReference type,
ReadOnlySpan<byte> name,
ReadOnlySpan<byte> signature,
[NotNullWhen(true)]
out JniMethodInfo? method)
{
method = null;

if (!type.IsValid)
throw new ArgumentException ("Handle must be valid.", "type");

IntPtr env = JniEnvironment.EnvironmentPointer;
IntPtr id;
IntPtr thrown;
fixed (void* name_ptr = &MemoryMarshal.GetReference (name))
fixed (void* signature_ptr = &MemoryMarshal.GetReference (signature)) {
id = JniNativeMethods.GetStaticMethodID (env, type.Handle, (IntPtr) name_ptr, (IntPtr) signature_ptr);
thrown = JniNativeMethods.ExceptionOccurred (env);
}

if (thrown != IntPtr.Zero) {
JniNativeMethods.ExceptionClear (env);
JniEnvironment.References.RawDeleteLocalRef (env, thrown);
thrown = IntPtr.Zero;
return false;
}

Debug.Assert (id != IntPtr.Zero);
if (id == IntPtr.Zero) {
return false;
}

#if DEBUG
method = new JniMethodInfo (name.ToString (), signature.ToString (), id, isStatic: true);
#else // DEBUG
method = new JniMethodInfo (null!, null!, id, isStatic: true);
#endif // DEBUG

return true;
}
}
}
22 changes: 22 additions & 0 deletions src/Java.Interop/Java.Interop/JniMemberInfoLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace Java.Interop;

public ref struct JniMemberInfoLookup {
public string EncodedMember {get; private set;}
public ReadOnlySpan<byte> MemberName {get; private set;}
public ReadOnlySpan<byte> MemberSignature {get; private set;}

[Obsolete ("Use the JniMemberInfoLookup(string, ReadOnlySpan<byte>, ReadOnlySpan<byte>) constructor.", error: true)]
public JniMemberInfoLookup ()
{
throw new NotSupportedException ();
}

public JniMemberInfoLookup (string encodedMember, ReadOnlySpan<byte> memberName, ReadOnlySpan<byte> memberSignature)
{
EncodedMember = encodedMember;
MemberName = memberName;
MemberSignature = memberSignature;
}
}
299 changes: 298 additions & 1 deletion src/Java.Interop/Java.Interop/JniPeerMembers.JniFields.cs

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions src/Java.Interop/Java.Interop/JniPeerMembers.JniFields.tt
Original file line number Diff line number Diff line change
@@ -49,6 +49,27 @@ namespace Java.Interop {
JniEnvironment.InstanceFields.Set<#= info.JniCallType #>Field (self.PeerReference, f, value);
GC.KeepAlive (self);
}

public <#= info.ReturnType #> Get<#= info.ManagedType #>Value (
JniMemberInfoLookup member,
IJavaPeerable self)
{
JniPeerMembers.AssertSelf (self);

var f = GetFieldInfo (member);
var r = JniEnvironment.InstanceFields.Get<#= info.JniCallType #>Field (self.PeerReference, f);
GC.KeepAlive (self);
return r;
}

public void SetValue (JniMemberInfoLookup member, IJavaPeerable self, <#= info.ParameterType #> value)
{
JniPeerMembers.AssertSelf (self);

var f = GetFieldInfo (member);
JniEnvironment.InstanceFields.Set<#= info.JniCallType #>Field (self.PeerReference, f, value);
GC.KeepAlive (self);
}
<#
}
#>
@@ -70,6 +91,18 @@ namespace Java.Interop {
var f = GetFieldInfo (encodedMember);
JniEnvironment.StaticFields.SetStatic<#= info.JniCallType #>Field (Members.JniPeerType.PeerReference, f, value);
}

public <#= info.ReturnType #> Get<#= info.ManagedType #>Value (JniMemberInfoLookup member)
{
var f = GetFieldInfo (member);
return JniEnvironment.StaticFields.GetStatic<#= info.JniCallType #>Field (Members.JniPeerType.PeerReference, f);
}

public void SetValue (JniMemberInfoLookup member, <#= info.ParameterType #> value)
{
var f = GetFieldInfo (member);
JniEnvironment.StaticFields.SetStatic<#= info.JniCallType #>Field (Members.JniPeerType.PeerReference, f, value);
}
<#
}
#>
11 changes: 11 additions & 0 deletions src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceFields.cs
Original file line number Diff line number Diff line change
@@ -34,6 +34,17 @@ public JniFieldInfo GetFieldInfo (string encodedMember)
return f;
}
}

public JniFieldInfo GetFieldInfo (JniMemberInfoLookup member)
{
lock (InstanceFields) {
if (!InstanceFields.TryGetValue (member.EncodedMember, out var f)) {
f = Members.JniPeerType.GetInstanceField (member.MemberName, member.MemberSignature);
InstanceFields.Add (member.EncodedMember, f);
}
return f;
}
}
}}
}

42 changes: 42 additions & 0 deletions src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs
Original file line number Diff line number Diff line change
@@ -157,6 +157,48 @@ JniMethodInfo GetMethodInfo (string method, string signature)
return JniPeerType.GetInstanceMethod (method, signature);
}


public JniMethodInfo GetMethodInfo (JniMemberInfoLookup member)
{
lock (InstanceMethods) {
if (InstanceMethods.TryGetValue (member.EncodedMember, out var m)) {
return m;
}
}
var info = GetMethodInfo (member.MemberName, member.MemberSignature);
lock (InstanceMethods) {
if (InstanceMethods.TryGetValue (member.EncodedMember, out var m)) {
return m;
}
InstanceMethods.Add (member.EncodedMember, info);
}
return info;
}

JniMethodInfo GetMethodInfo (ReadOnlySpan<byte> method, ReadOnlySpan<byte> signature)
{
var m = (JniMethodInfo?) null;
var newMethod = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (Members.JniPeerTypeName, method, signature);
if (newMethod.HasValue) {
var typeName = newMethod.Value.TargetJniType ?? Members.JniPeerTypeName;
var methodName = newMethod.Value.TargetJniMethodName ?? method.ToString ();
var methodSig = newMethod.Value.TargetJniMethodSignature ?? signature.ToString ();

using var t = new JniType (typeName);
if (newMethod.Value.TargetJniMethodInstanceToStatic &&
t.TryGetStaticMethod (methodName, methodSig, out m)) {
m.ParameterCount = newMethod.Value.TargetJniMethodParameterCount;
m.StaticRedirect = new JniType (typeName);
return m;
}
if (t.TryGetInstanceMethod (methodName, methodSig, out m)) {
return m;
}
Console.Error.WriteLine ($"warning: For declared method `{Members.JniPeerTypeName}.{method.ToString ()}.{signature.ToString ()}`, could not find requested method `{typeName}.{methodName}.{methodSig}`!");
}
return JniPeerType.GetInstanceMethod (method, signature);
}

public unsafe JniObjectReference StartCreateInstance (string constructorSignature, Type declaringType, JniArgumentValue* parameters)
{
if (constructorSignature == null)

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Java.Interop {

@@ -80,6 +81,25 @@ namespace Java.Interop {
}
}

public unsafe <#= returnType.ReturnType #> InvokeAbstract<#= returnType.ManagedType #>Method (JniMemberInfoLookup member, IJavaPeerable self, ReadOnlySpan<JniArgumentValue> parameters)
{
JniPeerMembers.AssertSelf (self);

try {
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (m, self, params_ptr<#= byRefParamDecl #>)) {
<#= returnByRefParam #>;
}
<#= setByRefParam #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, params_ptr);
<#= returnByRefParam #>;
}
}
finally {
GC.KeepAlive (self);
}
}

public unsafe <#= returnType.ReturnType #> InvokeVirtual<#= returnType.ManagedType #>Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters)
{
JniPeerMembers.AssertSelf (self);
@@ -109,6 +129,37 @@ namespace Java.Interop {
}
}

public unsafe <#= returnType.ReturnType #> InvokeVirtual<#= returnType.ManagedType #>Method (JniMemberInfoLookup member, IJavaPeerable self, ReadOnlySpan<JniArgumentValue> parameters)
{
JniPeerMembers.AssertSelf (self);

try {
var declaringType = DeclaringType;
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
if (Members.UsesVirtualDispatch (self, declaringType)) {
var m = GetMethodInfo (member);
if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (m, self, params_ptr<#= byRefParamDecl #>)) {
<#= returnByRefParam #>;
}
<#= setByRefParam #>JniEnvironment.InstanceMethods.Call<#= returnType.JniCallType #>Method (self.PeerReference, m, params_ptr);
<#= returnByRefParam #>;
}
var j = Members.GetPeerMembers (self);
var n = j.InstanceMethods.GetMethodInfo (member);
do {
if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (n, self, params_ptr<#= byRefParamDecl #>)) {
<#= returnByRefParam #>;
}
<#= setByRefParam #>JniEnvironment.InstanceMethods.CallNonvirtual<#= returnType.JniCallType #>Method (self.PeerReference, j.JniPeerType.PeerReference, n, params_ptr);
<#= returnByRefParam #>;
} while (false);
}
}
finally {
GC.KeepAlive (self);
}
}

public unsafe <#= returnType.ReturnType #> InvokeNonvirtual<#= returnType.ManagedType #>Method (string encodedMember, IJavaPeerable self, JniArgumentValue* parameters)
{
JniPeerMembers.AssertSelf (self);
@@ -125,6 +176,25 @@ namespace Java.Interop {
GC.KeepAlive (self);
}
}

public unsafe <#= returnType.ReturnType #> InvokeNonvirtual<#= returnType.ManagedType #>Method (JniMemberInfoLookup member, IJavaPeerable self, ReadOnlySpan<JniArgumentValue> parameters)
{
JniPeerMembers.AssertSelf (self);

var m = GetMethodInfo (member);
try {
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
if (TryInvoke<#= returnType.ManagedType #>StaticRedirect (m, self, params_ptr<#= byRefParamDecl #>)) {
<#= returnByRefParam #>;
}
<#= setByRefParam #>JniEnvironment.InstanceMethods.CallNonvirtual<#= returnType.JniCallType #>Method (self.PeerReference, JniPeerType.PeerReference, m, params_ptr);
<#= returnByRefParam #>;
}
}
finally {
GC.KeepAlive (self);
}
}
<#
}
#>
11 changes: 11 additions & 0 deletions src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticFields.cs
Original file line number Diff line number Diff line change
@@ -30,6 +30,17 @@ public JniFieldInfo GetFieldInfo (string encodedMember)
}
}

public JniFieldInfo GetFieldInfo (JniMemberInfoLookup member)
{
lock (StaticFields) {
if (!StaticFields.TryGetValue (member.EncodedMember, out var f)) {
f = Members.JniPeerType.GetInstanceField (member.MemberName, member.MemberSignature);
StaticFields.Add (member.EncodedMember, f);
}
return f;
}
}

internal void Dispose ()
{
StaticFields.Clear ();
146 changes: 146 additions & 0 deletions src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Java.Interop
{
@@ -66,6 +67,46 @@ JniMethodInfo GetMethodInfo (string method, string signature)
return Members.JniPeerType.GetStaticMethod (method, signature);
}

public JniMethodInfo GetMethodInfo (JniMemberInfoLookup member)
{
lock (StaticMethods) {
if (StaticMethods.TryGetValue (member.EncodedMember, out var m)) {
return m;
}
}
var info = GetMethodInfo (member.MemberName, member.MemberSignature);
lock (StaticMethods) {
if (StaticMethods.TryGetValue (member.EncodedMember, out var m)) {
return m;
}
StaticMethods.Add (member.EncodedMember, info);
}
return info;
}

JniMethodInfo GetMethodInfo (ReadOnlySpan<byte> method, ReadOnlySpan<byte> signature)
{
var m = (JniMethodInfo?) null;
var newMethod = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (Members.JniPeerTypeName, method, signature);
if (newMethod.HasValue) {
using var t = new JniType (newMethod.Value.TargetJniType ?? Members.JniPeerTypeName);
if (t.TryGetStaticMethod (
newMethod.Value.TargetJniMethodName ?? method.ToString (),
newMethod.Value.TargetJniMethodSignature ?? signature.ToString (),
out m)) {
return m;
}
}
if (Members.JniPeerType.TryGetStaticMethod (method, signature, out m)) {
return m;
}
m = FindInFallbackTypes (method, signature);
if (m != null) {
return m;
}
return Members.JniPeerType.GetStaticMethod (method, signature);
}

#pragma warning disable CA1801
JniType GetMethodDeclaringType (JniMethodInfo method)
{
@@ -105,65 +146,170 @@ JniType GetMethodDeclaringType (JniMethodInfo method)
}
#endif // NET

JniMethodInfo? FindInFallbackTypes (ReadOnlySpan<byte> method, ReadOnlySpan<byte> signature)
{
var fallbackTypes = JniEnvironment.Runtime.TypeManager.GetStaticMethodFallbackTypes (Members.JniPeerTypeName);
if (fallbackTypes == null) {
return null;
}
foreach (var ft in fallbackTypes) {
JniType? t = null;
try {
if (!JniType.TryParse (ft, out t)) {
continue;
}
if (t.TryGetStaticMethod (method, signature, out var m)) {
m.StaticRedirect = t;
t = null;
return m;
}
}
finally {
t?.Dispose ();
}
}
return null;
}

public unsafe void InvokeVoidMethod (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
JniEnvironment.StaticMethods.CallStaticVoidMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe void InvokeVoidMethod (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
JniEnvironment.StaticMethods.CallStaticVoidMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe bool InvokeBooleanMethod (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticBooleanMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe bool InvokeBooleanMethod (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticBooleanMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe sbyte InvokeSByteMethod (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticByteMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe sbyte InvokeSByteMethod (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticByteMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe char InvokeCharMethod (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticCharMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe char InvokeCharMethod (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticCharMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe short InvokeInt16Method (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticShortMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe short InvokeInt16Method (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticShortMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe int InvokeInt32Method (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticIntMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe int InvokeInt32Method (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticIntMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe long InvokeInt64Method (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticLongMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe long InvokeInt64Method (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticLongMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe float InvokeSingleMethod (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticFloatMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe float InvokeSingleMethod (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticFloatMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe double InvokeDoubleMethod (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticDoubleMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe double InvokeDoubleMethod (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticDoubleMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}

public unsafe JniObjectReference InvokeObjectMethod (string encodedMember, JniArgumentValue* parameters)
{
var m = GetMethodInfo (encodedMember);
return JniEnvironment.StaticMethods.CallStaticObjectMethod (GetMethodDeclaringType (m).PeerReference, m, parameters);
}

public unsafe JniObjectReference InvokeObjectMethod (JniMemberInfoLookup member, ReadOnlySpan<JniArgumentValue> parameters)
{
var m = GetMethodInfo (member);
fixed (JniArgumentValue* params_ptr = &MemoryMarshal.GetReference (parameters)) {
return JniEnvironment.StaticMethods.CallStaticObjectMethod (GetMethodDeclaringType (m).PeerReference, m, params_ptr);
}
}
}}
}

16 changes: 16 additions & 0 deletions src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs
Original file line number Diff line number Diff line change
@@ -423,6 +423,22 @@ IEnumerable<Type> CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe

protected virtual ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSimpleReference, string jniMethodName, string jniMethodSignature) => null;

public ReplacementMethodInfo? GetReplacementMethodInfo (string jniSimpleReference, ReadOnlySpan<byte> jniMethodName, ReadOnlySpan<byte> jniMethodSignature)
{
AssertValid ();
AssertSimpleReference (jniSimpleReference, nameof (jniSimpleReference));
if (jniMethodName.IsEmpty) {
throw new ArgumentNullException (nameof (jniMethodName));
}
if (jniMethodSignature.IsEmpty) {
throw new ArgumentNullException (nameof (jniMethodSignature));
}

return GetReplacementMethodInfoCore (jniSimpleReference, jniMethodName, jniMethodSignature);
}

protected virtual ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSimpleReference, ReadOnlySpan<byte> jniMethodName, ReadOnlySpan<byte> jniMethodSignature) => null;

public virtual void RegisterNativeMembers (
JniType nativeClass,
[DynamicallyAccessedMembers (MethodsAndPrivateNested)]
88 changes: 88 additions & 0 deletions src/Java.Interop/Java.Interop/JniType.cs
Original file line number Diff line number Diff line change
@@ -210,6 +210,13 @@ public JniFieldInfo GetInstanceField (string name, string signature)
return JniEnvironment.InstanceFields.GetFieldID (PeerReference, name, signature);
}

public JniFieldInfo GetInstanceField (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

return JniEnvironment.InstanceFields.GetFieldID (PeerReference, name, signature);
}

public JniFieldInfo GetCachedInstanceField ([NotNull] ref JniFieldInfo? cachedField, string name, string signature)
{
AssertValid ();
@@ -223,13 +230,34 @@ public JniFieldInfo GetCachedInstanceField ([NotNull] ref JniFieldInfo? cachedFi
return cachedField;
}

public JniFieldInfo GetCachedInstanceField ([NotNull] ref JniFieldInfo? cachedField, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

if (cachedField != null && cachedField.IsValid)
return cachedField;
var m = GetInstanceField (name, signature);
if (Interlocked.CompareExchange (ref cachedField, m, null) != null) {
// No cleanup required; let the GC collect the unused instance
}
return cachedField;
}

public JniFieldInfo GetStaticField (string name, string signature)
{
AssertValid ();

return JniEnvironment.StaticFields.GetStaticFieldID (PeerReference, name, signature);
}


public JniFieldInfo GetStaticField (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

return JniEnvironment.StaticFields.GetStaticFieldID (PeerReference, name, signature);
}

public JniFieldInfo GetCachedStaticField ([NotNull] ref JniFieldInfo? cachedField, string name, string signature)
{
AssertValid ();
@@ -243,13 +271,33 @@ public JniFieldInfo GetCachedStaticField ([NotNull] ref JniFieldInfo? cachedFiel
return cachedField;
}

public JniFieldInfo GetCachedStaticField ([NotNull] ref JniFieldInfo? cachedField, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

if (cachedField != null && cachedField.IsValid)
return cachedField;
var m = GetStaticField (name, signature);
if (Interlocked.CompareExchange (ref cachedField, m, null) != null) {
// No cleanup required; let the GC collect the unused instance
}
return cachedField;
}

public JniMethodInfo GetInstanceMethod (string name, string signature)
{
AssertValid ();

return JniEnvironment.InstanceMethods.GetMethodID (PeerReference, name, signature);
}

public JniMethodInfo GetInstanceMethod (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

return JniEnvironment.InstanceMethods.GetMethodID (PeerReference, name, signature);
}

#if NET
internal bool TryGetInstanceMethod (string name, string signature, [NotNullWhen(true)] out JniMethodInfo? method)
{
@@ -308,13 +356,33 @@ public JniMethodInfo GetCachedInstanceMethod ([NotNull] ref JniMethodInfo? cache
return cachedMethod;
}

public JniMethodInfo GetCachedInstanceMethod ([NotNull] ref JniMethodInfo? cachedMethod, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

if (cachedMethod != null && cachedMethod.IsValid)
return cachedMethod;
var m = GetInstanceMethod (name, signature);
if (Interlocked.CompareExchange (ref cachedMethod, m, null) != null) {
// No cleanup required; let the GC collect the unused instance
}
return cachedMethod;
}

public JniMethodInfo GetStaticMethod (string name, string signature)
{
AssertValid ();

return JniEnvironment.StaticMethods.GetStaticMethodID (PeerReference, name, signature);
}


public JniMethodInfo GetStaticMethod (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

return JniEnvironment.StaticMethods.GetStaticMethodID (PeerReference, name, signature);
}
#if NET
internal bool TryGetStaticMethod (string name, string signature, [NotNullWhen(true)] out JniMethodInfo? method)
{
@@ -360,6 +428,13 @@ IntPtr RawGetStaticMethodID (IntPtr env, string name, string signature, out IntP
}
#endif // NET

internal bool TryGetStaticMethod (ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature, [NotNullWhen(true)] out JniMethodInfo? method)
{
AssertValid ();

return JniEnvironment.StaticMethods.TryGetStaticMethod (PeerReference, name, signature, out method);
}

public JniMethodInfo GetCachedStaticMethod ([NotNull] ref JniMethodInfo? cachedMethod, string name, string signature)
{
AssertValid ();
@@ -372,5 +447,18 @@ public JniMethodInfo GetCachedStaticMethod ([NotNull] ref JniMethodInfo? cachedM
}
return cachedMethod;
}

public JniMethodInfo GetCachedStaticMethod ([NotNull] ref JniMethodInfo? cachedMethod, ReadOnlySpan<byte> name, ReadOnlySpan<byte> signature)
{
AssertValid ();

if (cachedMethod != null && cachedMethod.IsValid)
return cachedMethod;
var m = GetStaticMethod (name, signature);
if (Interlocked.CompareExchange (ref cachedMethod, m, null) != null) {
// No cleanup required; let the GC collect the unused instance
}
return cachedMethod;
}
}
}
100 changes: 100 additions & 0 deletions src/Java.Interop/PublicAPI.Unshipped.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
BeforeTargets="BeforeBuild"
Inputs="@(JavaPerformanceTestJar)"
Outputs="$(OutputPath)performance-test.jar">
<MakeDir Directories="$(IntermediateOutputPath)pt-classes" />
<MakeDir Directories="$(IntermediateOutputPath)pt-classes;$(OutputPath)" />
<Exec Command="&quot;$(JavaCPath)&quot; $(_JavacSourceOptions) -d &quot;$(IntermediateOutputPath)pt-classes&quot; @(JavaPerformanceTestJar->'%(Identity)', ' ')" />
<Exec Command="&quot;$(JarPath)&quot; cf &quot;$(OutputPath)performance-test.jar&quot; -C &quot;$(IntermediateOutputPath)pt-classes&quot; ." />
</Target>
24 changes: 24 additions & 0 deletions tests/Java.Interop-PerformanceTests/Java.Interop/JavaTiming.cs
Original file line number Diff line number Diff line change
@@ -128,6 +128,19 @@ public virtual unsafe int Timing_VirtualIntMethod_Marshal1Args (int value)
return _members.InstanceMethods.InvokeVirtualInt32Method ("VirtualIntMethod1Args.(I)I", this, args);
}

public virtual unsafe int Timing_Lookup_VirtualIntMethod_Marshal1Args (int value)
{
var member = new JniMemberInfoLookup (
"VirtualIntMethod1Args.(I)I",
"VirtualIntMethod1Args"u8,
"(I)I"u8
);
var args = stackalloc JniArgumentValue [1];
args [0] = new JniArgumentValue (value);

return _members.InstanceMethods.InvokeVirtualInt32Method (member, this, new ReadOnlySpan<JniArgumentValue> (args, 1));
}

public virtual int Timing_VirtualIntMethod_GenericMarshal1Args (int value)
{
return _members.InstanceMethods.InvokeGenericVirtualInt32Method ("VirtualIntMethod1Args.(I)I", this, value);
@@ -277,6 +290,17 @@ public unsafe JniObjectReference Timing_ToString_JniPeerMembers ()
return _members.InstanceMethods.InvokeVirtualObjectMethod (id, this, null);
}

public JniObjectReference Timing_ToString_JniPeerMembers_Lookup ()
{
var member = new JniMemberInfoLookup (
toString_name + "." + toString_sig,
"toString"u8,
"()Ljava/lang/String;"u8
);
ReadOnlySpan<JniArgumentValue> args = null;
return _members.InstanceMethods.InvokeVirtualObjectMethod (member, this, args);
}

public static unsafe JniObjectReference CreateRunnable ()
{
return _members.StaticMethods.InvokeObjectMethod ("CreateRunnable.()Ljava/lang/Runnable;", null);
45 changes: 45 additions & 0 deletions tests/Java.Interop-PerformanceTests/Java.Interop/TimingTests.cs
Original file line number Diff line number Diff line change
@@ -357,6 +357,12 @@ public void MethodLookupTiming ()
}
tp.Stop ();

var tl = Stopwatch.StartNew ();
for (int i = 0; i < count; ++i) {
var s = o.Timing_ToString_JniPeerMembers_Lookup ();
JniObjectReference.Dispose (ref s);
}
tl.Stop ();

var vtt = Stopwatch.StartNew ();
for (int i = 0; i < count; ++i) {
@@ -370,16 +376,24 @@ public void MethodLookupTiming ()
}
vti.Stop ();

var vtl = Stopwatch.StartNew ();
for (int i = 0; i < count; ++i) {
o.Timing_Lookup_VirtualIntMethod_Marshal1Args (i);
}
vtl.Stop ();


Console.WriteLine ("Method Lookup + Invoke Timing:");
Console.WriteLine ("\t Traditional: {0}", tt.Elapsed);
Console.WriteLine ("\t No caching: {0}", ta.Elapsed);
Console.WriteLine ("\t Dict w/ lock: {0}", td.Elapsed);
Console.WriteLine ("\tConcurrentDict: {0}", tc.Elapsed);
Console.WriteLine ("\tJniPeerMembers: {0}", tp.Elapsed);
Console.WriteLine ("\t JPM+Lookup: {0}", tl.Elapsed);
Console.WriteLine ();
Console.WriteLine ("\t (I)I virtual+traditional: {0}", vtt.Elapsed);
Console.WriteLine ("\t (I)I virtual+JniPeerMembers: {0}", vti.Elapsed);
Console.WriteLine ("\t (I)I virtual+JPM+Lookup: {0}", vtl.Elapsed);
}
using (var o = new DerivedJavaTiming ()) {
var ntt = Stopwatch.StartNew ();
@@ -402,6 +416,37 @@ public void MethodLookupTiming ()
}
}

[TestFixture]
class JniFieldLookupTiming : Java.InteropTests.JavaVMFixture {

[Test]
public void FieldLookupTiming ()
{
const string JniType = "com/xamarin/interop/performance/JavaTiming";
const string EncodedMember = "instanceIntField.I";
const int count = 10000;

var strLookupTime = Stopwatch.StartNew ();
for (int i = 0; i < count; ++i) {
var p = new JniPeerMembers (JniType, typeof (JavaTiming));
var f = p.InstanceFields.GetFieldInfo (EncodedMember);
}
strLookupTime.Stop ();

var encLookupTime = Stopwatch.StartNew ();
for (int i = 0; i < count; ++i) {
var p = new JniPeerMembers (JniType, typeof (JavaTiming));
var lookup = new JniMemberInfoLookup (EncodedMember, "instanceIntField"u8, "I"u8);
var f = p.InstanceFields.GetFieldInfo (lookup);
}
encLookupTime.Stop ();

Console.WriteLine ($"# {nameof (FieldLookupTiming)} Timing: looking up JavaTiming.instanceIntField {count} times");
Console.WriteLine ($"# .InstanceMethods.GetFieldInfo(string): {strLookupTime.Elapsed}");
Console.WriteLine ($"# .InstanceMethods.GetFieldInfo(JniMemberInfoLookup): {encLookupTime.Elapsed}");
}
}

[TestFixture]
class JavaArrayTiming : Java.InteropTests.JavaVMFixture {

Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

public class JavaTiming {

public int instanceIntField;

public static void StaticVoidMethod ()
{
}
16 changes: 8 additions & 8 deletions tests/Java.Interop-Tests/Java.Interop/JniTypeTest.cs
Original file line number Diff line number Diff line change
@@ -48,10 +48,10 @@ public unsafe void Dispose_Exceptions ()
Assert.Throws<ObjectDisposedException> (() => t.AllocObject ());
Assert.Throws<ObjectDisposedException> (() => t.NewObject (null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetConstructor (null));
Assert.Throws<ObjectDisposedException> (() => t.GetInstanceField (null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetInstanceMethod (null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetStaticField (null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetStaticMethod (null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetInstanceField (null, (string) null));
Assert.Throws<ObjectDisposedException> (() => t.GetInstanceMethod (null, (string) null));
Assert.Throws<ObjectDisposedException> (() => t.GetStaticField (null, (string) null));
Assert.Throws<ObjectDisposedException> (() => t.GetStaticMethod (null, (string) null));
Assert.Throws<ObjectDisposedException> (() => t.GetSuperclass ());
Assert.Throws<ObjectDisposedException> (() => t.IsAssignableFrom (null));
Assert.Throws<ObjectDisposedException> (() => t.IsInstanceOfType (new JniObjectReference ()));
@@ -60,14 +60,14 @@ public unsafe void Dispose_Exceptions ()
Assert.Throws<ObjectDisposedException> (() => t.UnregisterNativeMethods ());

JniFieldInfo jif = null;
Assert.Throws<ObjectDisposedException> (() => t.GetCachedInstanceField (ref jif, null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetCachedInstanceField (ref jif, (string) null, (string) null));
JniMethodInfo jim = null;
Assert.Throws<ObjectDisposedException> (() => t.GetCachedConstructor (ref jim, null));
Assert.Throws<ObjectDisposedException> (() => t.GetCachedInstanceMethod (ref jim, null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetCachedInstanceMethod (ref jim, (string) null, (string) null));
JniFieldInfo jsf = null;
Assert.Throws<ObjectDisposedException> (() => t.GetCachedStaticField (ref jsf, null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetCachedStaticField (ref jsf, (string) null, (string) null));
JniMethodInfo jsm = null;
Assert.Throws<ObjectDisposedException> (() => t.GetCachedStaticMethod (ref jsm, null, null));
Assert.Throws<ObjectDisposedException> (() => t.GetCachedStaticMethod (ref jsm, (string) null, (string) null));
}

[Test]