Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

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

Lines changed: 1760 additions & 305 deletions

‎samples/Hello/Program.cs‎

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

45
usingMono.Options;
@@ -9,10 +10,13 @@ namespace Hello
910
{
1011
classApp
1112
{
13+
constintN=1000000;
14+
1215
publicstaticvoidMain(string[]args)
1316
{
1417
string?jvmPath=global::Java.InteropTests.TestJVM.GetJvmLibraryPath();
1518
boolcreateMultipleVMs=false;
19+
boolreportTiming=false;
1620
boolshowHelp=false;
1721
varoptions=newOptionSet(){
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
varbuilder=newJreRuntimeOptions(){
3844
JniAddNativeMethodRegistrationAttributePresent=true,
3945
JvmLibraryPath=jvmPath,
4046
};
4147
builder.AddOption("-Xcheck:jni");
48+
4249
varjvm=builder.CreateJreVM();
43-
Console.WriteLine($"JniRuntime.CurrentRuntime == jvm? {ReferenceEquals(JniRuntime.CurrentRuntime,jvm)}");
44-
foreach(varhinJniRuntime.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
staticvoidCreateJLO()
@@ -58,6 +67,17 @@ static void CreateJLO ()
5867
Console.WriteLine($"binding? {jlo.ToString()}");
5968
}
6069

70+
staticvoidReportTiming()
71+
{
72+
varjlo=newJava.Lang.Object();
73+
vart=Stopwatch.StartNew();
74+
for(inti=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
staticunsafevoidCreateAnotherJVM()
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+
<LangVersionCondition=" '$(JIBuildingForNetCoreApp)' == 'True' ">9.0</LangVersion>
30+
<LangVersionCondition=" '$(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[intindex]{
233233
get{returnthis[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
voidICollection.CopyTo(Arrayarray,intindex)

‎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-
publicstaticunsafeJniObjectReferenceFindClass(stringclassname)
34+
publicstaticJniObjectReferenceFindClass(stringclassname)
35+
{
36+
returnTryFindClass(classname,throwOnError:true);
37+
}
38+
39+
staticunsafeJniObjectReferenceTryFindClass(stringclassname,boolthrowOnError)
3540
{
3641
if(classname==null)
3742
thrownewArgumentNullException(nameof(classname));
@@ -85,6 +90,10 @@ public static unsafe JniObjectReference FindClass (string classname)
8590
}
8691
}
8792

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

140+
#if NET
141+
publicstaticboolTryFindClass(stringclassname,outJniObjectReferenceinstance)
142+
{
143+
if(classname==null)
144+
thrownewArgumentNullException(nameof(classname));
145+
if(classname.Length==0)
146+
thrownewArgumentException("'classname' cannot be a zero-length string.",nameof(classname));
147+
148+
instance=TryFindClass(classname,throwOnError:false);
149+
returninstance.IsValid;
150+
}
151+
#endif // NET
152+
127153
publicstaticJniType?GetTypeFromInstance(JniObjectReferenceinstance)
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+
usingSystem;
6+
usingSystem.Diagnostics.CodeAnalysis;
7+
usingSystem.Collections.Generic;
8+
usingSystem.Diagnostics;
9+
usingSystem.Reflection;
10+
usingSystem.Runtime.InteropServices;
11+
usingSystem.Runtime.Versioning;
12+
13+
namespaceJava.Interop
14+
{
15+
publicstructJniMemberSignature:IEquatable<JniMemberSignature>
16+
{
17+
publicstaticreadonlyJniMemberSignatureEmpty;
18+
19+
string?memberName;
20+
string?memberSignature;
21+
22+
publicstringMemberName=>memberName??thrownewInvalidOperationException();
23+
publicstringMemberSignature=>memberSignature??thrownewInvalidOperationException();
24+
25+
publicJniMemberSignature(stringmemberName,stringmemberSignature)
26+
{
27+
if(string.IsNullOrEmpty(memberName)){
28+
thrownewArgumentNullException(nameof(memberName));
29+
}
30+
if(string.IsNullOrEmpty(memberSignature)){
31+
thrownewArgumentNullException(nameof(memberSignature));
32+
}
33+
this.memberName=memberName;
34+
this.memberSignature=memberSignature;
35+
}
36+
37+
publicstaticintGetParameterCountFromMethodSignature(stringjniMethodSignature)
38+
{
39+
if(jniMethodSignature.Length<"()V".Length||jniMethodSignature[0]!='('){
40+
thrownewArgumentException(
41+
$"Member signature `{jniMethodSignature}` is not a method signature. Method signatures must start with `(`.",
42+
nameof(jniMethodSignature));
43+
}
44+
intcount=0;
45+
intindex=1;
46+
while(index<jniMethodSignature.Length&&
47+
jniMethodSignature[index]!=')'){
48+
ExtractType(jniMethodSignature,refindex);
49+
count++;
50+
}
51+
returncount;
52+
}
53+
54+
internalstatic(intStartIndex,intLength)ExtractType(stringsignature,refintindex)
55+
{
56+
AssertSignatureIndex(signature,index);
57+
vari=index++;
58+
switch(signature[i]){
59+
case'[':
60+
if((i+1)>=signature.Length)
61+
thrownewInvalidOperationException($"Missing array type after '[' at index {i} in: `{signature}`");
62+
varrest=ExtractType(signature,refindex);
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+
intdepth=0;
76+
inte=index;
77+
while(e<signature.Length){
78+
varc=signature[e++];
79+
if(depth==0&&c==';')
80+
break;
81+
}
82+
if(e>signature.Length)
83+
thrownewInvalidOperationException($"Missing reference type after `{signature[i]}` at index {i} in `{signature}`!");
84+
index=e;
85+
return(StartIndex:i,Length:(e-i));
86+
default:
87+
thrownewInvalidOperationException($"Unknown JNI Type `{signature[i]}` within: `{signature}`!");
88+
}
89+
}
90+
91+
internalstaticvoidAssertSignatureIndex(stringsignature,intindex)
92+
{
93+
if(signature==null)
94+
thrownewArgumentNullException(nameof(signature));
95+
if(signature.Length==0)
96+
thrownewArgumentException("Descriptor cannot be empty string",nameof(signature));
97+
if(index>=signature.Length)
98+
thrownewArgumentException("index >= descriptor.Length",nameof(index));
99+
}
100+
101+
publicoverrideintGetHashCode()
102+
{
103+
return(memberName?.GetHashCode()??0)^
104+
(memberSignature?.GetHashCode()??0);
105+
}
106+
107+
publicoverrideboolEquals(object?obj)
108+
{
109+
varv=objasJniMemberSignature?;
110+
if(v.HasValue)
111+
returnEquals(v.Value);
112+
returnfalse;
113+
}
114+
115+
publicboolEquals(JniMemberSignatureother)
116+
{
117+
returnmemberName==other.memberName&&
118+
memberSignature==other.memberSignature;
119+
}
120+
121+
publicoverridestringToString()
122+
{
123+
return$"{nameof(JniMemberSignature)} {{ "+
124+
$"{nameof(MemberName)} = {(memberName==null?"null":"\""+memberName+"\"")}"+
125+
$", {nameof(MemberSignature)} = {(memberSignature==null?"null":"\""+memberSignature+"\"")}"+
126+
$"}}";
127+
}
128+
129+
publicstaticbooloperator==(JniMemberSignaturea,JniMemberSignatureb)=>a.Equals(b);
130+
publicstaticbooloperator!=(JniMemberSignaturea,JniMemberSignatureb)=>!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
publicboolIsStatic{get;privateset;}
1212

13+
#if NET
14+
internalJniType?StaticRedirect;
15+
internalint?ParameterCount;
16+
#endif //NET
17+
1318
internalboolIsValid{
1419
get{returnID!=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
publicJniMethodInfoGetMethodInfo(stringencodedMember)
8585
{
8686
lock(InstanceMethods){
87-
if(!InstanceMethods.TryGetValue(encodedMember,outvarm)){
88-
stringmethod,signature;
89-
JniPeerMembers.GetNameAndSignature(encodedMember,outmethod,outsignature);
90-
m=JniPeerType.GetInstanceMethod(method,signature);
91-
InstanceMethods.Add(encodedMember,m);
87+
if(InstanceMethods.TryGetValue(encodedMember,outvarm)){
88+
returnm;
9289
}
93-
returnm;
9490
}
91+
stringmethod,signature;
92+
JniPeerMembers.GetNameAndSignature(encodedMember,outmethod,outsignature);
93+
varinfo=GetMethodInfo(method,signature);
94+
lock(InstanceMethods){
95+
if(InstanceMethods.TryGetValue(encodedMember,outvarm)){
96+
returnm;
97+
}
98+
InstanceMethods.Add(encodedMember,info);
99+
}
100+
returninfo;
101+
}
102+
103+
JniMethodInfoGetMethodInfo(stringmethod,stringsignature)
104+
{
105+
#if NET
106+
varm=(JniMethodInfo?)null;
107+
varnewMethod=JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo(Members.JniPeerTypeName,method,signature);
108+
if(newMethod.HasValue){
109+
vartypeName=newMethod.Value.TargetJniType??Members.JniPeerTypeName;
110+
varmethodName=newMethod.Value.TargetJniMethodName??method;
111+
varmethodSig=newMethod.Value.TargetJniMethodSignature??signature;
112+
113+
usingvart=newJniType(typeName);
114+
if(newMethod.Value.TargetJniMethodInstanceToStatic&&
115+
t.TryGetStaticMethod(methodName,methodSig,outm)){
116+
m.ParameterCount=newMethod.Value.TargetJniMethodParameterCount;
117+
m.StaticRedirect=newJniType(typeName);
118+
returnm;
119+
}
120+
if(t.TryGetInstanceMethod(methodName,methodSig,outm)){
121+
returnm;
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+
returnJniPeerType.GetInstanceMethod(method,signature);
95127
}
96128

97129
publicunsafeJniObjectReferenceStartCreateInstance(stringconstructorSignature,TypedeclaringType,JniArgumentValue*parameters)

0 commit comments

Comments
 (0)