Skip to content

[Xamarin.Android.Build.Tasks] Java.Interop has typemaps #9768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 12, 2025

Conversation

jonpryor
Copy link
Contributor

@jonpryor jonpryor commented Feb 7, 2025

If you have a Debug build of .NET for Android, and you enable assembly logging:

% adb shell setprop debug.mono.log default,assembly

then adb logcat will contain messages such as:

I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.ManagedPeer, Java.Interop'
W monodroid: typemap: failed to map managed type to Java type: Java.Interop.ManagedPeer, Java.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 (Module ID
W monodroid-assembly: typemap: called from
W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type type)
W monodroid-assembly:    at Android.Runtime.AndroidTypeManager.GetSimpleReference(Type type)
W monodroid-assembly:    at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type type)
W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
W monodroid-assembly:    at Java.Interop.ManagedPeer..cctor()
W monodroid-assembly:    at Java.Interop.JniRuntime..ctor(CreationOptions options)
W monodroid-assembly:    at Android.Runtime.AndroidRuntime..ctor(IntPtr jnienv, IntPtr vm, IntPtr classLoader, IntPtr classLoader_loadClass, Boolean jniAddNativeMethodRegistrationAttributePresent)
W monodroid-assembly:    at Android.Runtime.JNIEnvInit.Initialize(JnienvInitializeArgs* args)
…
I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.JavaObject, Java.Interop'
W monodroid: typemap: failed to map managed type to Java type: Java.Interop.JavaObject, Java.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 (Module ID: 90427bf1-700a-4676-b23a-fb68e2e2e9da; Type token: 33554439)
W monodroid-assembly: typemap: called from
W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type type)
W monodroid-assembly:    at Android.Runtime.AndroidTypeManager.GetSimpleReference(Type type)
W monodroid-assembly:    at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type type)
W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
W monodroid-assembly:    at Java.Interop.JavaObject..cctor()
W monodroid-assembly:    at Java.Interop.TypeManager.Activate(IntPtr jobject, ConstructorInfo cinfo, Object[] parms)
W monodroid-assembly:    at Java.Interop.TypeManager.n_Activate(IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr)
W monodroid-assembly:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLLLL_V(_JniMarshal_PPLLLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2, IntPtr p3)

ManagedPeer and JavaObject are types within Java.Interop.dll which uses JniPeerMembers, and Debug builds of JniPeerMembers try to verify that the typemap entry is consistent:

partial class JniPeerMembers {
  JniPeerMembers string jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType, bool isInterface = false)
  {
    // …
#if DEBUG
    var signatureFromType   = JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType);
    if (signatureFromType.SimpleReference != jniPeerTypeName) {
      Debug.WriteLine ("WARNING-Java.Interop: ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof({0})).JniTypeName=\"{1}\" != \"{2}\"",
          managedPeerType.FullName,
          signatureFromType.SimpleReference,
          jniPeerTypeName);
      Debug.WriteLine (new System.Diagnostics.StackTrace (true));
    }
#endif  // DEBUG
  }
}

However, Java.Interop.dll is not considered to be a .NET for Android assembly, as it's built with e.g. $(TargetFramework)=net9.0 and not $(TargetFramework)=net9.0-android. As such, it is skipped during typemap processing.

Update XAJavaTypeScanner.SpecialAssemblies so that Java.Interop.dll is explicitly processed. This will ensure that there is a typemap entry for ManagedPeer and that the JniPeerMembers checks pass. This allows typemap entries for JavaObject and ManagedPeer to be created, removing the above warning messages.

If you have a Debug build of .NET for Android, and  you enable
assembly logging:

	% adb shell setprop debug.mono.log default,assembly

then `adb logcat` will contain messages such as:

	I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.ManagedPeer, Java.Interop'
	W monodroid: typemap: failed to map managed type to Java type: Java.Interop.ManagedPeer, Java.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 (Module ID
	W monodroid-assembly: typemap: called from
	W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type type)
	W monodroid-assembly:    at Android.Runtime.AndroidTypeManager.GetSimpleReference(Type type)
	W monodroid-assembly:    at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type type)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	W monodroid-assembly:    at Java.Interop.ManagedPeer..cctor()
	W monodroid-assembly:    at Java.Interop.JniRuntime..ctor(CreationOptions options)
	W monodroid-assembly:    at Android.Runtime.AndroidRuntime..ctor(IntPtr jnienv, IntPtr vm, IntPtr classLoader, IntPtr classLoader_loadClass, Boolean jniAddNativeMethodRegistrationAttributePresent)
	W monodroid-assembly:    at Android.Runtime.JNIEnvInit.Initialize(JnienvInitializeArgs* args)
	…
	I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.JavaObject, Java.Interop'
	W monodroid: typemap: failed to map managed type to Java type: Java.Interop.JavaObject, Java.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 (Module ID: 90427bf1-700a-4676-b23a-fb68e2e2e9da; Type token: 33554439)
	W monodroid-assembly: typemap: called from
	W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type type)
	W monodroid-assembly:    at Android.Runtime.AndroidTypeManager.GetSimpleReference(Type type)
	W monodroid-assembly:    at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type type)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	W monodroid-assembly:    at Java.Interop.JavaObject..cctor()
	W monodroid-assembly:    at Java.Interop.TypeManager.Activate(IntPtr jobject, ConstructorInfo cinfo, Object[] parms)
	W monodroid-assembly:    at Java.Interop.TypeManager.n_Activate(IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr)
	W monodroid-assembly:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLLLL_V(_JniMarshal_PPLLLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2, IntPtr p3)

`ManagedPeer` and `JavaObject` are types within `Java.Interop.dll`
which uses `JniPeerMembers`, and [Debug builds of `JniPeerMembers`][0]
try to verify that the typemap entry is consistent:

	partial class JniPeerMembers {
	  JniPeerMembers string jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType, bool isInterface = false)
	  {
	    // …
	#if DEBUG
	    var signatureFromType   = JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType);
	    if (signatureFromType.SimpleReference != jniPeerTypeName) {
	      Debug.WriteLine ("WARNING-Java.Interop: ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof({0})).JniTypeName=\"{1}\" != \"{2}\"",
	          managedPeerType.FullName,
	          signatureFromType.SimpleReference,
	          jniPeerTypeName);
	      Debug.WriteLine (new System.Diagnostics.StackTrace (true));
	    }
	#endif  // DEBUG
	  }
	}

However, `Java.Interop.dll` is not considered to be a .NET for Android
assembly, as it's built with e.g. `$(TargetFramework)=net9.0` and not
`$(TargetFramework)=net9.0-android`.  As such, it is skipped during
typemap processing.

Update `XAJavaTypeScanner.SpecialAssemblies` so that `Java.Interop.dll`
is explicitly processed.  This will ensure that there is a typemap
entry for `ManagedPeer` and that the `JniPeerMembers` checks pass.
This allows typemap entries for `JavaObject` and `ManagedPeer` to be
created, removing the above warning messages.

[0]: https://github.com/dotnet/java-interop/blob/6bc87e8b55bc00ae1423a5ae92cf5db573fc76ed/src/Java.Interop/Java.Interop/JniPeerMembers.cs#L45-L54
Copy link
Contributor

@dellis1972 dellis1972 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.
Odd this hasn't caused any problems before?

jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 10, 2025
Context: dotnet/android#9768

dotnet/android#9768 attempts to add types from `Java.Interop.dll` to
the .NET for Android typemaps, in order to fix Debug warnings like:

	I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.ManagedPeer, Java.Interop'

Unfortunately, this causes the assertion:

	AssertGetJniTypeInfoForType (typeof (JavaArray<JavaObject>),    "[Ljava/lang/Object;",  false,  1);

within `Java.InteropTests.JniTypeManagerTests.GetTypeSignature_Type()`
to fail:

	Expected string length 19 but was 33. Strings differ at index 0.
	Expected: "[Ljava/lang/Object;"
	But was:  "crc64d5d92128469ae06d/JavaArray_1"
	-----------^

The immediate cause of the failure is that
`JniRuntime.JniTypeManager.GetTypeSignature()` called
`JniRuntime.JniTypeManager.GetSimpleReference()` *before* it tried
to see if the type was `JavaArray<T>`.  As `Java.Interop.dll` was now
being processed for typemap purposes, and because `JavaArray<T>` did
not have a `[JniTypeSignatureAttribute]`, the typemap got the default
behavior of `crc64[hash…]`.

The broader cause is that `GetSimpleReference()` should be the
*fallback* implementation, used after all other attempts to get a
JNI name have failed.

Update `GetTypeSignature()` and `GetTypeSignatures()` so that
`GetSimpleReference()` and/or `GetSimpleReferences()` are in fact
treated as fallbacks.

Additionally, update `AssertGetJniTypeInfoForType()` to assert that
the value returned by `GetTypeSignature()` is the same as the value
return3ed by` GetTypeSignatures().First()`.  This was *commented* as
being the case, but we should *verify* that as well.

Finally, update `AssertGetJniTypeInfoForType()` so that when the
assertion fails, the source `type` value is provided.
Does It Build™?
Context: dotnet/java-interop@baf43b0

Support for MD5 was removed in dotnet/java-interop@baf43b09.
Attempting to use it now results in an XAGJS7015 error:

	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015: System.NotSupportedException: PackageNamingPolicy.LowercaseHash is no longer supported. [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.GetPackageName(TypeDefinition type, IMetadataResolver resolver) in /Users/builder/azdo/_work/3/s/xamarin-android/external/Java.Interop/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs:line 683 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.<>c__DisplayClass56_0.<ToJniName>b__2(TypeDefinition t) in /Users/builder/azdo/_work/3/s/xamarin-android/external/Java.Interop/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs:line 552 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName[T](T type, Func`2 decl, Func`2 name, Func`2 ns, Func`2 overrideName, Func`2 shouldUpdateName) in /Users/builder/azdo/_work/3/s/xamarin-android/external/Java.Interop/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs:line 720 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName(TypeDefinition type, ExportParameterKind exportKind, IMetadataResolver cache) in /Users/builder/azdo/_work/3/s/xamarin-android/external/Java.Interop/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs:line 548 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName(TypeDefinition type, IMetadataResolver resolver) in /Users/builder/azdo/_work/3/s/xamarin-android/external/Java.Interop/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs:line 527 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName(TypeDefinition type, TypeDefinitionCache cache) in /Users/builder/azdo/_work/3/s/xamarin-android/external/Java.Interop/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs:line 523 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Xamarin.Android.Tasks.TypeMapGenerator.GetDebugEntry(TypeDefinition td, TypeDefinitionCache cache) in /Users/builder/azdo/_work/3/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs:line 345 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Xamarin.Android.Tasks.TypeMapGenerator.GenerateDebugNativeAssembly(Boolean skipJniAddNativeMethodRegistrationAttributeScan, String outputDirectory) in /Users/builder/azdo/_work/3/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs:line 266 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Xamarin.Android.Tasks.TypeMapGenerator.GenerateDebug(Boolean skipJniAddNativeMethodRegistrationAttributeScan, String outputDirectory, Boolean generateNativeAssembly) in /Users/builder/azdo/_work/3/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs:line 179 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Xamarin.Android.Tasks.TypeMapGenerator.Generate(Boolean debugBuild, Boolean skipJniAddNativeMethodRegistrationAttributeScan, String outputDirectory, Boolean generateNativeAssembly) in /Users/builder/azdo/_work/3/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs:line 170 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Xamarin.Android.Tasks.GenerateJavaStubs.WriteTypeMappings(NativeCodeGenState state) in /Users/builder/azdo/_work/3/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs:line 466 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Xamarin.Android.Tasks.GenerateJavaStubs.Run(Boolean useMarshalMethods) in /Users/builder/azdo/_work/3/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs:line 269 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Xamarin.Android.Tasks.GenerateJavaStubs.RunTask() in /Users/builder/azdo/_work/3/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs:line 112 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]
	…\Xamarin.Android.Common.targets(1504,3): error XAGJS7015:    at Microsoft.Android.Build.Tasks.AndroidTask.Execute() in /Users/builder/azdo/_work/3/s/xamarin-android/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs:line 25 [D:\a\_work\1\a\TestRelease\02-10_20.56.48\temp\PackageNamingPolicyLowercaseMD5\UnnamedProject.csproj]

The only reason this hasn't failed before is because `Mono.Android.dll`
is special-cased, so the PackageNamingPolicy value is ignored…
until we start processing `Java.Interop.dll`!

Remove `LowercaseMD5` from `BuildTest.PackageNamingPolicy()` and
move it into a new `PackageNamingPolicy_LowercaseMD5_IsObsolete()`
test which asserts that the build fails.
New typemaps, new file sizes.
@jonpryor
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

`PackageNamingPolicy_LowercaseMD5_IsObsolete()` was failing because
`$(AndroidManifest)` was set.  Bad copy & paste!

Fix:

    JNI ERROR (app bug): accessed stale Local 0xd1  (index 13 in a table of size 13)

within `FromNative()`.
@jonpryor
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Context: 25d1f00

THE STORY SO FAR

Remember 25d1f00?

> With a partially heavy heart, we need to special-case typemap entries
> by processing `Mono.Android.dll` *first*, so that it gets first dibs
> at bindings for `java/lang/Object` and other types.
>
> …
>
> Note that the special-casing needs to happen in `NativeTypeMappingData`
> because typemaps were formerly processed in *sorted module order*, in
> which the sort order is based on the *byte representation* of the
> module's MVID (a GUID).  Additionally, *linking changes the MVID*,
> which means module order is *effectively random*.  Consequently,
> trying to special case typemap ordering anywhere else is ineffective.

The above is only true for *Release* builds.

*Debug* builds don't go through `NativeTypeMappingData`, and have a
different mechanism and format for typemap data.

Update `XAJavaTypeScanner.GetJavaTypes()` to process `Mono.Android.dll`
*before* any other assemblies, ensuring that `java/lang/Object` is
typemap'd to `Java.Lang.Object, Mono.Android.dll`.

Add a new `ObjectTest.java_lang_Object_Is_Java_Lang_Object()` unit
test which explicitly verifies the mapping for `java/lang/Object`.
The test doesn't fail now!

…because, due to "special-casing" so that Mono.Android.dll is
processed first -- and probably the `[JniTypeSignature]` additions
in java-interop? -- we don't trigger the exception codepath now.

¯\_(ツ)_/¯
@jonpryor
Copy link
Contributor Author

jonpryor commented Feb 11, 2025

Draft commit message:

Bump to dotnet/java-interop/main@d62008d1 (#9768)

Changes: https://github.com/dotnet/java-interop/compare/57f7bc849f36ec7c3159cc7729c7276b8dcd3ca4...d62008d1ad7f469f801c7c00613f3a9fc94648a1

  * dotnet/java-interop@d62008d1: [Java.Interop] GetSimpleReferences(): fallback for GetTypeSignatures() (dotnet/java-interop#1305)

Context: 25d1f007a7bd1ca3ce960315fabd04b9a687224e

If you have a Debug build of .NET for Android, and you enable
assembly logging:

	% adb shell setprop debug.mono.log default,assembly

then `adb logcat` will contain messages such as:

	I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.ManagedPeer, Java.Interop'
	W monodroid: typemap: failed to map managed type to Java type: Java.Interop.ManagedPeer, Java.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 (Module ID
	W monodroid-assembly: typemap: called from
	W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type type)
	W monodroid-assembly:    at Android.Runtime.AndroidTypeManager.GetSimpleReference(Type type)
	W monodroid-assembly:    at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type type)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	W monodroid-assembly:    at Java.Interop.ManagedPeer..cctor()
	W monodroid-assembly:    at Java.Interop.JniRuntime..ctor(CreationOptions options)
	W monodroid-assembly:    at Android.Runtime.AndroidRuntime..ctor(IntPtr jnienv, IntPtr vm, IntPtr classLoader, IntPtr classLoader_loadClass, Boolean jniAddNativeMethodRegistrationAttributePresent)
	W monodroid-assembly:    at Android.Runtime.JNIEnvInit.Initialize(JnienvInitializeArgs* args)
	
	I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.JavaObject, Java.Interop'
	W monodroid: typemap: failed to map managed type to Java type: Java.Interop.JavaObject, Java.Interop, Version=10.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 (Module ID: 90427bf1-700a-4676-b23a-fb68e2e2e9da; Type token: 33554439)
	W monodroid-assembly: typemap: called from
	W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type type)
	W monodroid-assembly:    at Android.Runtime.AndroidTypeManager.GetSimpleReference(Type type)
	W monodroid-assembly:    at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type type)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType, Boolean checkManagedPeerType, Boolean isInterface)
	W monodroid-assembly:    at Java.Interop.JniPeerMembers..ctor(String jniPeerTypeName, Type managedPeerType)
	W monodroid-assembly:    at Java.Interop.JavaObject..cctor()
	W monodroid-assembly:    at Java.Interop.TypeManager.Activate(IntPtr jobject, ConstructorInfo cinfo, Object[] parms)
	W monodroid-assembly:    at Java.Interop.TypeManager.n_Activate(IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr)
	W monodroid-assembly:    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLLLL_V(_JniMarshal_PPLLLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2, IntPtr p3)

`ManagedPeer` and `JavaObject` are types within `Java.Interop.dll`
which uses `JniPeerMembers`, and [Debug builds of `JniPeerMembers`][0]
try to verify that the typemap entry is consistent:

	partial class JniPeerMembers {
	  JniPeerMembers string jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType, bool isInterface = false)
	  {
	    // …
	#if DEBUG
	    var signatureFromType   = JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType);
	    if (signatureFromType.SimpleReference != jniPeerTypeName) {
	      Debug.WriteLine ("WARNING-Java.Interop: ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof({0})).JniTypeName=\"{1}\" != \"{2}\"",
	          managedPeerType.FullName,
	          signatureFromType.SimpleReference,
	          jniPeerTypeName);
	      Debug.WriteLine (new System.Diagnostics.StackTrace (true));
	    }
	#endif  // DEBUG
	  }
	}

However, `Java.Interop.dll` is not considered to be a .NET for Android
assembly, as it's built with e.g. `$(TargetFramework)=net9.0` and not
`$(TargetFramework)=net9.0-android`.  As such, it is skipped during
typemap processing.

Update `XAJavaTypeScanner.SpecialAssemblies` so that `Java.Interop.dll`
is explicitly processed.  This will ensure that there is a typemap
entry for `ManagedPeer` and that the `JniPeerMembers` checks pass.
This allows typemap entries for `JavaObject` and `ManagedPeer` to be
created, removing the above warning messages.

This also requires updating `XAJavaTypeScanner.GetJavaTypes()` so
that in Debug builds, `Mono.Android.dll` is processed *before* any
other assemblies, ensuring that `java/lang/Object` is typemap'd to
`Java.Lang.Object, Mono.Android.dll`.  See also 25d1f007, which did
the same thing for *Release* builds.

Add a new `ObjectTest.java_lang_Object_Is_Java_Lang_Object()` unit
test which explicitly verifies the mapping for `java/lang/Object`.

[0]: https://github.com/dotnet/java-interop/blob/6bc87e8b55bc00ae1423a5ae92cf5db573fc76ed/src/Java.Interop/Java.Interop/JniPeerMembers.cs#L45-L54

jonpryor added a commit to dotnet/java-interop that referenced this pull request Feb 12, 2025
#1305)

Context: dotnet/android#9768

dotnet/android#9768 attempts to add types from `Java.Interop.dll` to
the .NET for Android typemaps, in order to fix Debug warnings like:

	I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.ManagedPeer, Java.Interop'

Unfortunately, the initial attempt to generate typemaps for
`Java.Interop.dll` caused the assertion:

	AssertGetJniTypeInfoForType (typeof (JavaArray<JavaObject>),    "[Ljava/lang/Object;",  false,  1);

within `Java.InteropTests.JniTypeManagerTests.GetTypeSignature_Type()`
to fail with:

	Expected string length 19 but was 33. Strings differ at index 0.
	Expected: "[Ljava/lang/Object;"
	But was:  "crc64d5d92128469ae06d/JavaArray_1"
	-----------^

The immediate cause of the failure is that
`JniRuntime.JniTypeManager.GetTypeSignature()` called
`JniRuntime.JniTypeManager.GetSimpleReference()` *before* it tried
to see if the type was `JavaArray<T>`.  As `Java.Interop.dll` was now
being processed for typemap purposes, and because `JavaArray<T>` did
not have a `[JniTypeSignatureAttribute]`, the typemap got the default
behavior of `crc64[hash…]`.

The broader cause is that `GetSimpleReference()` should be the
*fallback* implementation, used after all other attempts to get a
JNI name have failed.

Update `GetTypeSignature()` and `GetTypeSignatures()` so that
`GetSimpleReference()` and/or `GetSimpleReferences()` are in fact
treated as fallbacks.

Additionally, update `AssertGetJniTypeInfoForType()` to assert that
the value returned by `GetTypeSignature()` is the same as the value
return3ed by` GetTypeSignatures().First()`.  This was *commented* as
being the case, but we should *verify* that as well.

Finally, *move* the
`type.GetCustomAttribute<JniTypeSignatureAttribute()` and
`GetReplacementType()` logic into `GetSimpleReferences()`.  This 
This emphasizes the "fallback" nature of `GetSimpleReference()`,
adds an missing `GetReplacementType()` invocation from the
`GetTypeSignatures()` codepath, and this is what
[`AndroidTypeManager.GetSimpleReference()`][0] was already doing.

[0]: https://github.com/dotnet/android/blob/21c413195e300b6440eb437dade4f3a114e795f7/src/Mono.Android/Android.Runtime/AndroidRuntime.cs#L279-L289
@jonpryor
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@jonpryor
Copy link
Contributor Author

Fully green here: https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=10997525&view=results

Not sure how two separate PR builds were started for this…

@jonpryor jonpryor merged commit 7acf328 into main Feb 12, 2025
1 of 21 checks passed
@jonpryor jonpryor deleted the dev/jonp/jonp-Java.Interop.dll-has-typemaps branch February 12, 2025 13:22
grendello added a commit that referenced this pull request Feb 12, 2025
@github-actions github-actions bot locked and limited conversation to collaborators Mar 15, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants