From af195b04a579b0454d33ddaca757723bdecc9493 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 5 Feb 2025 10:30:43 -0600 Subject: [PATCH 1/7] [nativeaot] introduce `Microsoft.Android.Runtime.NativeAOT.dll` This moves: * C# code from `samples/NativeAOT` to a new assembly, `Microsoft.Android.Runtime.NativeAOT.dll`. * Java code from `samples/NativeAOT` to `Xamarin.Android.Build.Tasks`, to be generated at build time. * C# code from `Java.Interop.Environment.csproj` has been trimmed down and moved to `Microsoft.Android.Runtime.NativeAOT.dll` as well. --- Xamarin.Android.sln | 7 + .../Microsoft.Android.Runtime.proj | 6 +- samples/NativeAOT/AndroidManifest.xml | 7 - samples/NativeAOT/NativeAOT.csproj | 8 - .../JavaInteropRuntime.cs | 2 +- .../Android.Runtime.NativeAOT}/Logging.cs | 2 +- .../NativeAotTypeManager.cs | 37 +-- .../NativeAotValueManager.cs | 31 ++- .../AssemblyInfo.cs | 5 + .../Java.Interop/JreRuntime.cs | 180 ++++++++++++++ .../ManagedObjectReferenceManager.cs | 229 ++++++++++++++++++ ...Microsoft.Android.Runtime.NativeAOT.csproj | 40 +++ .../Properties/AssemblyInfo.cs.in | 3 +- .../Microsoft.Android.Sdk.NativeAOT.targets | 2 + .../Resources}/JavaInteropRuntime.java | 4 +- .../Resources}/NativeAotRuntimeProvider.java | 0 .../Tasks/GenerateJavaStubs.cs | 30 ++- .../Utilities/ManifestDocument.cs | 13 +- .../Xamarin.Android.Build.Tasks.csproj | 19 +- .../Xamarin.Android.Common.targets | 6 + 20 files changed, 551 insertions(+), 80 deletions(-) rename {samples/NativeAOT => src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT}/JavaInteropRuntime.cs (97%) rename {samples/NativeAOT => src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT}/Logging.cs (98%) rename {samples/NativeAOT => src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT}/NativeAotTypeManager.cs (85%) rename {samples/NativeAOT => src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT}/NativeAotValueManager.cs (87%) create mode 100644 src/Microsoft.Android.Runtime.NativeAOT/AssemblyInfo.cs create mode 100644 src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs create mode 100644 src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/ManagedObjectReferenceManager.cs create mode 100644 src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj rename {samples/NativeAOT => src/Xamarin.Android.Build.Tasks/Resources}/JavaInteropRuntime.java (60%) rename {samples/NativeAOT => src/Xamarin.Android.Build.Tasks/Resources}/NativeAotRuntimeProvider.java (100%) diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index 5227e5fd898..782b20c69db 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -125,6 +125,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Sdk.Analy EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proguard-android", "src\proguard-android\proguard-android.csproj", "{5FD0133B-69E5-4474-9B67-9FD1D0150C70}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Runtime.NativeAOT", "src\Microsoft.Android.Runtime.NativeAOT\Microsoft.Android.Runtime.NativeAOT.csproj", "{E8831F32-11D7-D42C-E43C-711998BC357A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|AnyCPU = Debug|AnyCPU @@ -347,6 +349,10 @@ Global {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|AnyCPU.Build.0 = Debug|Any CPU {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|AnyCPU.ActiveCfg = Release|Any CPU {5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|AnyCPU.Build.0 = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {E8831F32-11D7-D42C-E43C-711998BC357A}.Release|AnyCPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -406,6 +412,7 @@ Global {A39B6D7C-6616-40D6-8AE4-C6CEE93D2708} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} {5E806C9F-1B67-4B6B-A6AB-258834250DBB} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} {5FD0133B-69E5-4474-9B67-9FD1D0150C70} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} + {E8831F32-11D7-D42C-E43C-711998BC357A} = {04E3E11E-B47D-4599-8AFC-50515A95E715} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {53A1F287-EFB2-4D97-A4BB-4A5E145613F6} diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 9c3c22c1390..5f19f16ffdc 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -37,7 +37,11 @@ projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll" /> <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll" /> <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.Runtime.dll" /> - + + <_AndroidRuntimePackAssemblies + Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Microsoft.Android.Runtime.NativeAOT.dll" + Condition=" '$(AndroidRuntime)' == 'NativeAOT' " + /> <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Mono.Android.Export.dll" /> diff --git a/samples/NativeAOT/AndroidManifest.xml b/samples/NativeAOT/AndroidManifest.xml index 70e89c99003..3fead98ca0f 100644 --- a/samples/NativeAOT/AndroidManifest.xml +++ b/samples/NativeAOT/AndroidManifest.xml @@ -1,13 +1,6 @@ - - \ No newline at end of file diff --git a/samples/NativeAOT/NativeAOT.csproj b/samples/NativeAOT/NativeAOT.csproj index e392eaa7646..18922a65b67 100644 --- a/samples/NativeAOT/NativeAOT.csproj +++ b/samples/NativeAOT/NativeAOT.csproj @@ -9,10 +9,6 @@ 1 1.0 apk - true - - true - ..\..\product.snk android-arm64 @@ -29,8 +25,4 @@ <_FastDeploymentDiagnosticLogging>true - - - - \ No newline at end of file diff --git a/samples/NativeAOT/JavaInteropRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs similarity index 97% rename from samples/NativeAOT/JavaInteropRuntime.cs rename to src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs index 8c86e5d264b..aab88256273 100644 --- a/samples/NativeAOT/JavaInteropRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs @@ -2,7 +2,7 @@ using Java.Interop; using System.Runtime.InteropServices; -namespace NativeAOT; +namespace Android.Runtime.NativeAOT; static class JavaInteropRuntime { diff --git a/samples/NativeAOT/Logging.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/Logging.cs similarity index 98% rename from samples/NativeAOT/Logging.cs rename to src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/Logging.cs index 5073ba9e5a3..24e84371c72 100644 --- a/samples/NativeAOT/Logging.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/Logging.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace NativeAOT; +namespace Android.Runtime.NativeAOT; internal sealed class LogcatTextWriter : TextWriter { diff --git a/samples/NativeAOT/NativeAotTypeManager.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs similarity index 85% rename from samples/NativeAOT/NativeAotTypeManager.cs rename to src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs index baffdef8859..3a7fb1d6dcc 100644 --- a/samples/NativeAOT/NativeAotTypeManager.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs @@ -3,7 +3,7 @@ using Java.Interop; using Java.Interop.Tools.TypeNameMappings; -namespace NativeAOT; +namespace Android.Runtime.NativeAOT; partial class NativeAotTypeManager : JniRuntime.JniTypeManager { @@ -11,25 +11,24 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager { internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; - // TODO: list of types specific to this application - Dictionary typeMappings = new () { - ["android/app/Activity"] = typeof (Android.App.Activity), - ["android/app/Application"] = typeof (Android.App.Application), - ["android/content/Context"] = typeof (Android.Content.Context), - ["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper), - ["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle), - ["android/os/Bundle"] = typeof (Android.OS.Bundle), - ["android/view/ContextThemeWrapper"] = typeof (Android.Views.ContextThemeWrapper), - ["my/MainActivity"] = typeof (MainActivity), - ["my/MainApplication"] = typeof (MainApplication), - }; + static readonly IDictionary TypeMappings = new Dictionary (StringComparer.Ordinal); public NativeAotTypeManager () { AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: NativeAotTypeManager()"); + InitializeTypeMappings (); } - protected override Type? GetInvokerTypeCore (Type type) + void InitializeTypeMappings () + { + // Should be replaced by src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs + throw new InvalidOperationException ("TypeMappings should be replaced during trimming!"); + } + + [return: DynamicallyAccessedMembers (Constructors)] + protected override Type? GetInvokerTypeCore ( + [DynamicallyAccessedMembers (Constructors)] + Type type) { const string suffix = "Invoker"; @@ -68,6 +67,10 @@ static Type MakeGenericType ( return MakeGenericType (suffixDefinition, arguments); } + // NOTE: suppressions below also in `src/Mono.Android/Android.Runtime/AndroidRuntime.cs` + [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value parsed from parameter 'methods'.")] + [UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")] + [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")] public override void RegisterNativeMembers ( JniType nativeClass, [DynamicallyAccessedMembers (MethodsAndPrivateNested)] @@ -143,7 +146,7 @@ public override void RegisterNativeMembers ( protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) { AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}`"); - if (typeMappings.TryGetValue (jniSimpleReference, out var target)) { + if (TypeMappings.TryGetValue (jniSimpleReference, out var target)) { Console.WriteLine ($"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{target}`"); yield return target; } @@ -161,9 +164,7 @@ protected override IEnumerable GetSimpleReferences (Type type) IEnumerable CreateSimpleReferencesEnumerator (Type type) { - if (typeMappings == null) - yield break; - foreach (var e in typeMappings) { + foreach (var e in TypeMappings) { if (e.Value == type) yield return e.Key; } diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs similarity index 87% rename from samples/NativeAOT/NativeAotValueManager.cs rename to src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs index b1996b88b30..84e27c4d204 100644 --- a/samples/NativeAOT/NativeAotValueManager.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -13,9 +14,9 @@ using Android.Runtime; using Java.Interop; -namespace NativeAOT; +namespace Android.Runtime.NativeAOT; -internal class NativeAotValueManager : JniRuntime.JniValueManager +class NativeAotValueManager : JniRuntime.JniValueManager { const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; @@ -113,12 +114,12 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal "Warning: Not registering PeerReference={0} IdentityHashCode=0x{1} Instance={2} Instance.Type={3} Java.Type={4}; " + "keeping previously registered PeerReference={5} Instance={6} Instance.Type={7} Java.Type={8}.", ignoreValue.PeerReference.ToString (), - key.ToString ("x"), - RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x"), + key.ToString ("x", CultureInfo.InvariantCulture), + RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x", CultureInfo.InvariantCulture), ignoreValue.GetType ().FullName, JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference), keepValue.PeerReference.ToString (), - RuntimeHelpers.GetHashCode (keepValue).ToString ("x"), + RuntimeHelpers.GetHashCode (keepValue).ToString ("x", CultureInfo.InvariantCulture), keepValue.GetType ().FullName, JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference)); } @@ -186,8 +187,8 @@ public override void FinalizePeer (IJavaPeerable value) if (o.LogGlobalReferenceMessages) { o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", h.ToString (), - value.JniIdentityHashCode.ToString ("x"), - RuntimeHelpers.GetHashCode (value).ToString ("x"), + value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture), + RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture), value.GetType ().ToString ()); } RemovePeer (value); @@ -200,8 +201,8 @@ public override void FinalizePeer (IJavaPeerable value) if (o.LogGlobalReferenceMessages) { o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", h.ToString (), - value.JniIdentityHashCode.ToString ("x"), - RuntimeHelpers.GetHashCode (value).ToString ("x"), + value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture), + RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture), value.GetType ().ToString ()); } value.SetPeerReference (new JniObjectReference ()); @@ -214,9 +215,11 @@ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference refer try { ActivateViaReflection (reference, cinfo, argumentValues); } catch (Exception e) { - var m = string.Format ("Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", + var m = string.Format ( + CultureInfo.InvariantCulture, + "Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", reference, - GetJniIdentityHashCode (reference).ToString ("x"), + GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture), JniEnvironment.Types.GetJniTypeNameFromInstance (reference), cinfo.DeclaringType?.FullName); Debug.WriteLine (m); @@ -257,7 +260,11 @@ public override List GetSurfacedPeers () static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; - protected override IJavaPeerable? TryCreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type type) + protected override IJavaPeerable? TryCreatePeer ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type type) { var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null); if (c != null) { diff --git a/src/Microsoft.Android.Runtime.NativeAOT/AssemblyInfo.cs b/src/Microsoft.Android.Runtime.NativeAOT/AssemblyInfo.cs new file mode 100644 index 00000000000..00c8d7afa79 --- /dev/null +++ b/src/Microsoft.Android.Runtime.NativeAOT/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.Versioning; + +// NOTE: silences the CA1416 analyzer about supported Android APIs +[assembly: TargetPlatformAttribute("Android35.0")] +[assembly: SupportedOSPlatformAttribute("Android21.0")] diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs new file mode 100644 index 00000000000..752fff16078 --- /dev/null +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs @@ -0,0 +1,180 @@ +// Originally from: https://github.com/dotnet/java-interop/blob/dd3c1d0514addfe379f050627b3e97493e985da6/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using Android.Runtime.NativeAOT; + +namespace Java.Interop { + + struct JavaVMInitArgs { + #pragma warning disable CS0649 // Field is never assigned to; + public JniVersion version; /* use JNI_VERSION_1_2 or later */ + + public int nOptions; + public IntPtr /* JavaVMOption[] */ options; + public byte ignoreUnrecognized; + #pragma warning restore CS0649 + } + + class JreRuntimeOptions : JniRuntime.CreationOptions { + + internal List Options = new List (); + + public bool IgnoreUnrecognizedOptions {get; set;} + + public Collection ClassPath {get; private set;} + + public TextWriter? JniGlobalReferenceLogWriter {get; set;} + public TextWriter? JniLocalReferenceLogWriter {get; set;} + + internal JvmLibraryHandler? LibraryHandler {get; set;} + + public JreRuntimeOptions () + { + JniVersion = JniVersion.v1_2; + ClassPath = new Collection (); + } + + public JreRuntimeOptions AddOption (string option) + { + Options.Add (option); + return this; + } + + public JreRuntimeOptions AddSystemProperty (string name, string value) + { + if (name == null) + throw new ArgumentNullException ("name"); + if (value == null) + throw new ArgumentNullException ("value"); + if (name == "java.class.path") + throw new ArgumentException ("Do not use AddSystemProperty() for the 'java.class.path' property. Add to the ClassPath collection instead.", "name"); + Options.Add (string.Format (CultureInfo.InvariantCulture, "-D{0}={1}", name, value)); + return this; + } + + public JreRuntime CreateJreVM () + { + return new JreRuntime (this); + } + } + + class JreRuntime : JniRuntime + { + static JreRuntime () + { + } + + static JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder) + { + if (builder == null) + throw new ArgumentNullException ("builder"); + if (builder.InvocationPointer == IntPtr.Zero && + builder.EnvironmentPointer == IntPtr.Zero && + string.IsNullOrEmpty (builder.JvmLibraryPath)) + throw new InvalidOperationException ($"Member `{nameof (JreRuntimeOptions)}.{nameof (JreRuntimeOptions.JvmLibraryPath)}` must be set."); + + builder.LibraryHandler = JvmLibraryHandler.Create (); + +#if NET + builder.TypeManager ??= new NativeAotTypeManager (); +#endif // NET + + builder.ValueManager = builder.ValueManager ?? new NativeAotValueManager (builder.TypeManager); + builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter); + + if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero) + return builder; + + throw new NotImplementedException (); + } + + [UnconditionalSuppressMessage ("Trimming", "IL3000", Justification = "We check for a null Assembly.Location value!")] + internal static string? GetAssemblyLocation (Assembly assembly) + { + var location = assembly.Location; + if (!string.IsNullOrEmpty (location)) + return location; + return null; + } + + JvmLibraryHandler LibraryHandler; + + internal protected JreRuntime (JreRuntimeOptions builder) + : base (CreateJreVM (builder)) + { + LibraryHandler = builder.LibraryHandler!; + } + + public override string? GetCurrentManagedThreadName () + { + return Thread.CurrentThread.Name; + } + + public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool fNeedFileInfo) + { + return new StackTrace (skipFrames, fNeedFileInfo) + .ToString (); + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + LibraryHandler?.Dispose (); + LibraryHandler = null!; + } + + public new IEnumerable GetAvailableInvocationPointers () + { + return LibraryHandler.GetAvailableInvocationPointers (); + } + } + + internal abstract partial class JvmLibraryHandler : IDisposable { + public abstract void LoadJvmLibrary (string path); + public abstract int CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args); + public abstract IEnumerable GetAvailableInvocationPointers (); + + public abstract void Dispose (); + + public static JvmLibraryHandler Create () + { + var handler = Environment.GetEnvironmentVariable ("JI_LOADER_TYPE"); + switch (handler?.ToLowerInvariant ()) { + case "": + case null: + case "native-library": + return new NativeLibraryJvmLibraryHandler (); + default: + Console.Error.WriteLine ($"Unsupported JI_LOADER_TYPE value of `{handler}`."); + throw new NotSupportedException (); + } + } + } + +#if NET + + class NativeLibraryJvmLibraryHandler : JvmLibraryHandler { + + public override void LoadJvmLibrary (string path) => + throw new NotImplementedException (); + + public override int CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args) => + throw new NotImplementedException (); + + public override IEnumerable GetAvailableInvocationPointers () => + throw new NotImplementedException (); + + public override void Dispose () { } + } + +#endif // NET +} diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/ManagedObjectReferenceManager.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/ManagedObjectReferenceManager.cs new file mode 100644 index 00000000000..b152ff5b797 --- /dev/null +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/ManagedObjectReferenceManager.cs @@ -0,0 +1,229 @@ +// Originally from: https://github.com/dotnet/java-interop/blob/dd3c1d0514addfe379f050627b3e97493e985da6/src/Java.Runtime.Environment/Java.Interop/ManagedObjectReferenceManager.cs +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace Java.Interop { + + class ManagedObjectReferenceManager : JniRuntime.JniObjectReferenceManager { + + TextWriter? grefLog; + TextWriter? lrefLog; + + int grefCount; + int wgrefCount; + + + public override int GlobalReferenceCount => grefCount; + public override int WeakGlobalReferenceCount => wgrefCount; + + public override bool LogLocalReferenceMessages => lrefLog != null; + public override bool LogGlobalReferenceMessages => grefLog != null; + + public ManagedObjectReferenceManager (TextWriter? grefLog, TextWriter? lrefLog) + { + if (grefLog != null && lrefLog != null && object.ReferenceEquals (grefLog, lrefLog)) { + this.grefLog = this.lrefLog = TextWriter.Synchronized (grefLog); + return; + } + + var grefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_GREF_LOG"); + var lrefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_LREF_LOG"); + + bool samePath = !string.IsNullOrEmpty (grefPath) && + !string.IsNullOrEmpty (lrefPath) && + grefPath == lrefPath; + + if (grefLog != null) { + this.grefLog = TextWriter.Synchronized (grefLog); + } + if (lrefLog != null) { + this.lrefLog = TextWriter.Synchronized (lrefLog); + } + + if (this.grefLog == null && !string.IsNullOrEmpty (grefPath)) { + this.grefLog = TextWriter.Synchronized (CreateTextWriter (grefPath)); + } + if (this.lrefLog == null && samePath) { + this.lrefLog = this.grefLog; + } + if (this.lrefLog == null && !string.IsNullOrEmpty (lrefPath)) { + this.lrefLog = TextWriter.Synchronized (CreateTextWriter (lrefPath)); + } + } + + public override void OnSetRuntime (JniRuntime runtime) + { + base.OnSetRuntime (runtime); + } + + static TextWriter CreateTextWriter (string path) + { + return new StreamWriter (path, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)); + } + + public override void WriteLocalReferenceLine (string format, params object[] args) + { + if (lrefLog == null) + return; + lrefLog.WriteLine (format, args); + lrefLog.Flush (); + } + + public override JniObjectReference CreateLocalReference (JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return reference; + + var r = base.CreateLocalReference (reference, ref localReferenceCount); + + CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, r, Runtime); + + return r; + } + + public override void DeleteLocalReference (ref JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return; + + var r = reference; + + base.DeleteLocalReference (ref reference, ref localReferenceCount); + + DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime); + } + + public override void CreatedLocalReference (JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return; + base.CreatedLocalReference (reference, ref localReferenceCount); + CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, Runtime); + } + + public override IntPtr ReleaseLocalReference (ref JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return IntPtr.Zero; + var r = reference; + var p = base.ReleaseLocalReference (ref reference, ref localReferenceCount); + DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime); + return p; + } + + public override void WriteGlobalReferenceLine (string format, params object?[]? args) + { + if (grefLog == null) + return; + grefLog.WriteLine (format, args!); + grefLog.Flush (); + } + + public override JniObjectReference CreateGlobalReference (JniObjectReference reference) + { + if (!reference.IsValid) + return reference; + var n = base.CreateGlobalReference (reference); + int c = Interlocked.Increment (ref grefCount); + CreatedReference (grefLog, "+g+ grefc", c, reference, n, Runtime); + return n; + } + + public override void DeleteGlobalReference (ref JniObjectReference reference) + { + if (!reference.IsValid) + return; + int c = Interlocked.Decrement (ref grefCount); + DeletedReference (grefLog, "-g- grefc", c, reference, Runtime); + base.DeleteGlobalReference (ref reference); + } + + public override JniObjectReference CreateWeakGlobalReference (JniObjectReference reference) + { + if (!reference.IsValid) + return reference; + var n = base.CreateWeakGlobalReference (reference); + + int wc = Interlocked.Increment (ref wgrefCount); + int gc = grefCount; + if (grefLog != null) { + string message = $"+w+ grefc {gc} gwrefc {wc} obj-handle {reference.ToString ()} -> new-handle {n.ToString ()} " + + $"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + grefLog.WriteLine (message); + grefLog.Flush (); + } + + return n; + } + + public override void DeleteWeakGlobalReference (ref JniObjectReference reference) + { + if (!reference.IsValid) + return; + + int wc = Interlocked.Decrement (ref wgrefCount); + int gc = grefCount; + + if (grefLog != null) { + string message = $"-w- grefc {gc} gwrefc {wc} handle {reference.ToString ()} " + + $"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + grefLog.WriteLine (message); + grefLog.Flush (); + } + + base.DeleteWeakGlobalReference (ref reference); + } + + protected override void Dispose (bool disposing) + { + } + + static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime) + { + if (writer == null) + return; + string message = $"{kind} {count} handle {reference.ToString ()} " + + $"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + writer.WriteLine (message); + writer.Flush (); + } + + static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniObjectReference newReference, JniRuntime runtime) + { + if (writer == null) + return; + string message = $"{kind} {count} obj-handle {reference.ToString ()} -> new-handle {newReference.ToString ()} " + + $"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + writer.WriteLine (message); + writer.Flush (); + } + + static void DeletedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime) + { + if (writer == null) + return; + string message = $"{kind} {count} handle {reference.ToString ()} " + + $"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + writer.WriteLine (message); + writer.Flush (); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj b/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj new file mode 100644 index 00000000000..1c5d5debc2d --- /dev/null +++ b/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj @@ -0,0 +1,40 @@ + + + + + + $(DotNetTargetFramework) + enable + enable + false + ..\..\product.snk + true + $(_MonoAndroidNETDefaultOutDir) + + + + + + + + + + + $(BuildDependsOn); + _CopyToPackDirs; + + + + + + <_RuntimePackFiles Include="$(OutputPath)Microsoft.Android.Runtime.NativeAOT.dll" AndroidRID="%(AndroidAbiAndRuntimeFlavor.AndroidRID)" AndroidRuntime="%(AndroidAbiAndRuntimeFlavor.AndroidRuntime)" /> + + + + + + diff --git a/src/Mono.Android/Properties/AssemblyInfo.cs.in b/src/Mono.Android/Properties/AssemblyInfo.cs.in index 9d8b6f5694b..f083dafcd92 100644 --- a/src/Mono.Android/Properties/AssemblyInfo.cs.in +++ b/src/Mono.Android/Properties/AssemblyInfo.cs.in @@ -45,7 +45,6 @@ using System.Runtime.Versioning; [assembly: InternalsVisibleTo("Mono.Android-TestsMultiDex, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] [assembly: InternalsVisibleTo("Mono.Android-TestsAppBundle, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] [assembly: InternalsVisibleTo("Mono.Android.NET-Tests, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] -// Temporary for samples/NativeAOT -[assembly: InternalsVisibleTo("NativeAOT, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] +[assembly: InternalsVisibleTo("Microsoft.Android.Runtime.NativeAOT, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] [assembly: SuppressMessage ("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "Analyzer fails due to extended characters.", Scope = "member", Target = "~F:Android.Util.Patterns.GoodIriChar")] diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 64dd729f9ed..292a86e28ad 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -81,6 +81,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. + + diff --git a/samples/NativeAOT/JavaInteropRuntime.java b/src/Xamarin.Android.Build.Tasks/Resources/JavaInteropRuntime.java similarity index 60% rename from samples/NativeAOT/JavaInteropRuntime.java rename to src/Xamarin.Android.Build.Tasks/Resources/JavaInteropRuntime.java index 1eb07e9f2e5..cb0474682f9 100644 --- a/samples/NativeAOT/JavaInteropRuntime.java +++ b/src/Xamarin.Android.Build.Tasks/Resources/JavaInteropRuntime.java @@ -4,8 +4,8 @@ public class JavaInteropRuntime { static { - Log.d("JavaInteropRuntime", "Loading NativeAOT.so..."); - System.loadLibrary("NativeAOT"); + Log.d("JavaInteropRuntime", "Loading @MAIN_ASSEMBLY_NAME@.so..."); + System.loadLibrary("@MAIN_ASSEMBLY_NAME@"); } private JavaInteropRuntime() { diff --git a/samples/NativeAOT/NativeAotRuntimeProvider.java b/src/Xamarin.Android.Build.Tasks/Resources/NativeAotRuntimeProvider.java similarity index 100% rename from samples/NativeAOT/NativeAotRuntimeProvider.java rename to src/Xamarin.Android.Build.Tasks/Resources/NativeAotRuntimeProvider.java diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 2fa90dca06e..a908645c829 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -98,6 +98,9 @@ public class GenerateJavaStubs : AndroidTask public string CodeGenerationTarget { get; set; } = ""; + [Required] + public string TargetName { get; set; } = ""; + AndroidRuntime androidRuntime; JavaPeerStyle codeGenerationTarget; @@ -304,18 +307,33 @@ Dictionary MaybeGetArchAssemblies (Dictionary additionalProviders) { - if (androidRuntime != Xamarin.Android.Tasks.AndroidRuntime.MonoVM) { - Log.LogDebugMessage ($"Skipping MonoRuntimeProvider generation for: {androidRuntime}"); - } else { + if (androidRuntime != Xamarin.Android.Tasks.AndroidRuntime.CoreCLR) { // Create additional runtime provider java sources. - string providerTemplateFile = "MonoRuntimeProvider.Bundled.java"; + bool isMonoVM = androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.MonoVM; + string providerTemplateFile = isMonoVM ? + "MonoRuntimeProvider.Bundled.java" : + "NativeAotRuntimeProvider.java"; string providerTemplate = GetResource (providerTemplateFile); foreach (var provider in additionalProviders) { - var contents = providerTemplate.Replace ("MonoRuntimeProvider", provider); - var real_provider = Path.Combine (OutputDirectory, "src", "mono", provider + ".java"); + var contents = providerTemplate.Replace (isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider", provider); + var real_provider = isMonoVM ? + Path.Combine (OutputDirectory, "src", "mono", provider + ".java") : + Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", provider + ".java"); Files.CopyIfStringChanged (contents, real_provider); } + } else { + Log.LogDebugMessage ($"Skipping android.content.ContentProvider generation for: {androidRuntime}"); + } + + // For NativeAOT, generate JavaInteropRuntime.java + if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) { + const string fileName = "JavaInteropRuntime.java"; + string template = GetResource (fileName); + var contents = template.Replace ("@MAIN_ASSEMBLY_NAME@", TargetName); + var path = Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", fileName); + Log.LogDebugMessage ($"Writing: {path}"); + Files.CopyIfStringChanged (contents, path); } // Create additional application java sources. diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs index 7cb28b4942e..0930f930428 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs @@ -673,12 +673,15 @@ XElement CreateApplicationElement (XElement manifest, string applicationClass, L IList AddMonoRuntimeProviders (XElement app) { - if (AndroidRuntime != AndroidRuntime.MonoVM) { - //TODO: implement provider logic for non-Mono runtimes + if (AndroidRuntime == AndroidRuntime.CoreCLR) { + //TODO: implement provider logic for CoreCLR return []; } - app.Add (CreateMonoRuntimeProvider ("mono.MonoRuntimeProvider", null, --AppInitOrder)); + bool isMonoVM = AndroidRuntime == AndroidRuntime.MonoVM; + string packageName = isMonoVM ? "mono" : "net.dot.jni.nativeaot"; + string className = isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider"; + app.Add (CreateMonoRuntimeProvider ($"{packageName}.{className}", null, --AppInitOrder)); var providerNames = new List (); @@ -702,9 +705,9 @@ IList AddMonoRuntimeProviders (XElement app) case "activity": case "receiver": case "service": - string providerName = "MonoRuntimeProvider_" + procs.Count; + string providerName = $"{className}_{procs.Count}"; providerNames.Add (providerName); - app.Add (CreateMonoRuntimeProvider ("mono." + providerName, proc.Value, --AppInitOrder)); + app.Add (CreateMonoRuntimeProvider ($"{packageName}.{providerName}", proc.Value, --AppInitOrder)); break; } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index db23eeb6fe4..dd9b72972a8 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -204,26 +204,11 @@ - - machine.config - - - MonoRuntimeProvider.Bundled.java - - - proguard_xamarin.cfg - - - ApplicationRegistration.java - - - net.android.init.gradle.kts - JavaInteropTypeManager.java - - Resource.Designer.snk + + %(Filename)%(Extension) diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index d93711c4262..3123c94ff3b 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1387,6 +1387,11 @@ because xbuild doesn't support framework reference assemblies. ResourceName="MonoRuntimeProvider.Bundled.java" OutputPath="$(_AndroidIntermediateJavaSourceDirectory)mono\MonoRuntimeProvider.java" /> + From 51f18e4e44cbe8b3fb60762f0a68b351e4440a45 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 6 Feb 2025 10:45:24 -0600 Subject: [PATCH 2/7] MVP "typemap" implementation --- .../Microsoft.Android.Sdk.ILLink.csproj | 8 +- .../TypeMappingStep.cs | 102 ++++++++++++++++++ .../Microsoft.Android.Sdk.ILLink.targets | 7 ++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs diff --git a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj index 0f7136c2746..393ef9077c1 100644 --- a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj +++ b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj @@ -2,10 +2,11 @@ $(DotNetTargetFramework) - ILLINK + ILLINK;HAVE_CECIL false $(MicrosoftAndroidSdkOutDir) annotations + true @@ -26,8 +27,13 @@ + + + + + diff --git a/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs b/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs new file mode 100644 index 00000000000..9491dd06e87 --- /dev/null +++ b/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Java.Interop.Tools.Cecil; +using Java.Interop.Tools.TypeNameMappings; +using Mono.Cecil; +using Mono.Linker; +using Mono.Linker.Steps; + +namespace Microsoft.Android.Sdk.ILLink; + +/// +/// MVP "typemap" implementation for NativeAOT +/// +public class TypeMappingStep : BaseStep +{ + const string AssemblyName = "Microsoft.Android.Runtime.NativeAOT"; + const string TypeName = "Android.Runtime.NativeAOT.NativeAotTypeManager"; + readonly IDictionary TypeMappings = new Dictionary (StringComparer.Ordinal); + AssemblyDefinition? MicrosoftAndroidRuntimeNativeAot; + + protected override void ProcessAssembly (AssemblyDefinition assembly) + { + if (assembly.Name.Name == AssemblyName) { + MicrosoftAndroidRuntimeNativeAot = assembly; + return; + } + if (Annotations?.GetAction (assembly) == AssemblyAction.Delete) + return; + + foreach (var type in assembly.MainModule.Types) { + ProcessType (assembly, type); + } + } + + protected override void EndProcess () + { + if (MicrosoftAndroidRuntimeNativeAot is null) { + Context.LogMessage ($"Unable to find {AssemblyName} assembly"); + return; + } + + var module = MicrosoftAndroidRuntimeNativeAot.MainModule; + var type = module.GetType (TypeName); + if (type is null) { + Context.LogMessage ($"Unable to find {TypeName} type"); + return; + } + + var method = type.Methods.FirstOrDefault (m => m.Name == "InitializeTypeMappings"); + if (method is null) { + Context.LogMessage ($"Unable to find {TypeName}.InitializeTypeMappings() method"); + return; + } + + var field = type.Fields.FirstOrDefault (f => f.Name == "TypeMappings"); + if (field is null) { + Context.LogMessage ($"Unable to find {TypeName}.TypeMappings field"); + return; + } + + // Clear IL in method body + method.Body.Instructions.Clear (); + + Context.LogMessage ($"Writing {TypeMappings.Count} typemap entries"); + var il = method.Body.GetILProcessor (); + var addMethod = module.ImportReference (typeof (IDictionary).GetMethod ("Add")); + var getTypeFromHandle = module.ImportReference (typeof (Type).GetMethod ("GetTypeFromHandle")); + foreach (var (javaKey, typeDefinition) in TypeMappings) { + /* + * IL_0000: ldsfld class [System.Runtime]System.Collections.Generic.IDictionary`2 Android.Runtime.NativeAOT.NativeAotTypeManager::TypeMappings + * IL_0005: ldstr "android/app/Activity" + * IL_000a: ldtoken [Mono.Android]Android.App.Activity + * IL_000f: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle) + * IL_0014: callvirt instance void class [System.Runtime]System.Collections.Generic.IDictionary`2::Add(!0, !1) + */ + il.Emit (Mono.Cecil.Cil.OpCodes.Ldsfld, field); + il.Emit (Mono.Cecil.Cil.OpCodes.Ldstr, javaKey); + il.Emit (Mono.Cecil.Cil.OpCodes.Ldtoken, module.ImportReference (typeDefinition)); + il.Emit (Mono.Cecil.Cil.OpCodes.Call, getTypeFromHandle); + il.Emit (Mono.Cecil.Cil.OpCodes.Callvirt, addMethod); + } + + il.Emit (Mono.Cecil.Cil.OpCodes.Ret); + } + + void ProcessType (AssemblyDefinition assembly, TypeDefinition type) + { + if (type.HasJavaPeer (Context)) { + var javaName = JavaNativeTypeManager.ToJniName (type, Context); + if (!TypeMappings.TryAdd (javaName, type)) { + Context.LogMessage ($"Duplicate typemap entry for {javaName}"); + } + } + + if (!type.HasNestedTypes) + return; + + foreach (TypeDefinition nested in type.NestedTypes) + ProcessType (assembly, nested); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets index 6e783fdf500..ecddf43795d 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets @@ -84,11 +84,18 @@ This file contains the .NET 5-specific targets to customize ILLink BeforeStep="MarkStep" Type="MonoDroid.Tuner.FixLegacyResourceDesignerStep" /> + <_TrimmerCustomSteps + Condition=" '$(_AndroidRuntime)' == 'NativeAOT' " + Include="$(_AndroidLinkerCustomStepAssembly)" + AfterStep="CleanStep" + Type="Microsoft.Android.Sdk.ILLink.TypeMappingStep" + /> <_PreserveLists Include="$(MSBuildThisFileDirectory)..\PreserveLists\*.xml" /> + From 870d69bd47d9db98c5a97b077362bbe9cb60c875 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 7 Feb 2025 10:20:54 -0600 Subject: [PATCH 3/7] Change namespace to Microsoft.Android.Runtime --- .../Android.Runtime.NativeAOT/JavaInteropRuntime.cs | 2 +- .../Android.Runtime.NativeAOT/Logging.cs | 2 +- .../Android.Runtime.NativeAOT/NativeAotTypeManager.cs | 2 +- .../Android.Runtime.NativeAOT/NativeAotValueManager.cs | 2 +- .../Java.Interop/JreRuntime.cs | 2 +- .../Microsoft.Android.Runtime.NativeAOT.csproj | 1 + src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs | 4 ++-- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs index aab88256273..43177610294 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs @@ -2,7 +2,7 @@ using Java.Interop; using System.Runtime.InteropServices; -namespace Android.Runtime.NativeAOT; +namespace Microsoft.Android.Runtime; static class JavaInteropRuntime { diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/Logging.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/Logging.cs index 24e84371c72..880e107e72f 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/Logging.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/Logging.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace Android.Runtime.NativeAOT; +namespace Microsoft.Android.Runtime; internal sealed class LogcatTextWriter : TextWriter { diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs index 3a7fb1d6dcc..60e99b7129b 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs @@ -3,7 +3,7 @@ using Java.Interop; using Java.Interop.Tools.TypeNameMappings; -namespace Android.Runtime.NativeAOT; +namespace Microsoft.Android.Runtime; partial class NativeAotTypeManager : JniRuntime.JniTypeManager { diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs index 84e27c4d204..e54d49ca7e4 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs @@ -14,7 +14,7 @@ using Android.Runtime; using Java.Interop; -namespace Android.Runtime.NativeAOT; +namespace Microsoft.Android.Runtime; class NativeAotValueManager : JniRuntime.JniValueManager { diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs index 752fff16078..01c3e9b1ae5 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs @@ -10,7 +10,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Threading; -using Android.Runtime.NativeAOT; +using Microsoft.Android.Runtime; namespace Java.Interop { diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj b/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj index 1c5d5debc2d..5ae08596c98 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj +++ b/src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj @@ -10,6 +10,7 @@ ..\..\product.snk true $(_MonoAndroidNETDefaultOutDir) + Microsoft.Android.Runtime diff --git a/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs b/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs index 9491dd06e87..24197afb8b7 100644 --- a/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs +++ b/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs @@ -15,7 +15,7 @@ namespace Microsoft.Android.Sdk.ILLink; public class TypeMappingStep : BaseStep { const string AssemblyName = "Microsoft.Android.Runtime.NativeAOT"; - const string TypeName = "Android.Runtime.NativeAOT.NativeAotTypeManager"; + const string TypeName = "Microsoft.Android.Runtime.NativeAotTypeManager"; readonly IDictionary TypeMappings = new Dictionary (StringComparer.Ordinal); AssemblyDefinition? MicrosoftAndroidRuntimeNativeAot; @@ -68,7 +68,7 @@ protected override void EndProcess () var getTypeFromHandle = module.ImportReference (typeof (Type).GetMethod ("GetTypeFromHandle")); foreach (var (javaKey, typeDefinition) in TypeMappings) { /* - * IL_0000: ldsfld class [System.Runtime]System.Collections.Generic.IDictionary`2 Android.Runtime.NativeAOT.NativeAotTypeManager::TypeMappings + * IL_0000: ldsfld class [System.Runtime]System.Collections.Generic.IDictionary`2 Microsoft.Android.Runtime.NativeAotTypeManager::TypeMappings * IL_0005: ldstr "android/app/Activity" * IL_000a: ldtoken [Mono.Android]Android.App.Activity * IL_000f: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle) From 579dd2a189c49e3a2b6100267f7e80d34db593a0 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 11 Feb 2025 12:56:10 -0600 Subject: [PATCH 4/7] `TypeMappings` instance field --- .../Android.Runtime.NativeAOT/NativeAotTypeManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs index 60e99b7129b..d214acae6fd 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs @@ -11,7 +11,7 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager { internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; - static readonly IDictionary TypeMappings = new Dictionary (StringComparer.Ordinal); + readonly IDictionary TypeMappings = new Dictionary (StringComparer.Ordinal); public NativeAotTypeManager () { From d989734ef80358108d0cd9ef455ccdfec9e30df1 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 11 Feb 2025 13:05:37 -0600 Subject: [PATCH 5/7] JreRuntimeOptions -> NativeAotRuntimeOptions Remove some unused code as well --- .../JavaInteropRuntime.cs | 2 +- .../Java.Interop/JreRuntime.cs | 96 ++----------------- 2 files changed, 8 insertions(+), 90 deletions(-) diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs index 43177610294..869d82b1cd5 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs @@ -35,7 +35,7 @@ static void init (IntPtr jnienv, IntPtr klass) { try { var typeManager = new NativeAotTypeManager (); - var options = new JreRuntimeOptions { + var options = new NativeAotRuntimeOptions { EnvironmentPointer = jnienv, TypeManager = typeManager, ValueManager = new NativeAotValueManager (typeManager), diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs index 01c3e9b1ae5..630707a17cc 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs @@ -24,41 +24,16 @@ struct JavaVMInitArgs { #pragma warning restore CS0649 } - class JreRuntimeOptions : JniRuntime.CreationOptions { - - internal List Options = new List (); + class NativeAotRuntimeOptions : JniRuntime.CreationOptions { public bool IgnoreUnrecognizedOptions {get; set;} - public Collection ClassPath {get; private set;} - public TextWriter? JniGlobalReferenceLogWriter {get; set;} public TextWriter? JniLocalReferenceLogWriter {get; set;} - internal JvmLibraryHandler? LibraryHandler {get; set;} - - public JreRuntimeOptions () + public NativeAotRuntimeOptions () { JniVersion = JniVersion.v1_2; - ClassPath = new Collection (); - } - - public JreRuntimeOptions AddOption (string option) - { - Options.Add (option); - return this; - } - - public JreRuntimeOptions AddSystemProperty (string name, string value) - { - if (name == null) - throw new ArgumentNullException ("name"); - if (value == null) - throw new ArgumentNullException ("value"); - if (name == "java.class.path") - throw new ArgumentException ("Do not use AddSystemProperty() for the 'java.class.path' property. Add to the ClassPath collection instead.", "name"); - Options.Add (string.Format (CultureInfo.InvariantCulture, "-D{0}={1}", name, value)); - return this; } public JreRuntime CreateJreVM () @@ -73,23 +48,21 @@ static JreRuntime () { } - static JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder) + static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder) { if (builder == null) throw new ArgumentNullException ("builder"); if (builder.InvocationPointer == IntPtr.Zero && builder.EnvironmentPointer == IntPtr.Zero && string.IsNullOrEmpty (builder.JvmLibraryPath)) - throw new InvalidOperationException ($"Member `{nameof (JreRuntimeOptions)}.{nameof (JreRuntimeOptions.JvmLibraryPath)}` must be set."); - - builder.LibraryHandler = JvmLibraryHandler.Create (); + throw new InvalidOperationException ($"Member `{nameof (NativeAotRuntimeOptions)}.{nameof (NativeAotRuntimeOptions.JvmLibraryPath)}` must be set."); #if NET builder.TypeManager ??= new NativeAotTypeManager (); #endif // NET - builder.ValueManager = builder.ValueManager ?? new NativeAotValueManager (builder.TypeManager); - builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter); + builder.ValueManager ??= new NativeAotValueManager (builder.TypeManager); + builder.ObjectReferenceManager ??= new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter); if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero) return builder; @@ -106,12 +79,9 @@ static JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder) return null; } - JvmLibraryHandler LibraryHandler; - - internal protected JreRuntime (JreRuntimeOptions builder) + internal protected JreRuntime (NativeAotRuntimeOptions builder) : base (CreateJreVM (builder)) { - LibraryHandler = builder.LibraryHandler!; } public override string? GetCurrentManagedThreadName () @@ -124,57 +94,5 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f return new StackTrace (skipFrames, fNeedFileInfo) .ToString (); } - - protected override void Dispose (bool disposing) - { - base.Dispose (disposing); - LibraryHandler?.Dispose (); - LibraryHandler = null!; - } - - public new IEnumerable GetAvailableInvocationPointers () - { - return LibraryHandler.GetAvailableInvocationPointers (); - } - } - - internal abstract partial class JvmLibraryHandler : IDisposable { - public abstract void LoadJvmLibrary (string path); - public abstract int CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args); - public abstract IEnumerable GetAvailableInvocationPointers (); - - public abstract void Dispose (); - - public static JvmLibraryHandler Create () - { - var handler = Environment.GetEnvironmentVariable ("JI_LOADER_TYPE"); - switch (handler?.ToLowerInvariant ()) { - case "": - case null: - case "native-library": - return new NativeLibraryJvmLibraryHandler (); - default: - Console.Error.WriteLine ($"Unsupported JI_LOADER_TYPE value of `{handler}`."); - throw new NotSupportedException (); - } - } - } - -#if NET - - class NativeLibraryJvmLibraryHandler : JvmLibraryHandler { - - public override void LoadJvmLibrary (string path) => - throw new NotImplementedException (); - - public override int CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args) => - throw new NotImplementedException (); - - public override IEnumerable GetAvailableInvocationPointers () => - throw new NotImplementedException (); - - public override void Dispose () { } } - -#endif // NET } From 0aef1778adecd168b0d8a55bb9743f17e47957e3 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 11 Feb 2025 15:59:59 -0600 Subject: [PATCH 6/7] Update IL to read instance field Requires `Ldarg_0` on stack, then `Ldfld` instead of `Ldsfld`. --- .../TypeMappingStep.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs b/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs index 24197afb8b7..9b82e500fd8 100644 --- a/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs +++ b/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs @@ -68,13 +68,15 @@ protected override void EndProcess () var getTypeFromHandle = module.ImportReference (typeof (Type).GetMethod ("GetTypeFromHandle")); foreach (var (javaKey, typeDefinition) in TypeMappings) { /* - * IL_0000: ldsfld class [System.Runtime]System.Collections.Generic.IDictionary`2 Microsoft.Android.Runtime.NativeAotTypeManager::TypeMappings - * IL_0005: ldstr "android/app/Activity" - * IL_000a: ldtoken [Mono.Android]Android.App.Activity - * IL_000f: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle) - * IL_0014: callvirt instance void class [System.Runtime]System.Collections.Generic.IDictionary`2::Add(!0, !1) + * IL_0000: ldarg.0 + * IL_0001: ldfld class [System.Runtime]System.Collections.Generic.IDictionary`2 Microsoft.Android.Runtime.NativeAotTypeManager::TypeMappings + * IL_0006: ldstr "java/lang/Object" + * IL_000b: ldtoken [Mono.Android]Java.Lang.Object + * IL_0010: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle) + * IL_0015: callvirt instance void class [System.Runtime]System.Collections.Generic.IDictionary`2::Add(!0, !1) */ - il.Emit (Mono.Cecil.Cil.OpCodes.Ldsfld, field); + il.Emit (Mono.Cecil.Cil.OpCodes.Ldarg_0); + il.Emit (Mono.Cecil.Cil.OpCodes.Ldfld, field); il.Emit (Mono.Cecil.Cil.OpCodes.Ldstr, javaKey); il.Emit (Mono.Cecil.Cil.OpCodes.Ldtoken, module.ImportReference (typeDefinition)); il.Emit (Mono.Cecil.Cil.OpCodes.Call, getTypeFromHandle); From a82417b2f0c5d184b0502f393d6b13d29c454b01 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 12 Feb 2025 09:40:30 -0500 Subject: [PATCH 7/7] How long does InitializeTypeMappings take? This will be of interest if/when we start investigating alternate typemap initialization ideas. --- .../Android.Runtime.NativeAOT/NativeAotTypeManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs index d214acae6fd..b2f864587f3 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotTypeManager.cs @@ -15,8 +15,11 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager { public NativeAotTypeManager () { - AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: NativeAotTypeManager()"); + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"NativeAotTypeManager()"); + var startTicks = global::System.Environment.TickCount; InitializeTypeMappings (); + var endTicks = global::System.Environment.TickCount; + AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"InitializeTypeMappings() took {endTicks - startTicks}ms"); } void InitializeTypeMappings ()