diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs index e1763c9dd7f..d6b7bae9a22 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs @@ -11,7 +11,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// Generates the root _Microsoft.Android.TypeMaps.dll assembly that: /// /// References all per-assembly typemap assemblies via [assembly: TypeMapAssemblyTargetAttribute<__TypeMapAnchor>("name")]. -/// Emits a StartupHook class whose Initialize() method calls +/// Emits a TypeMapLoader class whose Initialize() method calls /// with the appropriate /// type mapping dictionaries. /// @@ -25,26 +25,29 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// [assembly: TypeMapAssemblyTarget<__TypeMapAnchor>("_Mono.Android.TypeMap")] /// [assembly: TypeMapAssemblyTarget<__TypeMapAnchor>("_MyApp.TypeMap")] /// -/// // Startup hook — called by DOTNET_STARTUP_HOOKS: -/// internal static class StartupHook +/// namespace Microsoft.Android.Runtime /// { -/// internal static void Initialize () +/// // Called directly from JNIEnvInit.Initialize(): +/// public static class TypeMapLoader /// { -/// // Option A: Shared universe -/// TrimmableTypeMap.Initialize( -/// TypeMapping.GetOrCreateExternalTypeMapping<Java.Lang.Object>(), -/// TypeMapping.GetOrCreateProxyTypeMapping<Java.Lang.Object>()); +/// public static void Initialize () +/// { +/// // Option A: Shared universe +/// TrimmableTypeMap.Initialize( +/// TypeMapping.GetOrCreateExternalTypeMapping<Java.Lang.Object>(), +/// TypeMapping.GetOrCreateProxyTypeMapping<Java.Lang.Object>()); /// -/// // Option B: Per-assembly universes (aggregated) -/// var typeMaps = new IReadOnlyDictionary<string, Type>[] { -/// TypeMapping.GetOrCreateExternalTypeMapping<_Mono_Android_TypeMap.__TypeMapAnchor>(), -/// TypeMapping.GetOrCreateExternalTypeMapping<_MyApp_TypeMap.__TypeMapAnchor>(), -/// }; -/// var proxyMaps = new IReadOnlyDictionary<Type, Type>[] { -/// TypeMapping.GetOrCreateProxyTypeMapping<_Mono_Android_TypeMap.__TypeMapAnchor>(), -/// TypeMapping.GetOrCreateProxyTypeMapping<_MyApp_TypeMap.__TypeMapAnchor>(), -/// }; -/// TrimmableTypeMap.Initialize(typeMaps, proxyMaps); +/// // Option B: Per-assembly universes (aggregated) +/// var typeMaps = new IReadOnlyDictionary<string, Type>[] { +/// TypeMapping.GetOrCreateExternalTypeMapping<_Mono_Android_TypeMap.__TypeMapAnchor>(), +/// TypeMapping.GetOrCreateExternalTypeMapping<_MyApp_TypeMap.__TypeMapAnchor>(), +/// }; +/// var proxyMaps = new IReadOnlyDictionary<Type, Type>[] { +/// TypeMapping.GetOrCreateProxyTypeMapping<_Mono_Android_TypeMap.__TypeMapAnchor>(), +/// TypeMapping.GetOrCreateProxyTypeMapping<_MyApp_TypeMap.__TypeMapAnchor>(), +/// }; +/// TrimmableTypeMap.Initialize(typeMaps, proxyMaps); +/// } /// } /// } /// @@ -108,8 +111,8 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSha // Emit [assembly: TypeMapAssemblyTargetAttribute<__TypeMapAnchor>("name")] for each per-assembly typemap EmitAssemblyTargetAttributes (pe, anchorTypeHandle, perAssemblyTypeMapNames); - // Emit [assembly: IgnoresAccessChecksTo("...")] so the startup hook can access - // internal types (SingleUniverseTypeMap, AggregateTypeMap, TrimmableTypeMap in Mono.Android, + // Emit [assembly: IgnoresAccessChecksTo("...")] so TypeMapLoader.Initialize() can access + // internal types (SingleUniverseTypeMap, AggregateTypeMap in Mono.Android, // and __TypeMapAnchor in each per-assembly typemap DLL). var accessTargets = new List { "Mono.Android" }; if (!useSharedTypemapUniverse) { @@ -117,8 +120,8 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSha } pe.EmitIgnoresAccessChecksToAttribute (accessTargets); - // Emit StartupHook class with Initialize() method - EmitStartupHook (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse); + // Emit TypeMapLoader class with Initialize() method + EmitTypeMapLoader (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse); pe.WritePE (stream); } @@ -142,7 +145,7 @@ static void EmitAssemblyTargetAttributes (PEAssemblyBuilder pe, EntityHandle anc } } - static void EmitStartupHook (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse) + static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse) { var metadata = pe.Metadata; @@ -167,11 +170,11 @@ static void EmitStartupHook (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle var getProxyMemberRef = AddTypeMappingMethodRef (pe, typeMappingRef, "GetOrCreateProxyTypeMapping", iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); - // Define the StartupHook type + // Define the TypeMapLoader type (public static class in Microsoft.Android.Runtime namespace) metadata.AddTypeDefinition ( - TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.Class, - default, - metadata.GetOrAddString ("StartupHook"), + TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.Class, + metadata.GetOrAddString ("Microsoft.Android.Runtime"), + metadata.GetOrAddString ("TypeMapLoader"), metadata.AddTypeReference (pe.SystemRuntimeRef, metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Object")), MetadataTokens.FieldDefinitionHandle (metadata.GetRowCount (TableIndex.Field) + 1), diff --git a/src/Microsoft.Android.TypeMaps.Ref/Microsoft.Android.TypeMaps.Ref.csproj b/src/Microsoft.Android.TypeMaps.Ref/Microsoft.Android.TypeMaps.Ref.csproj new file mode 100644 index 00000000000..a9830cdb95a --- /dev/null +++ b/src/Microsoft.Android.TypeMaps.Ref/Microsoft.Android.TypeMaps.Ref.csproj @@ -0,0 +1,16 @@ + + + + + $(DotNetTargetFramework) + _Microsoft.Android.TypeMaps + Microsoft.Android.Runtime + enable + + true + + + + diff --git a/src/Microsoft.Android.TypeMaps.Ref/TypeMapLoader.cs b/src/Microsoft.Android.TypeMaps.Ref/TypeMapLoader.cs new file mode 100644 index 00000000000..8060cdbebe8 --- /dev/null +++ b/src/Microsoft.Android.TypeMaps.Ref/TypeMapLoader.cs @@ -0,0 +1,17 @@ +using System; + +namespace Microsoft.Android.Runtime; + +/// +/// Entry point for typemap initialization. The implementation is generated at build time +/// by the trimmable typemap generator, replacing this reference assembly. +/// +public static class TypeMapLoader +{ + /// + /// Initializes the trimmable typemap by constructing the type mapping dictionaries + /// and calling TrimmableTypeMap.Initialize(). + /// + public static void Initialize () => throw new NotImplementedException ( + "This is a reference assembly stub. The real implementation is generated at build time."); +} diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 52677116363..f59a8568f66 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -179,6 +179,9 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) if (!RuntimeFeature.TrimmableTypeMap) { args->registerJniNativesFn = (IntPtr)(delegate* unmanaged)&RegisterJniNatives; } + if (RuntimeFeature.TrimmableTypeMap) { + InitializeTrimmableTypeMap (); + } RunStartupHooksIfNeeded (); SetSynchronizationContext (); } @@ -187,6 +190,14 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) [UnmanagedCallConv (CallConvs = new[] { typeof (CallConvCdecl) })] private static unsafe partial void xamarin_app_init (IntPtr env, delegate* unmanaged get_function_pointer); + // Separate method so the JIT doesn't try to resolve TypeMapLoader (from _Microsoft.Android.TypeMaps.dll) + // when compiling JNIEnvInit.Initialize() in non-trimmable builds where that assembly isn't present. + [MethodImpl (MethodImplOptions.NoInlining)] + static void InitializeTrimmableTypeMap () + { + TypeMapLoader.Initialize (); + } + static void RunStartupHooksIfNeeded () { // Return if startup hooks are disabled or not CoreCLR diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 2272fc1d9dc..7b94c8f2184 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -17,7 +17,7 @@ namespace Microsoft.Android.Runtime; /// and provides peer creation, invoker resolution, container factories, and native /// method registration. All proxy attribute access is encapsulated here. /// -class TrimmableTypeMap +public class TrimmableTypeMap { static readonly Lock s_initLock = new (); static readonly JavaPeerProxy s_noPeerSentinel = new MissingJavaPeerProxy (); @@ -38,10 +38,10 @@ class TrimmableTypeMap /// /// Initializes the singleton with a single merged typemap universe. - /// Called from the startup hook in the generated root assembly (_Microsoft.Android.TypeMaps) - /// when assembly typemaps are merged (Release builds). + /// Called from in the generated root assembly + /// (_Microsoft.Android.TypeMaps) when assembly typemaps are merged (Release builds). /// - internal static void Initialize (IReadOnlyDictionary typeMap, IReadOnlyDictionary proxyMap) + public static void Initialize (IReadOnlyDictionary typeMap, IReadOnlyDictionary proxyMap) { ArgumentNullException.ThrowIfNull (typeMap); ArgumentNullException.ThrowIfNull (proxyMap); @@ -50,10 +50,10 @@ internal static void Initialize (IReadOnlyDictionary typeMap, IRea /// /// Initializes the singleton with multiple per-assembly typemap universes. - /// Called from the startup hook in the generated root assembly (_Microsoft.Android.TypeMaps) - /// when each assembly has its own typemap universe (Debug builds). + /// Called from in the generated root assembly + /// (_Microsoft.Android.TypeMaps) when each assembly has its own typemap universe (Debug builds). /// - internal static void Initialize (IReadOnlyDictionary[] typeMaps, IReadOnlyDictionary[] proxyMaps) + public static void Initialize (IReadOnlyDictionary[] typeMaps, IReadOnlyDictionary[] proxyMaps) { ArgumentNullException.ThrowIfNull (typeMaps); ArgumentNullException.ThrowIfNull (proxyMaps); diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 55352540d70..89bf52ac3b1 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -61,6 +61,12 @@ + + + false + all + ILLink.LinkAttributes.xml diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets index 4156e0db1b5..7654d9bf248 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets @@ -43,7 +43,7 @@ See: https://github.com/dotnet/sdk/pull/52581 diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets index 40a2c1519c3..52ce49be5cf 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets @@ -11,9 +11,6 @@ <_TypeMapAssemblyName>_Microsoft.Android.TypeMaps - - true @@ -34,28 +31,6 @@ Condition=" '$(_AndroidRuntime)' == 'CoreCLR' " /> - - - - <_ExistingStartupHooks Include="@(RuntimeEnvironmentVariable)" Condition=" '%(Identity)' == 'DOTNET_STARTUP_HOOKS' " /> - - - - <_ExistingStartupHooks Remove="@(_ExistingStartupHooks)" /> - - - diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs index 0acd64e1812..84dd6a0a2f4 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs @@ -139,11 +139,13 @@ public void Generate_BothMergeModes_ProduceValidPEAssembly (bool useSharedTypema var reader = pe.GetMetadataReader (); - // Both modes should have StartupHook type with Initialize method + // Both modes should have TypeMapLoader type in the correct namespace, with public visibility and Initialize method var typeDefs = reader.TypeDefinitions .Select (h => reader.GetTypeDefinition (h)) .ToList (); - Assert.Contains (typeDefs, t => reader.GetString (t.Name) == "StartupHook"); + var typeMapLoader = typeDefs.Single (t => reader.GetString (t.Name) == "TypeMapLoader"); + Assert.Equal ("Microsoft.Android.Runtime", reader.GetString (typeMapLoader.Namespace)); + Assert.True (typeMapLoader.Attributes.HasFlag (System.Reflection.TypeAttributes.Public)); // Both modes should have assembly target attributes var targetAttrs = GetTypeMapAssemblyTargetAttributes (reader);