diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs
index 01f29e322c1..e1763c9dd7f 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs
@@ -1,22 +1,52 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
-/// Generates the root _Microsoft.Android.TypeMaps.dll assembly that references
-/// all per-assembly typemap assemblies via
-/// [assembly: TypeMapAssemblyTargetAttribute<Java.Lang.Object>("name")].
+/// 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
+/// with the appropriate
+/// type mapping dictionaries.
+///
///
///
/// The generated assembly looks like this (pseudo-C#):
///
-/// // One attribute per per-assembly typemap assembly — tells the runtime where to find TypeMap entries:
-/// [assembly: TypeMapAssemblyTarget<Java.Lang.Object>("_Mono.Android.TypeMap")]
-/// [assembly: TypeMapAssemblyTarget<Java.Lang.Object>("_MyApp.TypeMap")]
+/// internal class __TypeMapAnchor { }
+///
+/// // One attribute per per-assembly typemap assembly:
+/// [assembly: TypeMapAssemblyTarget<__TypeMapAnchor>("_Mono.Android.TypeMap")]
+/// [assembly: TypeMapAssemblyTarget<__TypeMapAnchor>("_MyApp.TypeMap")]
+///
+/// // Startup hook — called by DOTNET_STARTUP_HOOKS:
+/// internal static class StartupHook
+/// {
+/// internal 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);
+/// }
+/// }
///
///
public sealed class RootTypeMapAssemblyGenerator
@@ -35,10 +65,11 @@ public RootTypeMapAssemblyGenerator (Version systemRuntimeVersion)
/// Generates the root typemap assembly and writes it to the given stream.
///
/// Names of per-assembly typemap assemblies to reference.
+ /// True to merge all assemblies into a single typemap universe, false for per-assembly universes.
/// Stream to write the output PE to.
/// Optional assembly name (defaults to _Microsoft.Android.TypeMaps).
/// Optional module name for the PE metadata.
- public void Generate (IReadOnlyList perAssemblyTypeMapNames, Stream stream, string? assemblyName = null, string? moduleName = null)
+ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null)
{
if (perAssemblyTypeMapNames is null) {
throw new ArgumentNullException (nameof (perAssemblyTypeMapNames));
@@ -53,30 +84,298 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, Stream stre
var pe = new PEAssemblyBuilder (_systemRuntimeVersion);
pe.EmitPreamble (assemblyName, moduleName);
- // Reference the open generic TypeMapAssemblyTargetAttribute`1 from System.Runtime.InteropServices
+ EntityHandle anchorTypeHandle;
+ if (useSharedTypemapUniverse) {
+ // In merged mode, all per-assembly typemaps use Java.Lang.Object as the shared
+ // anchor type, so the root assembly must also use Java.Lang.Object.
+ anchorTypeHandle = pe.Metadata.AddTypeReference (pe.MonoAndroidRef,
+ pe.Metadata.GetOrAddString ("Java.Lang"),
+ pe.Metadata.GetOrAddString ("Object"));
+ } else {
+ // In aggregate mode, each per-assembly typemap has its own __TypeMapAnchor.
+ // The root also defines its own for TypeMapAssemblyTargetAttribute grouping.
+ var objectRef = pe.Metadata.AddTypeReference (pe.SystemRuntimeRef,
+ pe.Metadata.GetOrAddString ("System"), pe.Metadata.GetOrAddString ("Object"));
+ anchorTypeHandle = pe.Metadata.AddTypeDefinition (
+ TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class,
+ default,
+ pe.Metadata.GetOrAddString ("__TypeMapAnchor"),
+ objectRef,
+ MetadataTokens.FieldDefinitionHandle (pe.Metadata.GetRowCount (TableIndex.Field) + 1),
+ MetadataTokens.MethodDefinitionHandle (pe.Metadata.GetRowCount (TableIndex.MethodDef) + 1));
+ }
+
+ // 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,
+ // and __TypeMapAnchor in each per-assembly typemap DLL).
+ var accessTargets = new List { "Mono.Android" };
+ if (!useSharedTypemapUniverse) {
+ accessTargets.AddRange (perAssemblyTypeMapNames);
+ }
+ pe.EmitIgnoresAccessChecksToAttribute (accessTargets);
+
+ // Emit StartupHook class with Initialize() method
+ EmitStartupHook (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse);
+
+ pe.WritePE (stream);
+ }
+
+ static void EmitAssemblyTargetAttributes (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames)
+ {
var openAttrRef = pe.Metadata.AddTypeReference (pe.SystemRuntimeInteropServicesRef,
pe.Metadata.GetOrAddString ("System.Runtime.InteropServices"),
pe.Metadata.GetOrAddString ("TypeMapAssemblyTargetAttribute`1"));
- // Reference Java.Lang.Object from Mono.Android (the type universe)
- var javaLangObjectRef = pe.Metadata.AddTypeReference (pe.MonoAndroidRef,
- pe.Metadata.GetOrAddString ("Java.Lang"), pe.Metadata.GetOrAddString ("Object"));
-
- // Build TypeSpec for TypeMapAssemblyTargetAttribute
- var closedAttrTypeSpec = pe.MakeGenericTypeSpec (openAttrRef, javaLangObjectRef);
+ var closedAttrTypeSpec = pe.MakeGenericTypeSpec (openAttrRef, anchorTypeHandle);
- // MemberRef for .ctor(string) on the closed generic type
var ctorRef = pe.AddMemberRef (closedAttrTypeSpec, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1,
rt => rt.Void (),
p => p.AddParameter ().Type ().String ()));
- // Add [assembly: TypeMapAssemblyTargetAttribute("name")] for each per-assembly typemap
foreach (var name in perAssemblyTypeMapNames) {
var blobHandle = pe.BuildAttributeBlob (blob => blob.WriteSerializedString (name));
pe.Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, ctorRef, blobHandle);
}
+ }
- pe.WritePE (stream);
+ static void EmitStartupHook (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse)
+ {
+ var metadata = pe.Metadata;
+
+ // Type references
+ var iReadOnlyDictOpenRef = metadata.AddTypeReference (pe.SystemRuntimeRef,
+ metadata.GetOrAddString ("System.Collections.Generic"), metadata.GetOrAddString ("IReadOnlyDictionary`2"));
+ var systemTypeRef = metadata.AddTypeReference (pe.SystemRuntimeRef,
+ metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Type"));
+
+ var trimmableTypeMapRef = metadata.AddTypeReference (pe.MonoAndroidRef,
+ metadata.GetOrAddString ("Microsoft.Android.Runtime"), metadata.GetOrAddString ("TrimmableTypeMap"));
+
+ var typeMappingRef = metadata.AddTypeReference (pe.SystemRuntimeInteropServicesRef,
+ metadata.GetOrAddString ("System.Runtime.InteropServices"), metadata.GetOrAddString ("TypeMapping"));
+
+ // Build MemberRefs for TypeMapping methods using manual signature encoding
+ // TypeMapping.GetOrCreateExternalTypeMapping() returns IReadOnlyDictionary
+ var getExternalMemberRef = AddTypeMappingMethodRef (pe, typeMappingRef, "GetOrCreateExternalTypeMapping",
+ iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
+
+ // TypeMapping.GetOrCreateProxyTypeMapping() returns IReadOnlyDictionary
+ var getProxyMemberRef = AddTypeMappingMethodRef (pe, typeMappingRef, "GetOrCreateProxyTypeMapping",
+ iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
+
+ // Define the StartupHook type
+ metadata.AddTypeDefinition (
+ TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.Class,
+ default,
+ metadata.GetOrAddString ("StartupHook"),
+ metadata.AddTypeReference (pe.SystemRuntimeRef,
+ metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Object")),
+ MetadataTokens.FieldDefinitionHandle (metadata.GetRowCount (TableIndex.Field) + 1),
+ MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1));
+
+ if (useSharedTypemapUniverse) {
+ // TrimmableTypeMap.Initialize(IReadOnlyDictionary, IReadOnlyDictionary)
+ var initializeRef = AddInitializeSingleRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef);
+ EmitInitializeWithSingleTypeMap (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, initializeRef);
+ } else {
+ // TrimmableTypeMap.Initialize(IReadOnlyDictionary[], IReadOnlyDictionary[])
+ var initializeRef = AddInitializeAggregateRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef);
+ var externalDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
+ var proxyDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
+ EmitInitializeWithAggregateTypeMap (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef);
+ }
+ }
+
+ static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle,
+ MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef,
+ MemberReferenceHandle initializeRef)
+ {
+ var getExternalSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, anchorTypeHandle);
+ var getProxySpec = MakeGenericMethodSpec (pe, getProxyMemberRef, anchorTypeHandle);
+
+ pe.EmitBody ("Initialize",
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
+ sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }),
+ encoder => {
+ // TypeMapping.GetOrCreateExternalTypeMapping<__TypeMapAnchor>()
+ encoder.OpCode (ILOpCode.Call);
+ encoder.Token (getExternalSpec);
+ // TypeMapping.GetOrCreateProxyTypeMapping<__TypeMapAnchor>()
+ encoder.OpCode (ILOpCode.Call);
+ encoder.Token (getProxySpec);
+ // TrimmableTypeMap.Initialize(typeMap, proxyMap)
+ encoder.OpCode (ILOpCode.Call);
+ encoder.Token (initializeRef);
+ encoder.OpCode (ILOpCode.Ret);
+ });
+ }
+
+ static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe,
+ IReadOnlyList perAssemblyTypeMapNames,
+ MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef,
+ MemberReferenceHandle initializeRef,
+ TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle proxyDictTypeSpec,
+ TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef)
+ {
+ var count = perAssemblyTypeMapNames.Count;
+
+ var getExternalSpecs = new EntityHandle [count];
+ var getProxySpecs = new EntityHandle [count];
+ for (int i = 0; i < count; i++) {
+ var asmRef = pe.FindOrAddAssemblyRef (perAssemblyTypeMapNames [i]);
+ var perAsmAnchorRef = pe.Metadata.AddTypeReference (asmRef,
+ default, pe.Metadata.GetOrAddString ("__TypeMapAnchor"));
+ getExternalSpecs [i] = MakeGenericMethodSpec (pe, getExternalMemberRef, perAsmAnchorRef);
+ getProxySpecs [i] = MakeGenericMethodSpec (pe, getProxyMemberRef, perAsmAnchorRef);
+ }
+
+ pe.EmitBody ("Initialize",
+ MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
+ sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }),
+ encoder => {
+ // var typeMaps = new IReadOnlyDictionary[N];
+ encoder.LoadConstantI4 (count);
+ encoder.OpCode (ILOpCode.Newarr);
+ encoder.Token (externalDictTypeSpec);
+ encoder.OpCode (ILOpCode.Stloc_0);
+
+ for (int i = 0; i < count; i++) {
+ encoder.OpCode (ILOpCode.Ldloc_0);
+ encoder.LoadConstantI4 (i);
+ encoder.OpCode (ILOpCode.Call);
+ encoder.Token (getExternalSpecs [i]);
+ encoder.OpCode (ILOpCode.Stelem_ref);
+ }
+
+ // var proxyMaps = new IReadOnlyDictionary[N];
+ encoder.LoadConstantI4 (count);
+ encoder.OpCode (ILOpCode.Newarr);
+ encoder.Token (proxyDictTypeSpec);
+ encoder.OpCode (ILOpCode.Stloc_1);
+
+ for (int i = 0; i < count; i++) {
+ encoder.OpCode (ILOpCode.Ldloc_1);
+ encoder.LoadConstantI4 (i);
+ encoder.OpCode (ILOpCode.Call);
+ encoder.Token (getProxySpecs [i]);
+ encoder.OpCode (ILOpCode.Stelem_ref);
+ }
+
+ // TrimmableTypeMap.Initialize(typeMaps, proxyMaps)
+ encoder.OpCode (ILOpCode.Ldloc_0);
+ encoder.OpCode (ILOpCode.Ldloc_1);
+ encoder.OpCode (ILOpCode.Call);
+ encoder.Token (initializeRef);
+ encoder.OpCode (ILOpCode.Ret);
+ },
+ encodeLocals: localsSig => {
+ // LOCAL_SIG header + 2 locals
+ localsSig.WriteByte (0x07); // LOCAL_SIG
+ localsSig.WriteCompressedInteger (2); // count
+ // local 0: IReadOnlyDictionary[]
+ localsSig.WriteByte (0x1D); // SZARRAY
+ EncodeIReadOnlyDictType (localsSig, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
+ // local 1: IReadOnlyDictionary[]
+ localsSig.WriteByte (0x1D); // SZARRAY
+ EncodeIReadOnlyDictType (localsSig, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
+ });
+ }
+
+ ///
+ /// Creates a MethodSpec for a generic method instantiation with a specific type argument.
+ ///
+ static MethodSpecificationHandle MakeGenericMethodSpec (PEAssemblyBuilder pe, MemberReferenceHandle openMethodRef, EntityHandle typeArg)
+ {
+ var blob = new BlobBuilder (16);
+ blob.WriteByte (0x0A); // GENMETHOD_INST
+ blob.WriteCompressedInteger (1); // generic arity
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeArg));
+ return pe.Metadata.AddMethodSpecification (openMethodRef, pe.Metadata.GetOrAddBlob (blob));
+ }
+
+ ///
+ /// Creates a MemberRef for a TypeMapping generic method with the correct return type signature.
+ /// The method signature is: generic arity 1, 0 params, returns IReadOnlyDictionary<K, V>.
+ ///
+ static MemberReferenceHandle AddTypeMappingMethodRef (PEAssemblyBuilder pe, TypeReferenceHandle typeMappingRef, string methodName,
+ TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, bool keyIsString)
+ {
+ var blob = new BlobBuilder (64);
+ // Method signature: GENERIC, arity=1, param count=0, return type
+ blob.WriteByte (0x10); // IMAGE_CEE_CS_CALLCONV_GENERIC
+ blob.WriteCompressedInteger (1); // generic parameter count
+ blob.WriteCompressedInteger (0); // parameter count
+ // Return type: IReadOnlyDictionary
+ EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString);
+ return pe.Metadata.AddMemberReference (typeMappingRef,
+ pe.Metadata.GetOrAddString (methodName), pe.Metadata.GetOrAddBlob (blob));
+ }
+
+ ///
+ /// Creates a MemberRef for TrimmableTypeMap.Initialize(IReadOnlyDictionary<string, Type>, IReadOnlyDictionary<Type, Type>).
+ ///
+ static MemberReferenceHandle AddInitializeSingleRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef,
+ TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef)
+ {
+ var blob = new BlobBuilder (64);
+ blob.WriteByte (0x00); // DEFAULT (static)
+ blob.WriteCompressedInteger (2); // parameter count
+ blob.WriteByte (0x01); // return type: void
+ EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
+ EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
+ return pe.Metadata.AddMemberReference (trimmableTypeMapRef,
+ pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob));
+ }
+
+ ///
+ /// Creates a MemberRef for TrimmableTypeMap.Initialize(IReadOnlyDictionary<string, Type>[], IReadOnlyDictionary<Type, Type>[]).
+ ///
+ static MemberReferenceHandle AddInitializeAggregateRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef,
+ TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef)
+ {
+ var blob = new BlobBuilder (64);
+ blob.WriteByte (0x00); // DEFAULT (static)
+ blob.WriteCompressedInteger (2); // parameter count
+ blob.WriteByte (0x01); // return type: void
+ // Param 1: IReadOnlyDictionary[]
+ blob.WriteByte (0x1D); // SZARRAY
+ EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
+ // Param 2: IReadOnlyDictionary[]
+ blob.WriteByte (0x1D); // SZARRAY
+ EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
+ return pe.Metadata.AddMemberReference (trimmableTypeMapRef,
+ pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob));
+ }
+
+ ///
+ /// Creates a TypeSpec for a closed IReadOnlyDictionary<K, V> generic type (for newarr).
+ ///
+ static TypeSpecificationHandle MakeIReadOnlyDictTypeSpec (PEAssemblyBuilder pe,
+ TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, bool keyIsString)
+ {
+ var blob = new BlobBuilder (32);
+ EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString);
+ return pe.Metadata.AddTypeSpecification (pe.Metadata.GetOrAddBlob (blob));
+ }
+
+ static void EncodeIReadOnlyDictType (BlobBuilder blob, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, bool keyIsString)
+ {
+ blob.WriteByte (0x15); // ELEMENT_TYPE_GENERICINST
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (iReadOnlyDictOpenRef));
+ blob.WriteCompressedInteger (2); // generic arity = 2
+ if (keyIsString) {
+ blob.WriteByte (0x0E); // ELEMENT_TYPE_STRING
+ } else {
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (systemTypeRef));
+ }
+ blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS
+ blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (systemTypeRef));
}
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index 9bb04468b6c..59c01a00bab 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -14,10 +14,12 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
/// The generated assembly looks like this (pseudo-C#):
///
-/// // Assembly-level TypeMap attributes — one per Java peer type:
-/// [assembly: TypeMap<Java.Lang.Object>("android/app/Activity", typeof(Activity_Proxy))] // unconditional (ACW)
-/// [assembly: TypeMap<Java.Lang.Object>("android/widget/TextView", typeof(TextView_Proxy), typeof(TextView))] // trimmable (MCW)
-/// [assembly: TypeMapAssociation<Java.Lang.Object>(typeof(MyTextView), typeof(Android_Widget_TextView_Proxy))] // managed → proxy
+/// // Assembly-level TypeMap attributes — one per Java peer type.
+/// // The anchor type T is Java.Lang.Object in merged mode (Release) or
+/// // a per-assembly __TypeMapAnchor in per-assembly mode (Debug):
+/// [assembly: TypeMap<T>("android/app/Activity", typeof(Activity_Proxy))] // unconditional (ACW)
+/// [assembly: TypeMap<T>("android/widget/TextView", typeof(TextView_Proxy), typeof(TextView))] // trimmable (MCW)
+/// [assembly: TypeMapAssociation<T>(typeof(MyTextView), typeof(Android_Widget_TextView_Proxy))] // managed → proxy
///
/// // One proxy type per Java peer that needs activation or UCO wrappers:
/// public sealed class Activity_Proxy : JavaPeerProxy<Activity>, IAndroidCallableWrapper // IAndroidCallableWrapper for ACWs only
@@ -107,6 +109,8 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _jniEnvTypesRegisterNativesRef;
MemberReferenceHandle _readOnlySpanOfJniNativeMethodCtorRef;
+ EntityHandle _anchorTypeHandle;
+
///
/// Creates a new emitter.
///
@@ -123,7 +127,11 @@ public TypeMapAssemblyEmitter (Version systemRuntimeVersion)
///
/// Emits a PE assembly from the given model and writes it to .
///
- public void Emit (TypeMapAssemblyData model, Stream stream)
+ ///
+ /// When true, uses Java.Lang.Object as the shared anchor type so all assemblies
+ /// share a single typemap universe. When false, emits a per-assembly __TypeMapAnchor.
+ ///
+ public void Emit (TypeMapAssemblyData model, Stream stream, bool useSharedTypemapUniverse = false)
{
if (model is null) {
throw new ArgumentNullException (nameof (model));
@@ -132,17 +140,26 @@ public void Emit (TypeMapAssemblyData model, Stream stream)
throw new ArgumentNullException (nameof (stream));
}
- EmitCore (model);
+ EmitCore (model, useSharedTypemapUniverse);
_pe.WritePE (stream);
}
- void EmitCore (TypeMapAssemblyData model)
+ void EmitCore (TypeMapAssemblyData model, bool useSharedTypemapUniverse)
{
_pe.EmitPreamble (model.AssemblyName, model.ModuleName, MetadataHelper.ComputeContentFingerprint (model));
_javaInteropRef = _pe.AddAssemblyRef ("Java.Interop", new Version (0, 0, 0, 0));
EmitTypeReferences ();
+ if (useSharedTypemapUniverse) {
+ // Use Java.Lang.Object as the shared anchor so all assemblies share a single
+ // typemap universe that can be merged at startup.
+ _anchorTypeHandle = _pe.Metadata.AddTypeReference (_pe.MonoAndroidRef,
+ _pe.Metadata.GetOrAddString ("Java.Lang"),
+ _pe.Metadata.GetOrAddString ("Object"));
+ } else {
+ EmitAnchorType ();
+ }
EmitMemberReferences ();
// Track wrapper method names → handles for RegisterNatives
@@ -212,6 +229,26 @@ void EmitTypeReferences ()
_readOnlySpanOfJniNativeMethodSpec = MakeGenericTypeSpec_ValueType (_readOnlySpanOpenRef, _jniNativeMethodRef);
}
+ ///
+ /// Emits an internal __TypeMapAnchor class used as the group type parameter
+ /// for TypeMap<T> and TypeMapAssociation<T>. Each per-assembly
+ /// typemap DLL gets its own anchor, creating an isolated typemap universe.
+ ///
+ void EmitAnchorType ()
+ {
+ var metadata = _pe.Metadata;
+ var objectRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
+ metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Object"));
+
+ _anchorTypeHandle = metadata.AddTypeDefinition (
+ TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class,
+ default,
+ metadata.GetOrAddString ("__TypeMapAnchor"),
+ objectRef,
+ MetadataTokens.FieldDefinitionHandle (metadata.GetRowCount (TableIndex.Field) + 1),
+ MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1));
+ }
+
void EmitMemberReferences ()
{
_getTypeFromHandleRef = _pe.AddMemberRef (_systemTypeRef, "GetTypeFromHandle",
@@ -306,10 +343,8 @@ void EmitTypeMapAttributeCtorRef ()
var typeMapAttrOpenRef = metadata.AddTypeReference (_pe.SystemRuntimeInteropServicesRef,
metadata.GetOrAddString ("System.Runtime.InteropServices"),
metadata.GetOrAddString ("TypeMapAttribute`1"));
- var javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
- metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object"));
- var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAttrOpenRef, javaLangObjectRef);
+ var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAttrOpenRef, _anchorTypeHandle);
// 2-arg: TypeMap(string jniName, Type proxyType) — unconditional
_typeMapAttrCtorRef2Arg = _pe.AddMemberRef (closedAttrTypeSpec, ".ctor",
@@ -337,9 +372,7 @@ void EmitTypeMapAssociationAttributeCtorRef ()
var typeMapAssociationAttrOpenRef = metadata.AddTypeReference (_pe.SystemRuntimeInteropServicesRef,
metadata.GetOrAddString ("System.Runtime.InteropServices"),
metadata.GetOrAddString ("TypeMapAssociationAttribute`1"));
- var javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
- metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object"));
- var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAssociationAttrOpenRef, javaLangObjectRef);
+ var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAssociationAttrOpenRef, _anchorTypeHandle);
_typeMapAssociationAttrCtorRef = _pe.AddMemberRef (closedAttrTypeSpec, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2,
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs
index 2cb92963816..939689a43a5 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs
@@ -24,10 +24,13 @@ public TypeMapAssemblyGenerator (Version systemRuntimeVersion)
/// Scanned Java peer types.
/// Stream to write the output PE assembly to.
/// Assembly name for the generated assembly.
- public void Generate (IReadOnlyList peers, Stream stream, string assemblyName)
+ ///
+ /// When true, uses Java.Lang.Object as the shared anchor type. When false, emits a per-assembly anchor.
+ ///
+ public void Generate (IReadOnlyList peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false)
{
var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName);
var emitter = new TypeMapAssemblyEmitter (_systemRuntimeVersion);
- emitter.Emit (model, stream);
+ emitter.Emit (model, stream, useSharedTypemapUniverse);
}
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
index 4970d59dc00..5d14490503a 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
@@ -25,6 +25,7 @@ public TrimmableTypeMapResult Execute (
IReadOnlyList<(string Name, PEReader Reader)> assemblies,
Version systemRuntimeVersion,
HashSet frameworkAssemblyNames,
+ bool useSharedTypemapUniverse = false,
ManifestConfig? manifestConfig = null,
XDocument? manifestTemplate = null)
{
@@ -41,7 +42,7 @@ public TrimmableTypeMapResult Execute (
RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig));
PropagateDeferredRegistrationToBaseClasses (allPeers);
- var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion);
+ var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse);
var jcwPeers = allPeers.Where (p =>
!frameworkAssemblyNames.Contains (p.AssemblyName)
|| p.JavaName.StartsWith ("mono/", StringComparison.Ordinal)).ToList ();
@@ -112,7 +113,7 @@ GeneratedManifest GenerateManifest (List allPeers, AssemblyManifes
return (peers, manifestInfo);
}
- List GenerateTypeMapAssemblies (List allPeers, Version systemRuntimeVersion)
+ List GenerateTypeMapAssemblies (List allPeers, Version systemRuntimeVersion, bool useSharedTypemapUniverse)
{
var peersByAssembly = allPeers.GroupBy (p => p.AssemblyName, StringComparer.Ordinal).OrderBy (g => g.Key, StringComparer.Ordinal);
var generatedAssemblies = new List ();
@@ -123,14 +124,14 @@ List GenerateTypeMapAssemblies (List allPeers,
perAssemblyNames.Add (assemblyName);
var peers = group.ToList ();
var stream = new MemoryStream ();
- generator.Generate (peers, stream, assemblyName);
+ generator.Generate (peers, stream, assemblyName, useSharedTypemapUniverse);
stream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly (assemblyName, stream));
logger.LogGeneratedTypeMapAssemblyInfo (assemblyName, peers.Count);
}
var rootStream = new MemoryStream ();
var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
- rootGenerator.Generate (perAssemblyNames, rootStream);
+ rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream);
rootStream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream));
logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count);
diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
index 8bb98c4a993..52677116363 100644
--- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
+++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
@@ -162,10 +162,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
);
JniRuntime.SetCurrent (androidRuntime);
- if (RuntimeFeature.TrimmableTypeMap) {
- TrimmableTypeMap.Initialize ();
- }
-
grefIGCUserPeer_class = args->grefIGCUserPeer;
grefGCUserPeerable_class = args->grefGCUserPeerable;
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs
new file mode 100644
index 00000000000..83925abd7c6
--- /dev/null
+++ b/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs
@@ -0,0 +1,44 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Android.Runtime;
+
+///
+/// Wraps N instances and flattens
+/// results across all universes. Debug-only — each assembly has its own
+/// universe with an isolated TypeMapLazyDictionary.
+///
+sealed class AggregateTypeMap : ITypeMapWithAliasing
+{
+ readonly SingleUniverseTypeMap[] _universes;
+
+ public AggregateTypeMap (SingleUniverseTypeMap[] universes)
+ {
+ ArgumentNullException.ThrowIfNull (universes);
+ _universes = universes;
+ }
+
+ public IEnumerable GetTypes (string jniName)
+ {
+ foreach (var universe in _universes) {
+ foreach (var type in universe.GetTypes (jniName)) {
+ yield return type;
+ }
+ }
+ }
+
+ public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType)
+ {
+ // First-wins: each managed type exists in exactly one assembly
+ foreach (var universe in _universes) {
+ if (universe.TryGetProxyType (managedType, out proxyType)) {
+ return true;
+ }
+ }
+ proxyType = null;
+ return false;
+ }
+}
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ITypeMapWithAliasing.cs b/src/Mono.Android/Microsoft.Android.Runtime/ITypeMapWithAliasing.cs
new file mode 100644
index 00000000000..0849741e96d
--- /dev/null
+++ b/src/Mono.Android/Microsoft.Android.Runtime/ITypeMapWithAliasing.cs
@@ -0,0 +1,29 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Android.Runtime;
+
+///
+/// Abstraction over the typemap dictionary that handles alias resolution.
+/// Both Debug (per-assembly universes) and Release (single merged universe)
+/// go through this interface, so doesn't
+/// need to know about aliasing mechanics.
+///
+interface ITypeMapWithAliasing
+{
+ ///
+ /// Returns all types mapped to a JNI name, resolving alias holders.
+ /// For non-alias entries this yields a single type. For alias groups
+ /// it follows each alias key and yields the surviving target types.
+ ///
+ IEnumerable GetTypes (string jniName);
+
+ ///
+ /// Resolves a managed type to its proxy type (the generated type that
+ /// carries the attribute).
+ ///
+ bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType);
+}
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs
new file mode 100644
index 00000000000..95ac2fe4163
--- /dev/null
+++ b/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs
@@ -0,0 +1,86 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using Java.Interop;
+
+namespace Microsoft.Android.Runtime;
+
+///
+/// Wraps a single universe
+/// and its proxy type map. Handles
+/// alias resolution within that universe.
+/// Used in both Debug (one per assembly) and Release (single merged).
+///
+sealed class SingleUniverseTypeMap : ITypeMapWithAliasing
+{
+ readonly IReadOnlyDictionary _typeMap;
+ readonly IReadOnlyDictionary _proxyTypeMap;
+
+ public SingleUniverseTypeMap (IReadOnlyDictionary typeMap, IReadOnlyDictionary proxyTypeMap)
+ {
+ ArgumentNullException.ThrowIfNull (typeMap);
+ ArgumentNullException.ThrowIfNull (proxyTypeMap);
+ _typeMap = typeMap;
+ _proxyTypeMap = proxyTypeMap;
+ }
+
+ public IEnumerable GetTypes (string jniName)
+ {
+ if (!_typeMap.TryGetValue (jniName, out var mappedType)) {
+ yield break;
+ }
+
+ // Fast path: non-alias entry
+ if (mappedType.GetCustomAttribute (inherit: false) is not null) {
+ yield return mappedType;
+ yield break;
+ }
+
+ // Slow path: alias holder — follow each alias key
+ var aliases = mappedType.GetCustomAttribute (inherit: false);
+ if (aliases is null) {
+ yield break;
+ }
+
+ foreach (var key in aliases.Aliases) {
+ if (_typeMap.TryGetValue (key, out var aliasEntryType) &&
+ aliasEntryType.GetCustomAttribute (inherit: false) is not null) {
+ yield return aliasEntryType;
+ }
+ }
+ }
+
+ public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType)
+ {
+ if (!_proxyTypeMap.TryGetValue (managedType, out var mappedProxyType)) {
+ proxyType = null;
+ return false;
+ }
+
+ // Fast path: direct proxy
+ if (mappedProxyType.GetCustomAttribute (inherit: false) is not null) {
+ proxyType = mappedProxyType;
+ return true;
+ }
+
+ // Slow path: alias holder — find the alias whose target type matches
+ var aliases = mappedProxyType.GetCustomAttribute (inherit: false);
+ if (aliases is not null) {
+ foreach (var key in aliases.Aliases) {
+ if (_typeMap.TryGetValue (key, out var aliasProxyType)) {
+ var aliasProxy = aliasProxyType.GetCustomAttribute (inherit: false);
+ if (aliasProxy is not null && TrimmableTypeMap.TargetTypeMatches (managedType, aliasProxy.TargetType)) {
+ proxyType = aliasProxyType;
+ return true;
+ }
+ }
+ }
+ }
+
+ proxyType = null;
+ return false;
+ }
+}
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
index 5a1a26cf33e..2272fc1d9dc 100644
--- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
+++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
@@ -13,7 +13,7 @@
namespace Microsoft.Android.Runtime;
///
-/// Central type map for the trimmable typemap path. Owns the TypeMapping dictionary
+/// Central type map for the trimmable typemap path. Owns the ITypeMapWithAliasing
/// and provides peer creation, invoker resolution, container factories, and native
/// method registration. All proxy attribute access is encapsulated here.
///
@@ -27,31 +27,57 @@ class TrimmableTypeMap
s_instance ?? throw new InvalidOperationException (
"TrimmableTypeMap has not been initialized. Ensure RuntimeFeature.TrimmableTypeMap is enabled and the JNI runtime is initialized.");
- readonly IReadOnlyDictionary _typeMap;
- readonly IReadOnlyDictionary _proxyTypeMap;
+ readonly ITypeMapWithAliasing _typeMap;
readonly ConcurrentDictionary _proxyCache = new ();
readonly ConcurrentDictionary _jniProxyCache = new (StringComparer.Ordinal);
- TrimmableTypeMap ()
+ TrimmableTypeMap (ITypeMapWithAliasing typeMap)
{
- _typeMap = TypeMapping.GetOrCreateExternalTypeMapping ();
- _proxyTypeMap = TypeMapping.GetOrCreateProxyTypeMapping ();
+ _typeMap = typeMap;
}
///
- /// Initializes the singleton instance and registers the bootstrap JNI native method.
- /// Must be called after the JNI runtime is initialized and before any JCW class is loaded.
+ /// 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).
///
- internal static void Initialize ()
+ internal static void Initialize (IReadOnlyDictionary typeMap, IReadOnlyDictionary proxyMap)
{
- if (s_instance is not null)
- return;
+ ArgumentNullException.ThrowIfNull (typeMap);
+ ArgumentNullException.ThrowIfNull (proxyMap);
+ InitializeCore (new SingleUniverseTypeMap (typeMap, proxyMap));
+ }
+
+ ///
+ /// 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).
+ ///
+ internal static void Initialize (IReadOnlyDictionary[] typeMaps, IReadOnlyDictionary[] proxyMaps)
+ {
+ ArgumentNullException.ThrowIfNull (typeMaps);
+ ArgumentNullException.ThrowIfNull (proxyMaps);
+ if (typeMaps.Length == 0) {
+ throw new ArgumentException ("At least one typemap universe must be provided.", nameof (typeMaps));
+ }
+ if (typeMaps.Length != proxyMaps.Length) {
+ throw new ArgumentException ($"typeMaps.Length ({typeMaps.Length}) must equal proxyMaps.Length ({proxyMaps.Length}).");
+ }
+ var universes = new SingleUniverseTypeMap [typeMaps.Length];
+ for (int i = 0; i < typeMaps.Length; i++) {
+ universes [i] = new SingleUniverseTypeMap (typeMaps [i], proxyMaps [i]);
+ }
+ InitializeCore (new AggregateTypeMap (universes));
+ }
+ static void InitializeCore (ITypeMapWithAliasing typeMap)
+ {
lock (s_initLock) {
- if (s_instance is not null)
- return;
+ if (s_instance is not null) {
+ throw new InvalidOperationException ("TrimmableTypeMap has already been initialized.");
+ }
- var instance = new TrimmableTypeMap ();
+ var instance = new TrimmableTypeMap (typeMap);
instance.RegisterNatives ();
s_instance = instance;
}
@@ -104,29 +130,11 @@ internal bool TryGetTargetTypes (string jniName, [NotNullWhen (true)] out Type[]
JavaPeerProxy[] GetProxiesForJniName (string jniName)
{
return _jniProxyCache.GetOrAdd (jniName, static (name, self) => {
- if (!self._typeMap.TryGetValue (name, out var mappedType)) {
- return [];
- }
-
- // Fast path: non-alias entry
- var proxy = mappedType.GetCustomAttribute (inherit: false);
- if (proxy is not null) {
- return [proxy];
- }
-
- // Slow path: alias holder — resolve each alias key
- var aliases = mappedType.GetCustomAttribute (inherit: false);
- if (aliases is null) {
- return [];
- }
-
var result = new List ();
- foreach (var key in aliases.Aliases) {
- if (self._typeMap.TryGetValue (key, out var aliasEntryType)) {
- var aliasProxy = aliasEntryType.GetCustomAttribute (inherit: false);
- if (aliasProxy is not null) {
- result.Add (aliasProxy);
- }
+ foreach (var type in self._typeMap.GetTypes (name)) {
+ var proxy = type.GetCustomAttribute (inherit: false);
+ if (proxy is not null) {
+ result.Add (proxy);
}
}
return result.Count > 0 ? result.ToArray () : [];
@@ -161,41 +169,15 @@ JavaPeerProxy[] GetProxiesForJniName (string jniName)
}
var proxy = _proxyCache.GetOrAdd (managedType, static (type, self) => {
- if (!self._proxyTypeMap.TryGetValue (type, out var proxyType)) {
+ if (!self._typeMap.TryGetProxyType (type, out var proxyType)) {
return s_noPeerSentinel;
}
- // Fast path: direct proxy lookup (non-alias types)
- var proxy = proxyType.GetCustomAttribute (inherit: false);
- if (proxy is not null) {
- return proxy;
- }
-
- // Slow path: _proxyTypeMap mapped this type to an alias holder — resolve from aliases
- var aliases = proxyType.GetCustomAttribute (inherit: false);
- if (aliases is not null) {
- return GetProxyFromAliases (self, aliases, type) ?? s_noPeerSentinel;
- }
-
- return s_noPeerSentinel;
+ return proxyType.GetCustomAttribute (inherit: false) ?? s_noPeerSentinel;
}, this);
return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy;
}
- static JavaPeerProxy? GetProxyFromAliases (TrimmableTypeMap self, JavaPeerAliasesAttribute aliases, Type targetType)
- {
- foreach (var key in aliases.Aliases) {
- if (!self._typeMap.TryGetValue (key, out var aliasProxyType)) {
- continue;
- }
- var aliasProxy = aliasProxyType.GetCustomAttribute (inherit: false);
- if (aliasProxy is not null && TargetTypeMatches (targetType, aliasProxy.TargetType)) {
- return aliasProxy;
- }
- }
- return null;
- }
-
internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true)] out string? jniName)
{
jniName = GetProxyForManagedType (managedType)?.JniName;
diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj
index 12bb3a01446..55352540d70 100644
--- a/src/Mono.Android/Mono.Android.csproj
+++ b/src/Mono.Android/Mono.Android.csproj
@@ -354,11 +354,14 @@
+
+
+
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 a1f29e119d7..4156e0db1b5 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,10 +43,19 @@ See: https://github.com/dotnet/sdk/pull/52581
+ <_ExistingStartupHooks Include="@(RuntimeEnvironmentVariable)" Condition=" '%(Identity)' == 'DOTNET_STARTUP_HOOKS' and '%(Value)' != '$(_AndroidHotReloadAgentAssemblyPath)' " />
-
+
+
+ <_ExistingStartupHooks Remove="@(_ExistingStartupHooks)" />
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 52ce49be5cf..40a2c1519c3 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,6 +11,9 @@
<_TypeMapAssemblyName>_Microsoft.Android.TypeMaps
+
+ true
@@ -31,6 +34,28 @@
Condition=" '$(_AndroidRuntime)' == 'CoreCLR' " />
+
+
+
+ <_ExistingStartupHooks Include="@(RuntimeEnvironmentVariable)" Condition=" '%(Identity)' == 'DOTNET_STARTUP_HOOKS' " />
+
+
+
+ <_ExistingStartupHooks Remove="@(_ExistingStartupHooks)" />
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
index 120feb3c59a..f80a14157f1 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
@@ -129,6 +129,7 @@ public override bool RunTask ()
assemblies,
systemRuntimeVersion,
frameworkAssemblyNames,
+ useSharedTypemapUniverse: !Debug,
manifestConfig,
manifestTemplate);
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs
index 44d357f911b..0acd64e1812 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs
@@ -11,11 +11,11 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap.Tests;
public class RootTypeMapAssemblyGeneratorTests : FixtureTestBase
{
- static MemoryStream GenerateRootAssembly (IReadOnlyList perAssemblyNames, string? assemblyName = null)
+ static MemoryStream GenerateRootAssembly (IReadOnlyList perAssemblyNames, bool useSharedTypemapUniverse = false, string? assemblyName = null)
{
var stream = new MemoryStream ();
var generator = new RootTypeMapAssemblyGenerator (new Version (11, 0, 0, 0));
- generator.Generate (perAssemblyNames, stream, assemblyName);
+ generator.Generate (perAssemblyNames, useSharedTypemapUniverse, stream, assemblyName);
stream.Position = 0;
return stream;
}
@@ -33,7 +33,7 @@ public void Generate_ProducesValidPEAssembly ()
[InlineData ("MyRoot", "MyRoot")]
public void Generate_AssemblyName_MatchesExpected (string? assemblyName, string expectedName)
{
- using var stream = GenerateRootAssembly ([], assemblyName);
+ using var stream = GenerateRootAssembly ([], assemblyName: assemblyName);
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();
var asmDef = reader.GetAssemblyDefinition ();
@@ -54,13 +54,12 @@ public void Generate_ReferencesGenericTypeMapAssemblyTargetAttribute ()
reader.GetString (t.Name) == "TypeMapAssemblyTargetAttribute`1" &&
reader.GetString (t.Namespace) == "System.Runtime.InteropServices");
- Assert.Contains (typeRefs, t =>
- reader.GetString (t.Name) == "Object" &&
- reader.GetString (t.Namespace) == "Java.Lang");
-
var typeDefs = reader.TypeDefinitions
.Select (h => reader.GetTypeDefinition (h))
.ToList ();
+ Assert.Contains (typeDefs, t =>
+ reader.GetString (t.Name) == "__TypeMapAnchor");
+
Assert.DoesNotContain (typeDefs, t =>
reader.GetString (t.Name).Contains ("TypeMapAssemblyTarget"));
}
@@ -71,8 +70,8 @@ public void Generate_EmptyList_ProducesValidAssemblyWithNoTargetAttributes ()
using var stream = GenerateRootAssembly ([]);
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();
- var asmAttrs = reader.GetCustomAttributes (EntityHandle.AssemblyDefinition);
- Assert.Empty (asmAttrs);
+ var targetAttrs = GetTypeMapAssemblyTargetAttributes (reader);
+ Assert.Empty (targetAttrs);
}
[Fact]
@@ -82,8 +81,8 @@ public void Generate_MultipleTargets_HasCorrectAttributeCount ()
using var stream = GenerateRootAssembly (targets);
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();
- var asmAttrs = reader.GetCustomAttributes (EntityHandle.AssemblyDefinition);
- Assert.Equal (3, asmAttrs.Count ());
+ var targetAttrs = GetTypeMapAssemblyTargetAttributes (reader);
+ Assert.Equal (3, targetAttrs.Count);
}
[Fact]
@@ -94,9 +93,10 @@ public void Generate_AttributeBlobValues_MatchTargetNames ()
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();
+ var targetAttrs = GetTypeMapAssemblyTargetAttributes (reader);
+
var attrValues = new List ();
- foreach (var attrHandle in reader.GetCustomAttributes (EntityHandle.AssemblyDefinition)) {
- var attr = reader.GetCustomAttribute (attrHandle);
+ foreach (var attr in targetAttrs) {
var blob = reader.GetBlobReader (attr.Value);
// Custom attribute blob: prolog (2 bytes) + SerString value
@@ -111,4 +111,130 @@ public void Generate_AttributeBlobValues_MatchTargetNames ()
Assert.Contains ("_App.TypeMap", attrValues);
Assert.Contains ("_Mono.Android.TypeMap", attrValues);
}
+
+ static List GetTypeMapAssemblyTargetAttributes (MetadataReader reader)
+ {
+ var result = new List ();
+ foreach (var attrHandle in reader.GetCustomAttributes (EntityHandle.AssemblyDefinition)) {
+ var attr = reader.GetCustomAttribute (attrHandle);
+ if (attr.Constructor.Kind == HandleKind.MemberReference) {
+ var memberRef = reader.GetMemberReference ((MemberReferenceHandle)attr.Constructor);
+ if (memberRef.Parent.Kind == HandleKind.TypeSpecification) {
+ // TypeMapAssemblyTargetAttribute is a generic type spec
+ result.Add (attr);
+ }
+ }
+ }
+ return result;
+ }
+
+ [Theory]
+ [InlineData (true)]
+ [InlineData (false)]
+ public void Generate_BothMergeModes_ProduceValidPEAssembly (bool useSharedTypemapUniverse)
+ {
+ using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"], useSharedTypemapUniverse);
+ using var pe = new PEReader (stream);
+ Assert.True (pe.HasMetadata);
+
+ var reader = pe.GetMetadataReader ();
+
+ // Both modes should have StartupHook type with Initialize method
+ var typeDefs = reader.TypeDefinitions
+ .Select (h => reader.GetTypeDefinition (h))
+ .ToList ();
+ Assert.Contains (typeDefs, t => reader.GetString (t.Name) == "StartupHook");
+
+ // Both modes should have assembly target attributes
+ var targetAttrs = GetTypeMapAssemblyTargetAttributes (reader);
+ Assert.Equal (2, targetAttrs.Count);
+ }
+
+ [Fact]
+ public void Generate_MergedMode_ReferencesRootAnchorOnly ()
+ {
+ using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"], useSharedTypemapUniverse: true);
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ // In merged mode, the root assembly's __TypeMapAnchor is used.
+ // The per-assembly anchors should NOT be referenced directly (no assembly refs for per-assembly typemaps).
+ var asmRefs = reader.AssemblyReferences
+ .Select (h => reader.GetString (reader.GetAssemblyReference (h).Name))
+ .ToList ();
+ Assert.DoesNotContain ("_App.TypeMap", asmRefs);
+ Assert.DoesNotContain ("_Mono.Android.TypeMap", asmRefs);
+ }
+
+ [Fact]
+ public void Generate_AggregateMode_ReferencesPerAssemblyAnchors ()
+ {
+ using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"], useSharedTypemapUniverse: false);
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ // In aggregate mode, the root assembly should reference each per-assembly typemap.
+ var asmRefs = reader.AssemblyReferences
+ .Select (h => reader.GetString (reader.GetAssemblyReference (h).Name))
+ .ToList ();
+ Assert.Contains ("_App.TypeMap", asmRefs);
+ Assert.Contains ("_Mono.Android.TypeMap", asmRefs);
+ }
+
+ [Fact]
+ public void Generate_MergedMode_HasIgnoresAccessChecksToMonoAndroidOnly ()
+ {
+ using var stream = GenerateRootAssembly (["_App.TypeMap"], useSharedTypemapUniverse: true);
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var accessAttrs = GetIgnoresAccessChecksToValues (reader);
+ Assert.Contains ("Mono.Android", accessAttrs);
+ Assert.DoesNotContain ("_App.TypeMap", accessAttrs);
+ }
+
+ [Fact]
+ public void Generate_AggregateMode_HasIgnoresAccessChecksToAllAssemblies ()
+ {
+ using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"], useSharedTypemapUniverse: false);
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var accessAttrs = GetIgnoresAccessChecksToValues (reader);
+ Assert.Contains ("Mono.Android", accessAttrs);
+ Assert.Contains ("_App.TypeMap", accessAttrs);
+ Assert.Contains ("_Mono.Android.TypeMap", accessAttrs);
+ }
+
+ static List GetIgnoresAccessChecksToValues (MetadataReader reader)
+ {
+ var result = new List ();
+ foreach (var attrHandle in reader.GetCustomAttributes (EntityHandle.AssemblyDefinition)) {
+ var attr = reader.GetCustomAttribute (attrHandle);
+
+ string? typeName = null;
+ if (attr.Constructor.Kind == HandleKind.MemberReference) {
+ var memberRef = reader.GetMemberReference ((MemberReferenceHandle) attr.Constructor);
+ if (memberRef.Parent.Kind == HandleKind.TypeReference) {
+ typeName = reader.GetString (reader.GetTypeReference ((TypeReferenceHandle) memberRef.Parent).Name);
+ } else if (memberRef.Parent.Kind == HandleKind.TypeDefinition) {
+ typeName = reader.GetString (reader.GetTypeDefinition ((TypeDefinitionHandle) memberRef.Parent).Name);
+ }
+ } else if (attr.Constructor.Kind == HandleKind.MethodDefinition) {
+ var methodDef = reader.GetMethodDefinition ((MethodDefinitionHandle) attr.Constructor);
+ typeName = reader.GetString (reader.GetTypeDefinition (methodDef.GetDeclaringType ()).Name);
+ }
+
+ if (typeName != "IgnoresAccessChecksToAttribute") {
+ continue;
+ }
+ var blob = reader.GetBlobReader (attr.Value);
+ blob.ReadUInt16 (); // prolog
+ var value = blob.ReadSerializedString ();
+ if (value is not null) {
+ result.Add (value);
+ }
+ }
+ return result;
+ }
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs
index 8412d665597..973bbc86ffe 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs
@@ -136,6 +136,7 @@ public void Execute_ManifestPlaceholdersAreResolvedBeforeRooting ()
new List<(string, PEReader)> { ("TestFixtures", peReader) },
new Version (11, 0),
new HashSet (),
+ useSharedTypemapUniverse: false,
new ManifestConfig (
PackageName: "my.app",
AndroidApiLevel: "35",