Skip to content

Commit 1f27ab5

Browse files
authored
[Java.Interop] Type & Member Remapping Support (#936)
Context: #867 There are two scenarios for which a "generalized" type & member remapping solution would be useful: 1. Desugaring 2. Microsoft Intune MAM Note: this new support is only present when targeting .NET 6+, and will not (currently) be present in Classic Xamarin.Android. ~~ Desugaring ~~ Context: dotnet/android#4574 (comment) Context: dotnet/android#6142 (comment) The [Android Runtime][0] is responsible for running the Dalvik bytecode within e.g. `classes.dex`. The Android runtime and Dalvik bytecode formats have apparently changed over time in order to support newer Java language features, such as support for static interface methods: package org.webrtc; public /* partial */ interface EGLBase { public static EGLBase create() { /* … */} } Support for static interface methods was added in Java 8 and in Android v8.0 (API-26). If you want to use static interface methods on an Android device running API-25 or earlier, you must [desugar][1] the Java Bytecode. The result of [desugaring][2] the above `org.webrtc.EGLBase` type is that the static methods are moved to a *new* type, with a `$-CC` suffix, "as if" the Java code were instead: package org.webrtc; public /* partial */ interface EGLBase { // … } public final /* partial */ class EGLBase$-CC { public static EGLBase create { /* … */ } } While this works for pure Java code, this *does not work* with Xamarin.Android, because our bindings currently expect (require) that the types & members our binding process detects don't "move": // C# Binding for EGLBase namespace Org.Webrtc { public partial interface IEGLBase { private static readonly JniPeerMembers _members = new JniPeerMembers ("org/webrtc/EGLBase", typeof (IEGLBase)); public static IEGLBase Create() { const string __id = "create.()Lorg/webrtc/EglBase;" var __rm = _members.StaticMethods.InvokeObjectMethod (__id, null); return Java.Lang.Object.GetObject<IEGLBase>(__rm.Handle, JniHandleOwnership.TransferLocalRef); } } } The `new JniPeerMembers(…)` invocation will use `JNIEnv::FindClass()` to lookup the `org/webrtc/EGLBase` type, and `IEGLBase.Create()` will use `JNIEnv::GetStaticMethodID()` and `JNIEnv::CallStaticObjectMethod()` to lookup and invoke the `EGLBase.create()` method. However, when desugaring is used, *there is no* `EGLBase.create()` method. Consequently, in a "desugared" environment, `Java.Lang.NoSuchMethodError` will be thrown when `IEGLBase.Create()` is invoked, as `EGLBase.create()` doesn't exist; it "should" instead be looking for `EGLBase$-CC.create()`! Introduce partial support for this scenario by adding the new method: namespace Java.Interop { public partial class JniRuntime { public partial class JniTypeManager { public IReadOnlyList<string>? GetStaticMethodFallbackTypes (string jniSimpleReference) => GetStaticMethodFallbackTypesCore (jniSimpleReference); protected virtual IReadOnlyList<string>? GetStaticMethodFallbackTypesCore (string jniSimpleReference) => null; } } } `JniPeerMembers.JniStaticMethods.GetMethodInfo()` will attempt to lookup the static method, "as normal". If the method cannot be found, then `JniRuntime.JniTypeManager.GetStaticMethodFallbackTypes()` will be called, and we'll attempt to resolve the static method on each type returned by `GetStaticMethodFallbackTypes()`. If `GetStaticMethodFallbackTypes()` returns `null` or no fallback type provides the method, then a `NoSuchMethodError` will be thrown. TODO: Update xamarin-android to override `GetStaticMethodFallbackTypes()`, to return `$"{jniSimpleReference}$-CC"`. ~~ Microsoft Intune MAM ~~ Context: https://www.nuget.org/packages/Microsoft.Intune.MAM.Remapper.Tasks/ Context: https://docs.microsoft.com/en-us/mem/intune/developer/app-sdk-xamarin#remapper The [Microsoft Intune App SDK Xamarin Bindings][3] is in a similar-yet-different position: it involves Java & Dalvik Bytecode manipulation for various security purposes, and in order to make that work reasonably from Xamarin.Android, they *also* rewrite Xamarin.Android IL so that it's consistent with the manipulated Dalvik bytecode. See `readme.md` and `content/MonoAndroid10/remapping-config.json` within the [`Microsoft.Intune.MAM.Remapper.Tasks NuGet package`][4] for some additional details/tidbits such as: > This task operates on assemblies to replace the base type classes > which Microsoft Intune requires to implement its SDK (for example, > Intune requires using a `MAMActivity` in place of `Activity` and > methods such as `OnMAMCreate` instead of `OnCreate`). "ClassRewrites": [ { "Class": { "From": "android.app.Activity", "To": "com.microsoft.intune.mam.client.app.MAMActivity" }, "Methods": [ { "MakeStatic": false, "OriginalName": "onCreate", "NewName": "onMAMCreate", "OriginalParams": [ "android.os.Bundle" ] } ] }, { "Class": { "From": "android.content.pm.PackageManager", "To": "com.microsoft.intune.mam.client.content.pm.MAMPackageManagement" }, "Methods": [ { "MakeStatic": true, "OriginalName": "checkPermission", } ] } ] "GlobalMethodCalls": [ { "Class": { "From": "android.app.PendingIntent", "To": "com.microsoft.intune.mam.client.app.MAMPendingIntent" }, "Methods": [ { "MakeStatic": false, "OriginalName": "getActivities" }, ] } ] While what the InTune team has *works*, it suffers from a number of "workflow" problems, e.g. incremental builds are problematic (they might try to rewrite assemblies which were already rewritten), IL rewriting is *always* "fun", and if we change IL binding patterns, they'll need to support previous "binding styles" and newer styles. Introduce partial support for this scenario by adding the following members to `Java.Interop.JniRuntime.JniTypeManager`: namespace Java.Interop { public partial class JniRuntime { public struct ReplacementMethodInfo { public string? SourceJniType {get; set;} public string? SourceJniMethodName {get; set;} public string? SourceJniMethodSignature {get; set;} public string? TargetJniType {get; set;} public string? TargetJniMethodName {get; set;} public string? TargetJniMethodSignature {get; set;} public int? TargetJniMethodParameterCount {get; set;} public bool TargetJniMethodIsStatic {get; set;} } public partial class JniTypeManager { public string? GetReplacementType (string jniSimpleReference) => GetReplacementTypeCore (jniSimpleReference); protected virtual string? GetReplacementTypeCore (string jniSimpleReference) => null; public ReplacementMethodInfo? GetReplacementMethodInfo (string jniSimpleReference, string jniMethodName, string jniMethodSignature) => GetReplacementMethodInfoCore (jniSimpleReference, jniMethodName,jniMethodSignature); protected virtual ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSimpleReference, string jniMethodName, string jniMethodSignature) => null; } } } These new methods are used by `JniPeerMembers`. Consider "normal" usage of `JniPeerMembers`, within a binding: partial class Activity { static readonly JniPeerMembers _members = new XAPeerMembers (jniPeerTypeName:"android/app/Activity", managedPeerType:typeof (Activity)); } `JniRuntime.JniTypeManager.GetReplacementType()` allows "replacing" the `jniPeerTypeName` value with a "replacement" JNI type name. The "replacement" type will be used for all field and method lookups. This allows supporting the `ClassRewrites[0].Class.From` / `ClassRewrites[0].Class.To` semantics. `JniRuntime.JniTypeManager.GetReplacementMethodInfo()` is the key integration for all other "replacement" semantics. Given the source type, source method name, and source JNI signature, it is responsible for providing an *overriding* target type, target method name, and target JNI signature. For `ClassRewrites[0].Methods[0]`, this allows "renaming" `Activity.onCreate()` to `MAMActivity.onMAMCreate()`: var r = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo ( // Note: must match GetReplacementType() *output*, and since // `Activity` is mapped to `MAMActivity`… "com/microsoft/intune/mam/client/app/MAMActivity", "onCreate", "(Landroid/os/Bundle;)V" ); // is equivalent to: var r = new JniRuntime.ReplacementMethodInfo { SourceJniType = "com/microsoft/intune/mam/client/app/MAMActivity", // from input parameter SourceJniMethodName = "onCreate", // from input parameter SourceJniMethodSignature = "(Landroid/os/Bundle;)V", // from input parameter TargetJniType = "com/microsoft/intune/mam/client/app/MAMActivity", TargetJniMethodName = "onMAMCreate", TargetJniMethodSignature = null, // "use original value" TargetJniMethodParameterCount = null, // unchanged TargetJniMethodIsStatic = false, } `ClassRewrites[1].Methods[0]` is "weird", in that it has `MakeStatic: true`. The semantics for when `MakeStatic: true` exists is that the "original" method is an *instance* method, and the target method is a *`static`* method, *and* the `this` instance now becomes a *parameter* to the method. This is likewise supported via `JniRuntime.JniTypeManager.GetReplacementMethodInfo()`, and is identified by: 1. `ReplacementMethodInfo.TargetJniMethodParameterCount` being a non-`null` value, and 2. `ReplacementMethodInfo.TargetJniMethodIsStatic` is true. Thus, `ClassRewrites[1].Methods[0]` is akin to: var r = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo ( "android/content/pm/PackageManager", "checkPermission", "(Ljava/lang/String;Ljava/lang/String;)I" ); // is equivalent to: var r = new JniRuntime.ReplacementMethodInfo { SourceJniType = "android/content/pm/PackageManager", // from input parameter SourceJniMethodName = "checkPermission", // from input parameter SourceJniMethodSignature = "(Ljava/lang/String;Ljava/lang/String;)I", // from input parameter TargetJniType = "com/microsoft/intune/mam/client/content/pm/MAMPackageManagement", TargetJniMethodName = "CheckPermission", TargetJniMethodSignature = "(Landroid/content/pm/PackageManager;Ljava/lang/String;Ljava/lang/String;)I", // Note: `PackageManager` is inserted as new first parameter TargetJniMethodParameterCount = 3, TargetJniMethodIsStatic = true, } (Note that the subclass is responsible for providing this data.) `ReplacementMethodInfo.TargetJniMethodParameterCount` must be the number of parameters that the target method accepts. This is needed so that `JniPeerMembers.JniInstanceMethods.TryInvoke*StaticRedirect()` can appropriately reallocate the `JniArgumentValue*` array, so that the `this` parameter can be added to the beginning of the parameters. ~~ Overhead? ~~ All these extra invocations into `JniRuntime.JniTypeManager` imply additional overhead to invoke a Java method. However, this is all done during the *first* time a method is looked up, after which the `JniMethodInfo` instance is *cached* for a given (type, method, signature) tuple. To demonstrate overheads, `samples/Hello` has been updated to accept a new `-t` value; when provided, it invokes the `java.lang.Object.hashCode()` method 1 million times and calculates the average invocation overhead: % dotnet run --project samples/Hello -- -t Object.hashCode: 1000000 invocations. Total=00:00:00.5196758; Average=0.0005196758ms If we replace the .NET 6 `samples/Hello/bin/Debug/Java.Interop.dll` assembly with e.g. `bin/Debug/Java.Interop.dll`, we can compare to performance *without* these new changes, as the changes are only in the .NET 6 build: % \cp bin/Debug/Java.Interop{.dll,.pdb} samples/Hello/bin/Debug % dotnet samples/Hello/bin/Debug/Hello.dll -t Object.hashCode: 1000000 invocations. Total=00:00:00.5386676; Average=0.0005386676ms There is a fair bit of variation in `dotnet Hello.dll -t` output, but they're all roughly similar. There doesn't appear to be significant overhead for this particular test. ~~ Other Changes ~~ Cleaned up the `JniPeerMembers` constructors. The `Debug.Assert()` calls were duplicative and redundant. Replace the `Debug.Assert()` with `Debug.WriteLine()`. `Mono.Android.NET-Tests.apk` was running into an "unfixable" scenario: WARNING: ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof(Android.Runtime.JavaList)).JniTypeName="java/util/ArrayList" != "java/util/List" This was because of [`Android.Runtime.JavaList`][5] using a `JniPeerMembers` for `List` while registering `ArrayList`, causing typemaps to associate `JavaList` with `ArrayList`: [Register ("java/util/ArrayList", DoNotGenerateAcw=true)] partial class JavaList { internal static readonly JniPeerMembers list_members = new XAPeerMembers ("java/util/List", typeof (JavaList), isInterface: true); } @jonpryor doesn't want to try fixing this right now; turning the assertion into a diagnostic message feels preferable. [0]: https://en.wikipedia.org/wiki/Android_Runtime [1]: https://developer.android.com/studio/write/java8-support [2]: https://developer.android.com/studio/write/java8-support-table [3]: https://docs.microsoft.com/en-us/mem/intune/developer/app-sdk-xamarin [4]: https://www.nuget.org/packages/Microsoft.Intune.MAM.Remapper.Tasks/ [5]: https://github.com/xamarin/xamarin-android/blob/b250c04b09a2b725336ae6d6c5693e0b9f37e4cc/src/Mono.Android/Android.Runtime/JavaList.cs#L9-L13
1 parent 02aa54e commit 1f27ab5

24 files changed

+1760
-305
lines changed

samples/Hello/Program.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Threading;
34

45
using Mono.Options;
@@ -9,10 +10,13 @@ namespace Hello
910
{
1011
class App
1112
{
13+
const int N = 1000000;
14+
1215
public static void Main (string[] args)
1316
{
1417
string? jvmPath = global::Java.InteropTests.TestJVM.GetJvmLibraryPath ();
1518
bool createMultipleVMs = false;
19+
bool reportTiming = false;
1620
bool showHelp = false;
1721
var options = new OptionSet () {
1822
"Using the JVM from C#!",
@@ -24,6 +28,9 @@ public static void Main (string[] args)
2428
{ "m",
2529
"Create multiple Java VMs. This will likely creash.",
2630
v => createMultipleVMs = v != null },
31+
{ "t",
32+
$"Timing; invoke Object.hashCode() {N} times, print average.",
33+
v => reportTiming = v != null },
2734
{ "h|help",
2835
"Show this message and exit.",
2936
v => showHelp = v != null },
@@ -33,23 +40,25 @@ public static void Main (string[] args)
3340
options.WriteOptionDescriptions (Console.Out);
3441
return;
3542
}
36-
Console.WriteLine ("Hello World!");
3743
var builder = new JreRuntimeOptions () {
3844
JniAddNativeMethodRegistrationAttributePresent = true,
3945
JvmLibraryPath = jvmPath,
4046
};
4147
builder.AddOption ("-Xcheck:jni");
48+
4249
var jvm = builder.CreateJreVM ();
43-
Console.WriteLine ($"JniRuntime.CurrentRuntime == jvm? {ReferenceEquals (JniRuntime.CurrentRuntime, jvm)}");
44-
foreach (var h in JniRuntime.GetAvailableInvocationPointers ()) {
45-
Console.WriteLine ("PRE: GetCreatedJavaVMHandles: {0}", h);
46-
}
4750

48-
CreateJLO ();
51+
if (reportTiming) {
52+
ReportTiming ();
53+
return;
54+
}
4955

5056
if (createMultipleVMs) {
5157
CreateAnotherJVM ();
58+
return;
5259
}
60+
61+
CreateJLO ();
5362
}
5463

5564
static void CreateJLO ()
@@ -58,6 +67,17 @@ static void CreateJLO ()
5867
Console.WriteLine ($"binding? {jlo.ToString ()}");
5968
}
6069

70+
static void ReportTiming ()
71+
{
72+
var jlo = new Java.Lang.Object ();
73+
var t = Stopwatch.StartNew ();
74+
for (int i = 0; i < N; ++i) {
75+
jlo.GetHashCode ();
76+
}
77+
t.Stop ();
78+
Console.WriteLine ($"Object.hashCode: {N} invocations. Total={t.Elapsed}; Average={t.Elapsed.TotalMilliseconds / (double) N}ms");
79+
}
80+
6181
static unsafe void CreateAnotherJVM ()
6282
{
6383
Console.WriteLine ("Part 2!");

src/Java.Interop/Java.Interop.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
<OutputPath>$(ToolOutputFullPath)</OutputPath>
2727
<DocumentationFile>$(ToolOutputFullPath)Java.Interop.xml</DocumentationFile>
2828
<JNIEnvGenPath>$(BuildToolOutputFullPath)</JNIEnvGenPath>
29-
<LangVersion>8.0</LangVersion>
29+
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">9.0</LangVersion>
30+
<LangVersion Condition=" '$(LangVersion)' == '' ">8.0</LangVersion>
3031
<Nullable>enable</Nullable>
3132
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
3233
<MSBuildWarningsAsMessages>NU1702</MSBuildWarningsAsMessages>

src/Java.Interop/Java.Interop/JavaArray.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,9 @@ bool IList.IsFixedSize {
231231

232232
object? IList.this [int index] {
233233
get {return this [index];}
234-
#pragma warning disable 8601
234+
#pragma warning disable 8600,8601
235235
set {this [index] = (T) value;}
236-
#pragma warning restore 8601
236+
#pragma warning restore 8600,8601
237237
}
238238

239239
void ICollection.CopyTo (Array array, int index)

src/Java.Interop/Java.Interop/JniEnvironment.Types.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ static Types ()
3131
}
3232
}
3333

34-
public static unsafe JniObjectReference FindClass (string classname)
34+
public static JniObjectReference FindClass (string classname)
35+
{
36+
return TryFindClass (classname, throwOnError: true);
37+
}
38+
39+
static unsafe JniObjectReference TryFindClass (string classname, bool throwOnError)
3540
{
3641
if (classname == null)
3742
throw new ArgumentNullException (nameof (classname));
@@ -85,6 +90,10 @@ public static unsafe JniObjectReference FindClass (string classname)
8590
}
8691
}
8792

93+
if (!throwOnError) {
94+
(pendingException as IJavaPeerable)?.Dispose ();
95+
return default;
96+
}
8897
throw pendingException!;
8998
#endif // !FEATURE_JNIENVIRONMENT_JI_PINVOKES
9099
#if FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
@@ -120,10 +129,27 @@ public static unsafe JniObjectReference FindClass (string classname)
120129
var loadClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
121130
LogCreateLocalRef (loadClassThrown);
122131
pendingException = info.Runtime.GetExceptionForThrowable (ref loadClassThrown, JniObjectReferenceOptions.CopyAndDispose);
132+
if (!throwOnError) {
133+
(pendingException as IJavaPeerable)?.Dispose ();
134+
return default;
135+
}
123136
throw pendingException!;
124137
#endif // !FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
125138
}
126139

140+
#if NET
141+
public static bool TryFindClass (string classname, out JniObjectReference instance)
142+
{
143+
if (classname == null)
144+
throw new ArgumentNullException (nameof (classname));
145+
if (classname.Length == 0)
146+
throw new ArgumentException ("'classname' cannot be a zero-length string.", nameof (classname));
147+
148+
instance = TryFindClass (classname, throwOnError: false);
149+
return instance.IsValid;
150+
}
151+
#endif // NET
152+
127153
public static JniType? GetTypeFromInstance (JniObjectReference instance)
128154
{
129155
if (!instance.IsValid)
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#nullable enable
2+
3+
#if NET
4+
5+
using System;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Reflection;
10+
using System.Runtime.InteropServices;
11+
using System.Runtime.Versioning;
12+
13+
namespace Java.Interop
14+
{
15+
public struct JniMemberSignature : IEquatable<JniMemberSignature>
16+
{
17+
public static readonly JniMemberSignature Empty;
18+
19+
string? memberName;
20+
string? memberSignature;
21+
22+
public string MemberName => memberName ?? throw new InvalidOperationException ();
23+
public string MemberSignature => memberSignature ?? throw new InvalidOperationException ();
24+
25+
public JniMemberSignature (string memberName, string memberSignature)
26+
{
27+
if (string.IsNullOrEmpty (memberName)) {
28+
throw new ArgumentNullException (nameof (memberName));
29+
}
30+
if (string.IsNullOrEmpty (memberSignature)) {
31+
throw new ArgumentNullException (nameof (memberSignature));
32+
}
33+
this.memberName = memberName;
34+
this.memberSignature = memberSignature;
35+
}
36+
37+
public static int GetParameterCountFromMethodSignature (string jniMethodSignature)
38+
{
39+
if (jniMethodSignature.Length < "()V".Length || jniMethodSignature [0] != '(' ) {
40+
throw new ArgumentException (
41+
$"Member signature `{jniMethodSignature}` is not a method signature. Method signatures must start with `(`.",
42+
nameof (jniMethodSignature));
43+
}
44+
int count = 0;
45+
int index = 1;
46+
while (index < jniMethodSignature.Length &&
47+
jniMethodSignature [index] != ')') {
48+
ExtractType (jniMethodSignature, ref index);
49+
count++;
50+
}
51+
return count;
52+
}
53+
54+
internal static (int StartIndex, int Length) ExtractType (string signature, ref int index)
55+
{
56+
AssertSignatureIndex (signature, index);
57+
var i = index++;
58+
switch (signature [i]) {
59+
case '[':
60+
if ((i+1) >= signature.Length)
61+
throw new InvalidOperationException ($"Missing array type after '[' at index {i} in: `{signature}`");
62+
var rest = ExtractType (signature, ref index);
63+
return (StartIndex: i, Length: index - i);
64+
case 'B':
65+
case 'C':
66+
case 'D':
67+
case 'F':
68+
case 'I':
69+
case 'J':
70+
case 'S':
71+
case 'V':
72+
case 'Z':
73+
return (StartIndex: i, Length: 1);
74+
case 'L':
75+
int depth = 0;
76+
int e = index;
77+
while (e < signature.Length) {
78+
var c = signature [e++];
79+
if (depth == 0 && c == ';')
80+
break;
81+
}
82+
if (e > signature.Length)
83+
throw new InvalidOperationException ($"Missing reference type after `{signature [i]}` at index {i} in `{signature}`!");
84+
index = e;
85+
return (StartIndex: i, Length: (e - i));
86+
default:
87+
throw new InvalidOperationException ($"Unknown JNI Type `{signature [i]}` within: `{signature}`!");
88+
}
89+
}
90+
91+
internal static void AssertSignatureIndex (string signature, int index)
92+
{
93+
if (signature == null)
94+
throw new ArgumentNullException (nameof (signature));
95+
if (signature.Length == 0)
96+
throw new ArgumentException ("Descriptor cannot be empty string", nameof (signature));
97+
if (index >= signature.Length)
98+
throw new ArgumentException ("index >= descriptor.Length", nameof (index));
99+
}
100+
101+
public override int GetHashCode ()
102+
{
103+
return (memberName?.GetHashCode () ?? 0) ^
104+
(memberSignature?.GetHashCode () ?? 0);
105+
}
106+
107+
public override bool Equals (object? obj)
108+
{
109+
var v = obj as JniMemberSignature?;
110+
if (v.HasValue)
111+
return Equals (v.Value);
112+
return false;
113+
}
114+
115+
public bool Equals (JniMemberSignature other)
116+
{
117+
return memberName == other.memberName &&
118+
memberSignature == other.memberSignature;
119+
}
120+
121+
public override string ToString ()
122+
{
123+
return $"{nameof (JniMemberSignature)} {{ " +
124+
$"{nameof (MemberName)} = {(memberName == null ? "null" : "\"" + memberName + "\"")}" +
125+
$", {nameof (MemberSignature)} = {(memberSignature == null ? "null" : "\"" + memberSignature + "\"")}" +
126+
$"}}";
127+
}
128+
129+
public static bool operator== (JniMemberSignature a, JniMemberSignature b) => a.Equals (b);
130+
public static bool operator!= (JniMemberSignature a, JniMemberSignature b) => !a.Equals (b);
131+
}
132+
}
133+
134+
#endif // NET

src/Java.Interop/Java.Interop/JniMethodInfo.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ public sealed class JniMethodInfo
1010

1111
public bool IsStatic {get; private set;}
1212

13+
#if NET
14+
internal JniType? StaticRedirect;
15+
internal int? ParameterCount;
16+
#endif //NET
17+
1318
internal bool IsValid {
1419
get {return ID != IntPtr.Zero;}
1520
}

src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,46 @@ internal JniInstanceMethods GetConstructorsForType (Type declaringType)
8484
public JniMethodInfo GetMethodInfo (string encodedMember)
8585
{
8686
lock (InstanceMethods) {
87-
if (!InstanceMethods.TryGetValue (encodedMember, out var m)) {
88-
string method, signature;
89-
JniPeerMembers.GetNameAndSignature (encodedMember, out method, out signature);
90-
m = JniPeerType.GetInstanceMethod (method, signature);
91-
InstanceMethods.Add (encodedMember, m);
87+
if (InstanceMethods.TryGetValue (encodedMember, out var m)) {
88+
return m;
9289
}
93-
return m;
9490
}
91+
string method, signature;
92+
JniPeerMembers.GetNameAndSignature (encodedMember, out method, out signature);
93+
var info = GetMethodInfo (method, signature);
94+
lock (InstanceMethods) {
95+
if (InstanceMethods.TryGetValue (encodedMember, out var m)) {
96+
return m;
97+
}
98+
InstanceMethods.Add (encodedMember, info);
99+
}
100+
return info;
101+
}
102+
103+
JniMethodInfo GetMethodInfo (string method, string signature)
104+
{
105+
#if NET
106+
var m = (JniMethodInfo?) null;
107+
var newMethod = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (Members.JniPeerTypeName, method, signature);
108+
if (newMethod.HasValue) {
109+
var typeName = newMethod.Value.TargetJniType ?? Members.JniPeerTypeName;
110+
var methodName = newMethod.Value.TargetJniMethodName ?? method;
111+
var methodSig = newMethod.Value.TargetJniMethodSignature ?? signature;
112+
113+
using var t = new JniType (typeName);
114+
if (newMethod.Value.TargetJniMethodInstanceToStatic &&
115+
t.TryGetStaticMethod (methodName, methodSig, out m)) {
116+
m.ParameterCount = newMethod.Value.TargetJniMethodParameterCount;
117+
m.StaticRedirect = new JniType (typeName);
118+
return m;
119+
}
120+
if (t.TryGetInstanceMethod (methodName, methodSig, out m)) {
121+
return m;
122+
}
123+
Console.Error.WriteLine ($"warning: For declared method `{Members.JniPeerTypeName}.{method}.{signature}`, could not find requested method `{typeName}.{methodName}.{methodSig}`!");
124+
}
125+
#endif // NET
126+
return JniPeerType.GetInstanceMethod (method, signature);
95127
}
96128

97129
public unsafe JniObjectReference StartCreateInstance (string constructorSignature, Type declaringType, JniArgumentValue* parameters)

0 commit comments

Comments
 (0)