From 1ae98ce01e0dbee84c19e57b39947c31f57c9392 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:58:58 -0700 Subject: [PATCH 01/10] Rework PreserveX steps --- .../ApplyPreserveAttribute.cs | 2 +- .../MyBaseSubStep.cs | 52 ++++++ .../PreMarkSubStepsDispatcher.cs | 165 ++++++++++++++++++ .../PreserveExportedTypes.cs | 10 +- .../PreserveSubStepDispatcher.cs | 15 +- .../RemoveAttributesBase.cs | 103 ----------- 6 files changed, 233 insertions(+), 114 deletions(-) create mode 100644 src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs create mode 100644 src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs delete mode 100644 src/Microsoft.Android.Sdk.ILLink/RemoveAttributesBase.cs diff --git a/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs b/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs index 4c4af5ab10e..93af6911ed6 100644 --- a/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs +++ b/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs @@ -10,7 +10,7 @@ namespace Microsoft.Android.Sdk.ILLink { - public class ApplyPreserveAttribute : BaseSubStep { + public class ApplyPreserveAttribute : MyBaseSubStep { public override SubStepTargets Targets { get { diff --git a/src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs b/src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs new file mode 100644 index 00000000000..1924b72b7a2 --- /dev/null +++ b/src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Mono.Cecil; +using Mono.Linker; +using Mono.Linker.Steps; + +namespace Microsoft.Android.Sdk.ILLink +{ + public abstract class MyBaseSubStep + { + protected AnnotationStore Annotations => Context.Annotations; + + LinkContext? _context { get; set; } + protected LinkContext Context { + get { + Debug.Assert (_context != null); + return _context; + } + } + + public abstract SubStepTargets Targets { get; } + + public virtual void Initialize (LinkContext context) + { + _context = context; + } + + public virtual bool IsActiveFor (AssemblyDefinition assembly) => true; + + public virtual void ProcessType (TypeDefinition type) + { + } + + public virtual void ProcessField (FieldDefinition field) + { + } + + public virtual void ProcessMethod (MethodDefinition method) + { + } + + public virtual void ProcessProperty (PropertyDefinition property) + { + } + + public virtual void ProcessEvent (EventDefinition @event) + { + } + } +} diff --git a/src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs b/src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs new file mode 100644 index 00000000000..909be8dcabc --- /dev/null +++ b/src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs @@ -0,0 +1,165 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Mono.Cecil; +using Mono.Collections.Generic; +using Mono.Linker; +using Mono.Linker.Steps; + +namespace Microsoft.Android.Sdk.ILLink +{ + public class PreMarkSubStepsDispatcher : BaseStep + { + readonly List substeps; + + CategorizedSubSteps? categorized; + CategorizedSubSteps Categorized { + get { + Debug.Assert (categorized.HasValue); + return categorized.Value; + } + } + protected override void Process() + { + InitializeSubSteps (Context); + } + protected override void ProcessAssembly(AssemblyDefinition assembly) + { + BrowseAssembly (assembly); + } + + public PreMarkSubStepsDispatcher (IEnumerable subSteps) + { + substeps = [.. subSteps]; + } + + static bool HasSubSteps (List substeps) => substeps?.Count > 0; + + void BrowseAssembly (AssemblyDefinition assembly) + { + CategorizeSubSteps (assembly); + + if (!ShouldDispatchTypes ()) + return; + + BrowseTypes (assembly.MainModule.Types); + } + + bool ShouldDispatchTypes () + { + return HasSubSteps (Categorized.on_types) + || HasSubSteps (Categorized.on_fields) + || HasSubSteps (Categorized.on_methods) + || HasSubSteps (Categorized.on_properties) + || HasSubSteps (Categorized.on_events); + } + + void BrowseTypes (Collection types) + { + foreach (TypeDefinition type in types) { + DispatchType (type); + + if (type.HasFields && HasSubSteps (Categorized.on_fields)) { + foreach (FieldDefinition field in type.Fields) + DispatchField (field); + } + + if (type.HasMethods && HasSubSteps (Categorized.on_methods)) { + foreach (MethodDefinition method in type.Methods) + DispatchMethod (method); + } + + if (type.HasProperties && HasSubSteps (Categorized.on_properties)) { + foreach (PropertyDefinition property in type.Properties) + DispatchProperty (property); + } + + if (type.HasEvents && HasSubSteps (Categorized.on_events)) { + foreach (EventDefinition @event in type.Events) + DispatchEvent (@event); + } + + if (type.HasNestedTypes) + BrowseTypes (type.NestedTypes); + } + } + + void DispatchType (TypeDefinition type) + { + foreach (var substep in Categorized.on_types) { + substep.ProcessType (type); + } + } + + void DispatchField (FieldDefinition field) + { + foreach (var substep in Categorized.on_fields) { + substep.ProcessField (field); + } + } + + void DispatchMethod (MethodDefinition method) + { + foreach (var substep in Categorized.on_methods) { + substep.ProcessMethod (method); + } + } + + void DispatchProperty (PropertyDefinition property) + { + foreach (var substep in Categorized.on_properties) { + substep.ProcessProperty (property); + } + } + + void DispatchEvent (EventDefinition @event) + { + foreach (var substep in Categorized.on_events) { + substep.ProcessEvent (@event); + } + } + + void InitializeSubSteps (LinkContext context) + { + foreach (var substep in substeps) + substep.Initialize (context); + } + + void CategorizeSubSteps (AssemblyDefinition assembly) + { + categorized = new CategorizedSubSteps { + on_assemblies = new List (), + on_types = new List (), + on_fields = new List (), + on_methods = new List (), + on_properties = new List (), + on_events = new List () + }; + + foreach (var substep in substeps) + CategorizeSubStep (substep, assembly); + } + + void CategorizeSubStep (MyBaseSubStep substep, AssemblyDefinition assembly) + { + if (!substep.IsActiveFor (assembly)) + return; + + CategorizeTarget (substep, SubStepTargets.Assembly, Categorized.on_assemblies); + CategorizeTarget (substep, SubStepTargets.Type, Categorized.on_types); + CategorizeTarget (substep, SubStepTargets.Field, Categorized.on_fields); + CategorizeTarget (substep, SubStepTargets.Method, Categorized.on_methods); + CategorizeTarget (substep, SubStepTargets.Property, Categorized.on_properties); + CategorizeTarget (substep, SubStepTargets.Event, Categorized.on_events); + } + + static void CategorizeTarget (MyBaseSubStep substep, SubStepTargets target, List list) + { + if (!Targets (substep, target)) + return; + + list.Add (substep); + } + + static bool Targets (MyBaseSubStep substep, SubStepTargets target) => (substep.Targets & target) == target; + } +} diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs b/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs index 1b2664dc953..e5356ebb535 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs @@ -10,8 +10,7 @@ namespace Mono.Tuner { - public class PreserveExportedTypes : BaseSubStep { - + public class PreserveExportedTypes : MyBaseSubStep { public override SubStepTargets Targets { get { return SubStepTargets.Field @@ -21,7 +20,7 @@ public override SubStepTargets Targets { } } - public override bool IsActiveFor (AssemblyDefinition assembly) + public bool IsActiveFor (AssemblyDefinition assembly) { if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) return false; @@ -40,16 +39,13 @@ public override void ProcessMethod (MethodDefinition method) ProcessExports (method); } + public override void ProcessProperty (PropertyDefinition property) { ProcessExports (property.GetMethod); ProcessExports (property.SetMethod); } - public override void ProcessEvent (EventDefinition @event) - { - } - void ProcessExports (ICustomAttributeProvider provider) { if (provider == null) diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs b/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs index 2e0937abe0e..6dfa2131db0 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs @@ -1,19 +1,28 @@ using System; using System.Collections.Generic; using System.Text; -using Mono.Linker.Steps; using Mono.Tuner; namespace Microsoft.Android.Sdk.ILLink { - public class PreserveSubStepDispatcher : MarkSubStepsDispatcher + public class PreserveSubStepDispatcher : PreMarkSubStepsDispatcher { public PreserveSubStepDispatcher () - : base (new ISubStep[] { + : base (new MyBaseSubStep [] { new ApplyPreserveAttribute (), new PreserveExportedTypes () }) { } } + + internal struct CategorizedSubSteps + { + public List on_assemblies { get; set; } + public List on_types { get; set; } + public List on_fields { get; set; } + public List on_methods { get; set; } + public List on_properties { get; set; } + public List on_events { get; set; } + } } diff --git a/src/Microsoft.Android.Sdk.ILLink/RemoveAttributesBase.cs b/src/Microsoft.Android.Sdk.ILLink/RemoveAttributesBase.cs deleted file mode 100644 index 7f9cc8c40ef..00000000000 --- a/src/Microsoft.Android.Sdk.ILLink/RemoveAttributesBase.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -using Mono.Linker; -using Mono.Linker.Steps; - -using Mono.Tuner; - -using Mono.Cecil; - -namespace Microsoft.Android.Sdk.ILLink -{ - public abstract class RemoveAttributesBase : BaseSubStep { - - public override SubStepTargets Targets { - get { - return SubStepTargets.Assembly - | SubStepTargets.Type - | SubStepTargets.Field - | SubStepTargets.Method - | SubStepTargets.Property - | SubStepTargets.Event; - } - } - - public override bool IsActiveFor (AssemblyDefinition assembly) - { - return Annotations.GetAction (assembly) == AssemblyAction.Link; - } - - public override void ProcessAssembly (AssemblyDefinition assembly) - { - ProcessAttributeProvider (assembly); - ProcessAttributeProvider (assembly.MainModule); - } - - public override void ProcessType (TypeDefinition type) - { - ProcessAttributeProvider (type); - - if (type.HasGenericParameters) - ProcessAttributeProviderCollection (type.GenericParameters); - } - - void ProcessAttributeProviderCollection (IList list) - { - for (int i = 0; i < list.Count; i++) - ProcessAttributeProvider ((ICustomAttributeProvider) list [i]); - } - - public override void ProcessField (FieldDefinition field) - { - ProcessAttributeProvider (field); - } - - public override void ProcessMethod (MethodDefinition method) - { - ProcessMethodAttributeProvider (method); - } - - void ProcessMethodAttributeProvider (MethodDefinition method) - { - ProcessAttributeProvider (method); - ProcessAttributeProvider (method.MethodReturnType); - - if (method.HasParameters) - ProcessAttributeProviderCollection (method.Parameters); - - if (method.HasGenericParameters) - ProcessAttributeProviderCollection (method.GenericParameters); - } - - public override void ProcessProperty (PropertyDefinition property) - { - ProcessAttributeProvider (property); - } - - public override void ProcessEvent (EventDefinition @event) - { - ProcessAttributeProvider (@event); - } - - void ProcessAttributeProvider (ICustomAttributeProvider provider) - { - if (!provider.HasCustomAttributes) - return; - - for (int i = 0; i < provider.CustomAttributes.Count; i++) { - var attrib = provider.CustomAttributes [i]; - if (!IsRemovedAttribute (attrib)) - continue; - - WillRemoveAttribute (provider, attrib); - provider.CustomAttributes.RemoveAt (i--); - } - } - - protected abstract bool IsRemovedAttribute (CustomAttribute attribute); - protected virtual void WillRemoveAttribute (ICustomAttributeProvider provider, CustomAttribute attribute) { } - } -} From cc9741311c8a9daaae965f2cde9feb5002529c9a Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 12 Sep 2025 12:30:10 -0700 Subject: [PATCH 02/10] Update Java.Interop --- external/Java.Interop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Java.Interop b/external/Java.Interop index f21b003d0c6..56937c6d9fd 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit f21b003d0c6325d157f681d2ac89221ad5ab17fc +Subproject commit 56937c6d9fd62742ca5d9adfec7fce00ece9f93c From 2947be9d82a0cdd536feae4cc32e7d45ddf5deb2 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:40:11 -0700 Subject: [PATCH 03/10] Add GenerateTypeMapAttributesStep --- .../GenerateTypeMapAttributesStep.cs | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs diff --git a/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs new file mode 100644 index 00000000000..3b27dec8d8a --- /dev/null +++ b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs @@ -0,0 +1,124 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Text; +using Java.Interop.Tools.Cecil; +using Java.Interop.Tools.TypeNameMappings; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Mono.Linker; +using Mono.Linker.Steps; + +namespace Microsoft.Android.Sdk.ILLink; + +/// +/// Generate TypeMap attributes using the .NET 10 TypeMapAttribute and TypeMapAssociationAttribute +/// +public class GenerateTypeMapAttributesStep : BaseStep +{ + const string TypeMapAttributeTypeName = "System.Runtime.InteropServices.TypeMapAttribute`1"; + TypeReference TypeMapAttribute { get; set; } + MethodReference TypeMapAttributeCtor { get; set; } + + const string JavaTypeMapUniverseTypeName = "Java.Lang.Object"; + TypeReference JavaTypeMapUniverseType { get; set; } + + TypeReference SystemTypeType { get; set; } + TypeReference SystemStringType { get; set; } + + AssemblyDefinition EntryPointAssembly { get; set; } + + protected override void Process () + { + EntryPointAssembly = Context.Annotations.GetType ().GetMethod ("GetEntryPointAssembly")?.Invoke (Context.Annotations, null) as AssemblyDefinition ?? throw new NotImplementedException ("asdfasdf NoEntryPoint"); + JavaTypeMapUniverseType = EntryPointAssembly.MainModule.ImportReference (Context.GetType (JavaTypeMapUniverseTypeName)); + var typeMapAttributeDefinition = Context.GetType (TypeMapAttributeTypeName); + TypeMapAttribute = EntryPointAssembly.MainModule.ImportReference (typeMapAttributeDefinition.MakeGenericInstanceType (JavaTypeMapUniverseType)); + var typeMapAttributeCtorDefinition = typeMapAttributeDefinition.Methods + .FirstOrDefault (m => m.IsConstructor + && m.Parameters is [ + { ParameterType.FullName: "System.String" }, + { ParameterType.FullName: "System.Type" }, + { ParameterType.FullName: "System.Type" }]) ?? throw new InvalidOperationException ("Couldn't find TypeMapAttribute..ctor(string, Type, Type)"); + var typeMapAttributeCtor = new MethodReference ( + typeMapAttributeCtorDefinition.Name, + typeMapAttributeCtorDefinition.ReturnType, + TypeMapAttribute) { + HasThis = typeMapAttributeCtorDefinition.HasThis, + ExplicitThis = typeMapAttributeCtorDefinition.ExplicitThis, + CallingConvention = typeMapAttributeCtorDefinition.CallingConvention, + }; + foreach (var param in typeMapAttributeCtorDefinition.Parameters) { + typeMapAttributeCtor.Parameters.Add (new ParameterDefinition ( + param.Name, + param.Attributes, + EntryPointAssembly.MainModule.ImportReference (param.ParameterType))); + } + + TypeMapAttributeCtor = EntryPointAssembly.MainModule.ImportReference (typeMapAttributeCtor); + SystemTypeType = EntryPointAssembly.MainModule.ImportReference (Context.GetType ("System.Type")); + SystemStringType = EntryPointAssembly.MainModule.ImportReference (Context.GetType ("System.String")); + + Context.LogMessage (MessageContainer.CreateInfoMessage ($""" + EntryPointAssembly: {EntryPointAssembly.Name} + TypeMapUnivers: {JavaTypeMapUniverseType.FullName} + TypeMapType: {typeMapAttributeDefinition.FullName} + TypeMapAttr: {TypeMapAttribute.FullName} + TypeMapAtrtibuteCtorDefinition: {typeMapAttributeCtorDefinition.FullName} + TypeMapAtrtibuteCtor: {typeMapAttributeCtor.FullName} + System.Type: {SystemTypeType} + System.String: {SystemStringType} + """)); + } + + + protected override void ProcessAssembly (AssemblyDefinition assembly) + { + foreach (var type in assembly.MainModule.Types) { + ProcessType (assembly, type); + } + } + Dictionary> javaNameToTypes = new (); + Dictionary typeToJavaName = new (); + + List injectedAttributes = new (); + + private void ProcessType (AssemblyDefinition assembly, TypeDefinition type) + { + if (type.HasJavaPeer (Context)) { + string javaName = JavaNativeTypeManager.ToJniName (type, Context); + Context.LogMessage (MessageContainer.CreateInfoMessage ($"Type '{type.FullName}' has peer '{javaName}'")); + injectedAttributes.Add (GenerateTypeMapAttribute (type, javaName)); + } else { + Context.LogMessage (MessageContainer.CreateInfoMessage ($"Type '{type.FullName}' has no peer")); + } + + if (!type.HasNestedTypes) + return; + + foreach (TypeDefinition nested in type.NestedTypes) + ProcessType (assembly, nested); + } + + CustomAttribute GenerateTypeMapAttribute (TypeDefinition type, string javaName) + { + CustomAttribute ca = new (TypeMapAttributeCtor); + ca.ConstructorArguments.Add (new (SystemStringType, javaName)); + ca.ConstructorArguments.Add (new (SystemTypeType, type)); + ca.ConstructorArguments.Add (new (SystemTypeType, type)); + return ca; + } + + protected override void EndProcess () + { + foreach (var attr in injectedAttributes) { + Context.LogMessage (MessageContainer.CreateInfoMessage ($"Injecting [{attr.AttributeType.FullName}({string.Join (", ", attr.ConstructorArguments.Select (caa => caa.ToString ()))})] into {EntryPointAssembly.Name}")); + EntryPointAssembly.CustomAttributes.Add (attr); + } + EntryPointAssembly.Write (Context.GetAssemblyLocation (EntryPointAssembly) + ".injected"); + } +} From 2067505a21ceda80cc518627f10472fb09210898 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:06:13 -0700 Subject: [PATCH 04/10] Mapping one-to-many, so crashing --- .editorconfig | 4 +- .vscode/launch.json | 18 ++ .vscode/tasks.json | 8 +- .../ApplyPreserveAttribute.cs | 2 +- .../GenerateTypeMapAttributesStep.cs | 157 +++++++++++++---- .../MarkJavaObjects.cs | 35 ++++ .../MyBaseSubStep.cs | 52 ------ .../PreMarkSubStepsDispatcher.cs | 165 ------------------ .../PreserveExportedTypes.cs | 10 +- .../PreserveSubStepDispatcher.cs | 15 +- .../Android.Runtime/JNIEnvInit.cs | 2 + src/Mono.Android/Java.Interop/TypeManager.cs | 8 +- .../ManagedValueManager.cs | 16 ++ src/Mono.Android/Mono.Android.csproj | 26 +-- .../Microsoft.Android.Sdk.ILLink.targets | 6 +- 15 files changed, 226 insertions(+), 298 deletions(-) delete mode 100644 src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs delete mode 100644 src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs diff --git a/.editorconfig b/.editorconfig index 10844a4a473..415070be82a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -37,8 +37,8 @@ indent_size = 4 [*.{cs,csx,java,vb,vbx}] insert_final_newline = true indent_style = tab -tab_width = 8 -indent_size = 8 +tab_width = 4 +indent_size = 4 max_line_length = 180 # CMake files diff --git a/.vscode/launch.json b/.vscode/launch.json index 404a2d6f95b..4fe5a1f2748 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -76,6 +76,24 @@ "clearjdb" ] } + , + { + "name": "Run Java.Interop Tests (.NET 9)", + "type": "coreclr", + "request": "launch", + "program": "C:/Program Files/dotnet/dotnet.exe", + "args": [ + "exec", + "C:/Program Files/dotnet/sdk/10.0.100-rc.1.25419.117/vstest.console.dll", + "--framework:.NETCoreApp,Version=v9.0", + "${workspaceFolder}/external/Java.Interop/bin/Test${input:configuration}-net9.0/Java.Base-Tests.dll", + "--logger:Microsoft.TestPlatform.MSBuildLogger;Verbosity=minimal", + "--nologo", + "--artifactsProcessingMode-collect" + ], + "cwd": "${workspaceFolder}/external/Java.Interop", + "console": "integratedTerminal" + } ], "inputs": [ { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b5273d6a0c8..ed7472fac2e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -100,7 +100,7 @@ { "label": "Build Microsoft.Android.Sdk.Analysis Tests", "type": "shell", - "windows": { "command": "dotnet-local.cmd build src/Microsoft.Android.Sdk.Analysis/Tests/Microsoft.Android.Sdk.Analysis.Tests.csproj -c ${input:configuration}", }, + "windows": { "command": "./dotnet-local.cmd build src/Microsoft.Android.Sdk.Analysis/Tests/Microsoft.Android.Sdk.Analysis.Tests.csproj -c ${input:configuration}", }, "linux": { "command": "./dotnet-local.sh build src/Microsoft.Android.Sdk.Analysis/Tests/Microsoft.Android.Sdk.Analysis.Tests.csproj -c ${input:configuration}",}, "osx": { "command": "./dotnet-local.sh build src/Microsoft.Android.Sdk.Analysis/Tests/Microsoft.Android.Sdk.Analysis.Tests.csproj -c ${input:configuration}",}, "group": { @@ -126,7 +126,7 @@ { "label": "prepare-sample-under-dotnet", "type": "shell", - "windows": { "command": "dotnet-local.cmd build --no-restore tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -c ${input:configuration} -t:GenerateNuGetConfig -p:AndroidNETTestConfigOutputDir=${workspaceRoot}/samples", }, + "windows": { "command": "./dotnet-local.cmd build --no-restore tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -c ${input:configuration} -t:GenerateNuGetConfig -p:AndroidNETTestConfigOutputDir=${workspaceRoot}/samples", }, "linux": { "command": "./dotnet-local.sh build --no-restore tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -c ${input:configuration} -t:GenerateNuGetConfig -p:AndroidNETTestConfigOutputDir=${workspaceRoot}/samples",}, "osx": { "command": "./dotnet-local.sh build --no-restore tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -c ${input:configuration} -t:GenerateNuGetConfig -p:AndroidNETTestConfigOutputDir=${workspaceRoot}/samples",}, "group": { @@ -140,7 +140,7 @@ { "label": "build-sample-under-dotnet", "type": "shell", - "windows": { "command": "dotnet-local.cmd build ${input:project} -p:Configuration=${input:configuration} -t:${input:target} -bl:${input:target}.binlog", }, + "windows": { "command": "./dotnet-local.cmd build ${workspaceFolder}/${input:project} -p:Configuration=${input:configuration} -t:${input:target} -bl:${input:target}.binlog", }, "linux": { "command": "${input:debugbuildtasks} ./dotnet-local.sh build ${input:project} -p:Configuration=${input:configuration} -t:${input:target} -bl:${input:target}.binlog",}, "osx": { "command": "${input:debugbuildtasks} ./dotnet-local.sh build ${input:project} -p:Configuration=${input:configuration} -t:${input:target} -bl:${input:target}.binlog",}, "group": { @@ -154,7 +154,7 @@ { "label": "run-sample-under-dotnet", "type": "shell", - "windows": { "command": "dotnet-local.cmd build ${input:project} \"-t:Run\" --no-restore -p:TargetFramework=${input:targetframework} -p:Configuration=${input:configuration} -p:AndroidAttachDebugger=${input:attach} -bl:run.binlog", }, + "windows": { "command": "./dotnet-local.cmd build ${workspaceFolder}/${input:project} \"-t:Run\" --no-restore -p:TargetFramework=${input:targetframework} -p:Configuration=${input:configuration} -p:AndroidAttachDebugger=${input:attach} -bl:run.binlog", }, "linux": { "command": "${input:debugbuildtasks} ./dotnet-local.sh build ${input:project} \"-t:Run\" --no-restore -p:TargetFramework=${input:targetframework} -p:Configuration=${input:configuration} -p:AndroidAttachDebugger=${input:attach} -bl:run.binlog",}, "osx": { "command": "${input:debugbuildtasks} ./dotnet-local.sh build ${input:project} \"-t:Run\" --no-restore -p:TargetFramework=${input:targetframework} -p:Configuration=${input:configuration} -p:AndroidAttachDebugger=${input:attach} -bl:run.binlog",}, "group": { diff --git a/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs b/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs index 93af6911ed6..4c4af5ab10e 100644 --- a/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs +++ b/src/Microsoft.Android.Sdk.ILLink/ApplyPreserveAttribute.cs @@ -10,7 +10,7 @@ namespace Microsoft.Android.Sdk.ILLink { - public class ApplyPreserveAttribute : MyBaseSubStep { + public class ApplyPreserveAttribute : BaseSubStep { public override SubStepTargets Targets { get { diff --git a/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs index 3b27dec8d8a..e586a679e2e 100644 --- a/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs +++ b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs @@ -1,14 +1,11 @@ using System; -using System.Buffers.Binary; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Runtime.InteropServices; -using System.Runtime.Loader; using System.Text; using Java.Interop.Tools.Cecil; using Java.Interop.Tools.TypeNameMappings; using Mono.Cecil; -using Mono.Cecil.Cil; using Mono.Cecil.Rocks; using Mono.Linker; using Mono.Linker.Steps; @@ -21,8 +18,20 @@ namespace Microsoft.Android.Sdk.ILLink; public class GenerateTypeMapAttributesStep : BaseStep { const string TypeMapAttributeTypeName = "System.Runtime.InteropServices.TypeMapAttribute`1"; - TypeReference TypeMapAttribute { get; set; } - MethodReference TypeMapAttributeCtor { get; set; } + TypeReference TypeMapAttribute; + MethodReference TypeMapAttributeCtor; + + const string TypeMapAssociationAttributeTypeName = "System.Runtime.InteropServices.TypeMapAssociationAttribute`1"; + TypeReference TypeMapAssociationAttribute; + MethodReference TypeMapAssociationAttributeCtor; + + const string TypeMapProxyAttributeTypeName = "Java.Interop.TypeMapProxyAttribute"; + TypeReference TypeMapProxyAttribute; + MethodReference TypeMapProxyAttributeCtor; + + const string TypeMapAssemblyTargetAttributeTypeName = "System.Runtime.InteropServices.TypeMapAssemblyTargetAttribute`1"; + TypeReference TypeMapAssemblyTargetAttribute; + MethodReference TypeMapAssemblyTargetAttributeCtor; const string JavaTypeMapUniverseTypeName = "Java.Lang.Object"; TypeReference JavaTypeMapUniverseType { get; set; } @@ -31,23 +40,25 @@ public class GenerateTypeMapAttributesStep : BaseStep TypeReference SystemStringType { get; set; } AssemblyDefinition EntryPointAssembly { get; set; } - - protected override void Process () + AssemblyDefinition MonoAndroidAssembly { get; set; } + + void GetTypeMapAttributeReferences ( + string attributeTypeName, + Func ctorSelector, + AssemblyDefinition addReferencesTo, + TypeReference typeMapUniverse, + out TypeReference attributeType, + out MethodReference ctor) { - EntryPointAssembly = Context.Annotations.GetType ().GetMethod ("GetEntryPointAssembly")?.Invoke (Context.Annotations, null) as AssemblyDefinition ?? throw new NotImplementedException ("asdfasdf NoEntryPoint"); - JavaTypeMapUniverseType = EntryPointAssembly.MainModule.ImportReference (Context.GetType (JavaTypeMapUniverseTypeName)); - var typeMapAttributeDefinition = Context.GetType (TypeMapAttributeTypeName); - TypeMapAttribute = EntryPointAssembly.MainModule.ImportReference (typeMapAttributeDefinition.MakeGenericInstanceType (JavaTypeMapUniverseType)); + var typeMapAttributeDefinition = Context.GetType (attributeTypeName); + attributeType = addReferencesTo.MainModule.ImportReference (typeMapAttributeDefinition.MakeGenericInstanceType (typeMapUniverse)); + var typeMapAttributeCtorDefinition = typeMapAttributeDefinition.Methods - .FirstOrDefault (m => m.IsConstructor - && m.Parameters is [ - { ParameterType.FullName: "System.String" }, - { ParameterType.FullName: "System.Type" }, - { ParameterType.FullName: "System.Type" }]) ?? throw new InvalidOperationException ("Couldn't find TypeMapAttribute..ctor(string, Type, Type)"); + .FirstOrDefault (ctorSelector) ?? throw new InvalidOperationException ($"Couldn't find {attributeTypeName}..ctor()"); var typeMapAttributeCtor = new MethodReference ( typeMapAttributeCtorDefinition.Name, typeMapAttributeCtorDefinition.ReturnType, - TypeMapAttribute) { + attributeType) { HasThis = typeMapAttributeCtorDefinition.HasThis, ExplicitThis = typeMapAttributeCtorDefinition.ExplicitThis, CallingConvention = typeMapAttributeCtorDefinition.CallingConvention, @@ -56,23 +67,54 @@ protected override void Process () typeMapAttributeCtor.Parameters.Add (new ParameterDefinition ( param.Name, param.Attributes, - EntryPointAssembly.MainModule.ImportReference (param.ParameterType))); + addReferencesTo.MainModule.ImportReference (param.ParameterType))); } + ctor = addReferencesTo.MainModule.ImportReference (typeMapAttributeCtor); + } + + protected override void Process () + { + EntryPointAssembly = Context.Annotations.GetType ().GetMethod ("GetEntryPointAssembly")?.Invoke (Context.Annotations, null) as AssemblyDefinition ?? throw new NotImplementedException ("asdfasdf NoEntryPoint"); + var javaTypeMapUniverseTypeDefinition = Context.GetType (JavaTypeMapUniverseTypeName); + JavaTypeMapUniverseType = EntryPointAssembly.MainModule.ImportReference (javaTypeMapUniverseTypeDefinition); + + GetTypeMapAttributeReferences (TypeMapAttributeTypeName, + m => m.IsConstructor + && m.Parameters is [ + { ParameterType.FullName: "System.String" }, + { ParameterType.FullName: "System.Type" }, + { ParameterType.FullName: "System.Type" }], + EntryPointAssembly, + JavaTypeMapUniverseType, + out TypeMapAttribute, + out TypeMapAttributeCtor); + + GetTypeMapAttributeReferences (TypeMapAssociationAttributeTypeName, + m => m.IsConstructor + && m.Parameters is [ + { ParameterType.FullName: "System.Type" }, + { ParameterType.FullName: "System.Type" }], + EntryPointAssembly, + JavaTypeMapUniverseType, + out TypeMapAssociationAttribute, + out TypeMapAssociationAttributeCtor); + + MonoAndroidAssembly = javaTypeMapUniverseTypeDefinition.Module.Assembly; + GetTypeMapAttributeReferences (TypeMapAssemblyTargetAttributeTypeName, + m => m.IsConstructor + && m.Parameters is [{ ParameterType.FullName: "System.String" }], + MonoAndroidAssembly, + JavaTypeMapUniverseType, + out TypeMapAssemblyTargetAttribute, + out TypeMapAssemblyTargetAttributeCtor); + + var typeMapProxyAttrTypeDef = Context.GetType ("Java.Interop.TypeMapProxyAttribute"); + var typeMapProxyAttrCtor = typeMapProxyAttrTypeDef.Methods.Single (m => m.IsConstructor); + TypeMapProxyAttribute = EntryPointAssembly.MainModule.ImportReference (typeMapProxyAttrTypeDef); + TypeMapProxyAttributeCtor = EntryPointAssembly.MainModule.ImportReference (typeMapProxyAttrCtor); - TypeMapAttributeCtor = EntryPointAssembly.MainModule.ImportReference (typeMapAttributeCtor); SystemTypeType = EntryPointAssembly.MainModule.ImportReference (Context.GetType ("System.Type")); SystemStringType = EntryPointAssembly.MainModule.ImportReference (Context.GetType ("System.String")); - - Context.LogMessage (MessageContainer.CreateInfoMessage ($""" - EntryPointAssembly: {EntryPointAssembly.Name} - TypeMapUnivers: {JavaTypeMapUniverseType.FullName} - TypeMapType: {typeMapAttributeDefinition.FullName} - TypeMapAttr: {TypeMapAttribute.FullName} - TypeMapAtrtibuteCtorDefinition: {typeMapAttributeCtorDefinition.FullName} - TypeMapAtrtibuteCtor: {typeMapAttributeCtor.FullName} - System.Type: {SystemTypeType} - System.String: {SystemStringType} - """)); } @@ -82,10 +124,9 @@ protected override void ProcessAssembly (AssemblyDefinition assembly) ProcessType (assembly, type); } } - Dictionary> javaNameToTypes = new (); - Dictionary typeToJavaName = new (); List injectedAttributes = new (); + List injectedTypes = new (); private void ProcessType (AssemblyDefinition assembly, TypeDefinition type) { @@ -93,6 +134,10 @@ private void ProcessType (AssemblyDefinition assembly, TypeDefinition type) string javaName = JavaNativeTypeManager.ToJniName (type, Context); Context.LogMessage (MessageContainer.CreateInfoMessage ($"Type '{type.FullName}' has peer '{javaName}'")); injectedAttributes.Add (GenerateTypeMapAttribute (type, javaName)); + + var proxyType = GenerateTypeMapProxyType (javaName, type); + injectedTypes.Add (proxyType); + injectedAttributes.Add (GenerateTypeMapAssociationAttribute (type, proxyType)); } else { Context.LogMessage (MessageContainer.CreateInfoMessage ($"Type '{type.FullName}' has no peer")); } @@ -104,6 +149,14 @@ private void ProcessType (AssemblyDefinition assembly, TypeDefinition type) ProcessType (assembly, nested); } + CustomAttribute GenerateTypeMapAssociationAttribute (TypeDefinition type, TypeDefinition proxyType) + { + var ca = new CustomAttribute (TypeMapAssociationAttributeCtor); + ca.ConstructorArguments.Add (new (SystemTypeType, type)); + ca.ConstructorArguments.Add (new (SystemTypeType, proxyType)); + return ca; + } + CustomAttribute GenerateTypeMapAttribute (TypeDefinition type, string javaName) { CustomAttribute ca = new (TypeMapAttributeCtor); @@ -115,10 +168,46 @@ CustomAttribute GenerateTypeMapAttribute (TypeDefinition type, string javaName) protected override void EndProcess () { + Context.Annotations.GetType ().GetField ("entry_assembly", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue (Context.Annotations, MonoAndroidAssembly); + foreach(var type in injectedTypes) { + EntryPointAssembly.MainModule.Types.Add (type); + } foreach (var attr in injectedAttributes) { Context.LogMessage (MessageContainer.CreateInfoMessage ($"Injecting [{attr.AttributeType.FullName}({string.Join (", ", attr.ConstructorArguments.Select (caa => caa.ToString ()))})] into {EntryPointAssembly.Name}")); EntryPointAssembly.CustomAttributes.Add (attr); } - EntryPointAssembly.Write (Context.GetAssemblyLocation (EntryPointAssembly) + ".injected"); + EntryPointAssembly.Write (Context.GetAssemblyLocation (EntryPointAssembly) + Random.Shared.GetHexString(4) + ".injected.dll"); + + // JNIEnvInit sets Mono.Android as the entrypoint assembly. Forward the typemap logic to the user/custom assembly; + CustomAttribute targetAssembly = new (TypeMapAssemblyTargetAttributeCtor); + targetAssembly.ConstructorArguments.Add (new (SystemStringType, EntryPointAssembly.Name.FullName)); + MonoAndroidAssembly.CustomAttributes.Add (targetAssembly); + MonoAndroidAssembly.Write (Path.Combine( + Path.GetDirectoryName(Context.GetAssemblyLocation (EntryPointAssembly)), + "Mono.Android." + Random.Shared.GetHexString (4) + ".injected.dll")); + } + + TypeDefinition GenerateTypeMapProxyType (string javaClassName, TypeDefinition mappedType) + { + StringBuilder mappedName = new (mappedType.Name); + TypeDefinition? declaringType = mappedType; + while (declaringType is not null) { + mappedName.Insert (0, "_"); + mappedName.Insert (0, declaringType.Name); + if (declaringType.DeclaringType is null) + break; + declaringType = declaringType.DeclaringType; + } + var proxyType = new TypeDefinition ( + mappedType.Module.Assembly.Name.Name + "._." + declaringType.Namespace, + mappedName.ToString() + "_", + TypeAttributes.Class | TypeAttributes.NotPublic | TypeAttributes.Sealed, + EntryPointAssembly.MainModule.TypeSystem.Object); + + var ca = new CustomAttribute (TypeMapProxyAttributeCtor); + ca.ConstructorArguments.Add (new CustomAttributeArgument (SystemStringType, javaClassName)); + proxyType.CustomAttributes.Add (ca); + + return proxyType; } } diff --git a/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs b/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs index dc39f615a08..a57cf8efd92 100644 --- a/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs +++ b/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs @@ -9,6 +9,41 @@ namespace MonoDroid.Tuner { + /// + /// Marks managed types and members that participate in Java <-> .NET interop so + /// they are not removed by the linker/trimmer. + /// + /// Behavior overview: + /// - For non-framework assemblies only, proactively preserves types which implement + /// Java interop contracts and members that are invoked from Java but may have no + /// static managed references. + /// - Preserves a user-specified HttpMessageHandler type if provided via + /// LinkContext custom data key "AndroidHttpClientHandlerType", keeping its + /// public parameterless constructor. + /// - Preserves custom views referenced from Android layout XML using the map loaded + /// from "AndroidCustomViewMapFile" (LinkContext custom data), keeping required + /// constructors (Context/IAttributeSet[/Int32]) and JNI constructors + /// (IntPtr) and (IntPtr, JniHandleOwnership). + /// - For types implementing Java.Interop interfaces (IJavaObject/IJavaPeerable), + /// preserves interface invoker types (e.g., IFooInvoker), their constructors and + /// invoked methods discovered via [Register] metadata, and the Java interfaces + /// themselves unless DoNotGenerateAcw/GenerateJavaPeer=false is specified. + /// - For user types which override Java-exposed members, preserves the overridden + /// methods (they are callable from Java but often unreferenced in managed code). + /// - For generated *Implementor types (event implementors), preserves "*Handler" + /// methods referenced from Java callbacks. + /// - Honors attributes implementing Java.Interop.IJniNameProviderAttribute as a + /// preservation signal. Android.Runtime.RegisterAttribute is ignored for this + /// purpose other than reading its properties. + /// + /// Inputs via LinkContext custom data: + /// - "AndroidHttpClientHandlerType": string assembly-qualified type name to keep. + /// - "AndroidCustomViewMapFile": string path to the custom view map produced at build time. + /// + /// This handler registers assembly/type callbacks and marks items via + /// Linker Annotations, effectively seeding the mark graph so MarkStep can + /// retain the necessary interop surface. + /// public class MarkJavaObjects : BaseMarkHandler { Dictionary> module_types = new Dictionary> (); diff --git a/src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs b/src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs deleted file mode 100644 index 1924b72b7a2..00000000000 --- a/src/Microsoft.Android.Sdk.ILLink/MyBaseSubStep.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using Mono.Cecil; -using Mono.Linker; -using Mono.Linker.Steps; - -namespace Microsoft.Android.Sdk.ILLink -{ - public abstract class MyBaseSubStep - { - protected AnnotationStore Annotations => Context.Annotations; - - LinkContext? _context { get; set; } - protected LinkContext Context { - get { - Debug.Assert (_context != null); - return _context; - } - } - - public abstract SubStepTargets Targets { get; } - - public virtual void Initialize (LinkContext context) - { - _context = context; - } - - public virtual bool IsActiveFor (AssemblyDefinition assembly) => true; - - public virtual void ProcessType (TypeDefinition type) - { - } - - public virtual void ProcessField (FieldDefinition field) - { - } - - public virtual void ProcessMethod (MethodDefinition method) - { - } - - public virtual void ProcessProperty (PropertyDefinition property) - { - } - - public virtual void ProcessEvent (EventDefinition @event) - { - } - } -} diff --git a/src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs b/src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs deleted file mode 100644 index 909be8dcabc..00000000000 --- a/src/Microsoft.Android.Sdk.ILLink/PreMarkSubStepsDispatcher.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using Mono.Cecil; -using Mono.Collections.Generic; -using Mono.Linker; -using Mono.Linker.Steps; - -namespace Microsoft.Android.Sdk.ILLink -{ - public class PreMarkSubStepsDispatcher : BaseStep - { - readonly List substeps; - - CategorizedSubSteps? categorized; - CategorizedSubSteps Categorized { - get { - Debug.Assert (categorized.HasValue); - return categorized.Value; - } - } - protected override void Process() - { - InitializeSubSteps (Context); - } - protected override void ProcessAssembly(AssemblyDefinition assembly) - { - BrowseAssembly (assembly); - } - - public PreMarkSubStepsDispatcher (IEnumerable subSteps) - { - substeps = [.. subSteps]; - } - - static bool HasSubSteps (List substeps) => substeps?.Count > 0; - - void BrowseAssembly (AssemblyDefinition assembly) - { - CategorizeSubSteps (assembly); - - if (!ShouldDispatchTypes ()) - return; - - BrowseTypes (assembly.MainModule.Types); - } - - bool ShouldDispatchTypes () - { - return HasSubSteps (Categorized.on_types) - || HasSubSteps (Categorized.on_fields) - || HasSubSteps (Categorized.on_methods) - || HasSubSteps (Categorized.on_properties) - || HasSubSteps (Categorized.on_events); - } - - void BrowseTypes (Collection types) - { - foreach (TypeDefinition type in types) { - DispatchType (type); - - if (type.HasFields && HasSubSteps (Categorized.on_fields)) { - foreach (FieldDefinition field in type.Fields) - DispatchField (field); - } - - if (type.HasMethods && HasSubSteps (Categorized.on_methods)) { - foreach (MethodDefinition method in type.Methods) - DispatchMethod (method); - } - - if (type.HasProperties && HasSubSteps (Categorized.on_properties)) { - foreach (PropertyDefinition property in type.Properties) - DispatchProperty (property); - } - - if (type.HasEvents && HasSubSteps (Categorized.on_events)) { - foreach (EventDefinition @event in type.Events) - DispatchEvent (@event); - } - - if (type.HasNestedTypes) - BrowseTypes (type.NestedTypes); - } - } - - void DispatchType (TypeDefinition type) - { - foreach (var substep in Categorized.on_types) { - substep.ProcessType (type); - } - } - - void DispatchField (FieldDefinition field) - { - foreach (var substep in Categorized.on_fields) { - substep.ProcessField (field); - } - } - - void DispatchMethod (MethodDefinition method) - { - foreach (var substep in Categorized.on_methods) { - substep.ProcessMethod (method); - } - } - - void DispatchProperty (PropertyDefinition property) - { - foreach (var substep in Categorized.on_properties) { - substep.ProcessProperty (property); - } - } - - void DispatchEvent (EventDefinition @event) - { - foreach (var substep in Categorized.on_events) { - substep.ProcessEvent (@event); - } - } - - void InitializeSubSteps (LinkContext context) - { - foreach (var substep in substeps) - substep.Initialize (context); - } - - void CategorizeSubSteps (AssemblyDefinition assembly) - { - categorized = new CategorizedSubSteps { - on_assemblies = new List (), - on_types = new List (), - on_fields = new List (), - on_methods = new List (), - on_properties = new List (), - on_events = new List () - }; - - foreach (var substep in substeps) - CategorizeSubStep (substep, assembly); - } - - void CategorizeSubStep (MyBaseSubStep substep, AssemblyDefinition assembly) - { - if (!substep.IsActiveFor (assembly)) - return; - - CategorizeTarget (substep, SubStepTargets.Assembly, Categorized.on_assemblies); - CategorizeTarget (substep, SubStepTargets.Type, Categorized.on_types); - CategorizeTarget (substep, SubStepTargets.Field, Categorized.on_fields); - CategorizeTarget (substep, SubStepTargets.Method, Categorized.on_methods); - CategorizeTarget (substep, SubStepTargets.Property, Categorized.on_properties); - CategorizeTarget (substep, SubStepTargets.Event, Categorized.on_events); - } - - static void CategorizeTarget (MyBaseSubStep substep, SubStepTargets target, List list) - { - if (!Targets (substep, target)) - return; - - list.Add (substep); - } - - static bool Targets (MyBaseSubStep substep, SubStepTargets target) => (substep.Targets & target) == target; - } -} diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs b/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs index e5356ebb535..1b2664dc953 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveExportedTypes.cs @@ -10,7 +10,8 @@ namespace Mono.Tuner { - public class PreserveExportedTypes : MyBaseSubStep { + public class PreserveExportedTypes : BaseSubStep { + public override SubStepTargets Targets { get { return SubStepTargets.Field @@ -20,7 +21,7 @@ public override SubStepTargets Targets { } } - public bool IsActiveFor (AssemblyDefinition assembly) + public override bool IsActiveFor (AssemblyDefinition assembly) { if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) return false; @@ -39,13 +40,16 @@ public override void ProcessMethod (MethodDefinition method) ProcessExports (method); } - public override void ProcessProperty (PropertyDefinition property) { ProcessExports (property.GetMethod); ProcessExports (property.SetMethod); } + public override void ProcessEvent (EventDefinition @event) + { + } + void ProcessExports (ICustomAttributeProvider provider) { if (provider == null) diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs b/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs index 6dfa2131db0..2e0937abe0e 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveSubStepDispatcher.cs @@ -1,28 +1,19 @@ using System; using System.Collections.Generic; using System.Text; +using Mono.Linker.Steps; using Mono.Tuner; namespace Microsoft.Android.Sdk.ILLink { - public class PreserveSubStepDispatcher : PreMarkSubStepsDispatcher + public class PreserveSubStepDispatcher : MarkSubStepsDispatcher { public PreserveSubStepDispatcher () - : base (new MyBaseSubStep [] { + : base (new ISubStep[] { new ApplyPreserveAttribute (), new PreserveExportedTypes () }) { } } - - internal struct CategorizedSubSteps - { - public List on_assemblies { get; set; } - public List on_types { get; set; } - public List on_fields { get; set; } - public List on_methods { get; set; } - public List on_properties { get; set; } - public List on_events { get; set; } - } } diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index f0c6947f8ee..6aac6b8ed25 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -115,6 +115,8 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) if (RuntimeFeature.IsMonoRuntime) { valueManager = new AndroidValueManager (); } else if (RuntimeFeature.IsCoreClrRuntime) { + // Set the entrypoint assembly to Mono.Android.dll for the InteropServices.TypeMapping logic + Assembly.SetEntryAssembly (Assembly.GetExecutingAssembly ()); valueManager = ManagedValueManager.GetOrCreateInstance (); } else { throw new NotSupportedException ("Internal error: unknown runtime not supported"); diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index b43cc3ba31e..d02c915a040 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -39,7 +39,7 @@ public static Dictionary ManagedToJni { } } - public static partial class TypeManager { + public static partial class _TypeManager { internal static string GetClassName (IntPtr class_ptr) { IntPtr ptr = RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name (class_ptr); @@ -486,12 +486,12 @@ public static void RegisterPackages (string[] packages, Converter } [Register ("mono/android/TypeManager", DoNotGenerateAcw = true)] - internal class JavaTypeManager : Java.Lang.Object + internal class _JavaTypeManager : Java.Lang.Object { [Register ("activate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V", "")] static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) { - TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + _TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); } [UnmanagedCallersOnly] @@ -499,7 +499,7 @@ static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, In { // TODO: need a full wrapper code here, a'la JNINativeWrapper.CreateDelegate try { - TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + _TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); } catch (Exception ex) { AndroidEnvironment.UnhandledException (ex); } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index e84a518971b..fb9139257b7 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -525,4 +525,20 @@ protected override bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (t } return base.TryUnboxPeerObject (value, out result); } + + public override IJavaPeerable? CreatePeer ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers ( + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? targetType) + { + if (!reference.IsValid) + return null; + + var peer = Java.Interop.TypeManager.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType) as IJavaPeerable; + JniObjectReference.Dispose (ref reference, options); + return peer; + } } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 18b95b5ae92..706c2f4a7ac 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -101,6 +101,8 @@ + + @@ -421,26 +423,10 @@ <_SourceFiles Include="$(OutputPath)Java.Interop.*" /> <_SourceFiles Include="$(OutputPath)System.IO.Hashing.dll" /> - - - - + + + + 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 d5411d79619..c620d36ee53 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 @@ -45,8 +45,12 @@ This file contains the .NET 5-specific targets to customize ILLink https://github.com/dotnet/sdk/blob/a5393731b5b7b225692fff121f747fbbc9e8b140/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.ILLink.targets#L131 --> + + <_TrimmerCustomSteps + Include="$(_AndroidLinkerCustomStepAssembly)" + BeforeStep="MarkStep" + Type="Microsoft.Android.Sdk.ILLink.PreserveSubStepDispatcher" /> - <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveSubStepDispatcher" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.MarkJavaObjects" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.PreserveJavaExceptions" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.PreserveApplications" /> From 8841cf9321da908cdd44846928dd12da959c91c1 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:47:19 -0700 Subject: [PATCH 05/10] Wip --- .../GenerateTypeMapAttributesStep.cs | 129 +++-- .../Java.Interop/TypeMapTypeManager.cs | 474 ++++++++++++++++++ 2 files changed, 562 insertions(+), 41 deletions(-) create mode 100644 src/Mono.Android/Java.Interop/TypeMapTypeManager.cs diff --git a/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs index e586a679e2e..1409406a876 100644 --- a/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs +++ b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -18,19 +19,16 @@ namespace Microsoft.Android.Sdk.ILLink; public class GenerateTypeMapAttributesStep : BaseStep { const string TypeMapAttributeTypeName = "System.Runtime.InteropServices.TypeMapAttribute`1"; - TypeReference TypeMapAttribute; MethodReference TypeMapAttributeCtor; const string TypeMapAssociationAttributeTypeName = "System.Runtime.InteropServices.TypeMapAssociationAttribute`1"; - TypeReference TypeMapAssociationAttribute; MethodReference TypeMapAssociationAttributeCtor; const string TypeMapProxyAttributeTypeName = "Java.Interop.TypeMapProxyAttribute"; - TypeReference TypeMapProxyAttribute; + //TypeReference TypeMapProxyAttribute; MethodReference TypeMapProxyAttributeCtor; const string TypeMapAssemblyTargetAttributeTypeName = "System.Runtime.InteropServices.TypeMapAssemblyTargetAttribute`1"; - TypeReference TypeMapAssemblyTargetAttribute; MethodReference TypeMapAssemblyTargetAttributeCtor; const string JavaTypeMapUniverseTypeName = "Java.Lang.Object"; @@ -39,7 +37,7 @@ public class GenerateTypeMapAttributesStep : BaseStep TypeReference SystemTypeType { get; set; } TypeReference SystemStringType { get; set; } - AssemblyDefinition EntryPointAssembly { get; set; } + AssemblyDefinition AssemblyToInjectTypeMap { get; set; } AssemblyDefinition MonoAndroidAssembly { get; set; } void GetTypeMapAttributeReferences ( @@ -47,11 +45,10 @@ void GetTypeMapAttributeReferences ( Func ctorSelector, AssemblyDefinition addReferencesTo, TypeReference typeMapUniverse, - out TypeReference attributeType, out MethodReference ctor) { var typeMapAttributeDefinition = Context.GetType (attributeTypeName); - attributeType = addReferencesTo.MainModule.ImportReference (typeMapAttributeDefinition.MakeGenericInstanceType (typeMapUniverse)); + var attributeType = addReferencesTo.MainModule.ImportReference (typeMapAttributeDefinition.MakeGenericInstanceType (typeMapUniverse)); var typeMapAttributeCtorDefinition = typeMapAttributeDefinition.Methods .FirstOrDefault (ctorSelector) ?? throw new InvalidOperationException ($"Couldn't find {attributeTypeName}..ctor()"); @@ -74,9 +71,9 @@ void GetTypeMapAttributeReferences ( protected override void Process () { - EntryPointAssembly = Context.Annotations.GetType ().GetMethod ("GetEntryPointAssembly")?.Invoke (Context.Annotations, null) as AssemblyDefinition ?? throw new NotImplementedException ("asdfasdf NoEntryPoint"); + AssemblyToInjectTypeMap = Context.Annotations.GetType ().GetMethod ("GetEntryPointAssembly")?.Invoke (Context.Annotations, null) as AssemblyDefinition ?? throw new NotImplementedException ("asdfasdf NoEntryPoint"); var javaTypeMapUniverseTypeDefinition = Context.GetType (JavaTypeMapUniverseTypeName); - JavaTypeMapUniverseType = EntryPointAssembly.MainModule.ImportReference (javaTypeMapUniverseTypeDefinition); + JavaTypeMapUniverseType = AssemblyToInjectTypeMap.MainModule.ImportReference (javaTypeMapUniverseTypeDefinition); GetTypeMapAttributeReferences (TypeMapAttributeTypeName, m => m.IsConstructor @@ -84,9 +81,8 @@ protected override void Process () { ParameterType.FullName: "System.String" }, { ParameterType.FullName: "System.Type" }, { ParameterType.FullName: "System.Type" }], - EntryPointAssembly, + AssemblyToInjectTypeMap, JavaTypeMapUniverseType, - out TypeMapAttribute, out TypeMapAttributeCtor); GetTypeMapAttributeReferences (TypeMapAssociationAttributeTypeName, @@ -94,9 +90,8 @@ protected override void Process () && m.Parameters is [ { ParameterType.FullName: "System.Type" }, { ParameterType.FullName: "System.Type" }], - EntryPointAssembly, + AssemblyToInjectTypeMap, JavaTypeMapUniverseType, - out TypeMapAssociationAttribute, out TypeMapAssociationAttributeCtor); MonoAndroidAssembly = javaTypeMapUniverseTypeDefinition.Module.Assembly; @@ -105,16 +100,15 @@ protected override void Process () && m.Parameters is [{ ParameterType.FullName: "System.String" }], MonoAndroidAssembly, JavaTypeMapUniverseType, - out TypeMapAssemblyTargetAttribute, out TypeMapAssemblyTargetAttributeCtor); - var typeMapProxyAttrTypeDef = Context.GetType ("Java.Interop.TypeMapProxyAttribute"); + var typeMapProxyAttrTypeDef = Context.GetType (TypeMapProxyAttributeTypeName); + var typeMapProxyAttribute = AssemblyToInjectTypeMap.MainModule.ImportReference (typeMapProxyAttrTypeDef); var typeMapProxyAttrCtor = typeMapProxyAttrTypeDef.Methods.Single (m => m.IsConstructor); - TypeMapProxyAttribute = EntryPointAssembly.MainModule.ImportReference (typeMapProxyAttrTypeDef); - TypeMapProxyAttributeCtor = EntryPointAssembly.MainModule.ImportReference (typeMapProxyAttrCtor); + TypeMapProxyAttributeCtor = AssemblyToInjectTypeMap.MainModule.ImportReference (typeMapProxyAttrCtor); - SystemTypeType = EntryPointAssembly.MainModule.ImportReference (Context.GetType ("System.Type")); - SystemStringType = EntryPointAssembly.MainModule.ImportReference (Context.GetType ("System.String")); + SystemTypeType = AssemblyToInjectTypeMap.MainModule.ImportReference (Context.GetType ("System.Type")); + SystemStringType = AssemblyToInjectTypeMap.MainModule.ImportReference (Context.GetType ("System.String")); } @@ -125,19 +119,59 @@ protected override void ProcessAssembly (AssemblyDefinition assembly) } } - List injectedAttributes = new (); - List injectedTypes = new (); + // Need to find all possible mappings and pick the best before emitting + Dictionary externalMappings = new (); + // Need to inject the Proxy type, but cannot modify the assembly while iterating through types + Dictionary proxyMappings = new (); + // list of proxy types to inject into AssemblyToInjectInto in EndProcess + List typesToInject = new (); + /// + /// Selects the best type that would be mapped to in the AndroidValueManager/AndroidTypeManager + /// + private TypeDefinition PickBestTargetType (TypeDefinition existing, TypeDefinition type) + { + if (type == existing) + return existing; + // Types in Mono.Android assembly should be chosen first + if (existing.Module.Assembly.Name.Name != "Mono.Android" && + type.Module.Assembly.Name.Name == "Mono.Android") { + return type; + } + // We found the `Invoker` type *before* the declared type + // Fix things up so the abstract type is first, and the `Invoker` is considered a duplicate. + if ((type.IsAbstract || type.IsInterface) && + !existing.IsAbstract && + !existing.IsInterface && + type.IsAssignableFrom ((TypeReference) existing, Context)) { + return type; + } + // we found a generic subclass of a non-generic type + if (type.IsGenericInstance && + !existing.IsGenericInstance && + type.IsAssignableFrom ((TypeReference) existing, Context)) { + return type; + } + return existing; + } + + /// + /// Iterates through all types to find types that map to/from java types, and stores + /// them for modifying the assemblies during EndProcess + /// private void ProcessType (AssemblyDefinition assembly, TypeDefinition type) { if (type.HasJavaPeer (Context)) { string javaName = JavaNativeTypeManager.ToJniName (type, Context); + if (externalMappings.TryGetValue (javaName, out var existing)) { + externalMappings [javaName] = PickBestTargetType (existing, type); + } else { + externalMappings.Add (javaName, type); + } Context.LogMessage (MessageContainer.CreateInfoMessage ($"Type '{type.FullName}' has peer '{javaName}'")); - injectedAttributes.Add (GenerateTypeMapAttribute (type, javaName)); - var proxyType = GenerateTypeMapProxyType (javaName, type); - injectedTypes.Add (proxyType); - injectedAttributes.Add (GenerateTypeMapAssociationAttribute (type, proxyType)); + typesToInject.Add (proxyType); + proxyMappings.Add (type, proxyType); } else { Context.LogMessage (MessageContainer.CreateInfoMessage ($"Type '{type.FullName}' has no peer")); } @@ -152,8 +186,8 @@ private void ProcessType (AssemblyDefinition assembly, TypeDefinition type) CustomAttribute GenerateTypeMapAssociationAttribute (TypeDefinition type, TypeDefinition proxyType) { var ca = new CustomAttribute (TypeMapAssociationAttributeCtor); - ca.ConstructorArguments.Add (new (SystemTypeType, type)); - ca.ConstructorArguments.Add (new (SystemTypeType, proxyType)); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(proxyType))); return ca; } @@ -161,29 +195,43 @@ CustomAttribute GenerateTypeMapAttribute (TypeDefinition type, string javaName) { CustomAttribute ca = new (TypeMapAttributeCtor); ca.ConstructorArguments.Add (new (SystemStringType, javaName)); - ca.ConstructorArguments.Add (new (SystemTypeType, type)); - ca.ConstructorArguments.Add (new (SystemTypeType, type)); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); return ca; } protected override void EndProcess () { + // HACK ALERT + // We override the entry_assembly so that the TypeMapHandler in illink can have a starting point for TypeMapTargetAssemblies. + // Mono.Android should be the entrypoint assembly so that we can call Assembly.SetEntryAssembly() during application initialization. + // TODO: + // - Add support for "EntryPointAssembly"s that don't have a .entrypoint or Main() method + // - Use MSBuild logic to set the EntryPointAssembly to Mono.Android Context.Annotations.GetType ().GetField ("entry_assembly", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue (Context.Annotations, MonoAndroidAssembly); - foreach(var type in injectedTypes) { - EntryPointAssembly.MainModule.Types.Add (type); + foreach (var type in typesToInject) { + AssemblyToInjectTypeMap.MainModule.Types.Add (type); + Debug.Assert (type.Module.Assembly is not null); + } + foreach (var mapping in externalMappings) { + var attr = GenerateTypeMapAttribute (mapping.Value, mapping.Key); + Context.LogMessage (MessageContainer.CreateInfoMessage ($"Injecting [{attr.AttributeType.FullName}({string.Join (", ", attr.ConstructorArguments.Select (caa => caa.ToString ()))})] into {AssemblyToInjectTypeMap.Name}")); + AssemblyToInjectTypeMap.CustomAttributes.Add (attr); } - foreach (var attr in injectedAttributes) { - Context.LogMessage (MessageContainer.CreateInfoMessage ($"Injecting [{attr.AttributeType.FullName}({string.Join (", ", attr.ConstructorArguments.Select (caa => caa.ToString ()))})] into {EntryPointAssembly.Name}")); - EntryPointAssembly.CustomAttributes.Add (attr); + foreach (var mapping in proxyMappings) { + var attr = GenerateTypeMapAssociationAttribute (mapping.Key, mapping.Value); + Context.LogMessage (MessageContainer.CreateInfoMessage ($"Injecting [{attr.AttributeType.FullName}({string.Join (", ", attr.ConstructorArguments.Select (caa => caa.ToString ()))})] into {AssemblyToInjectTypeMap.Name}")); + AssemblyToInjectTypeMap.CustomAttributes.Add (attr); } - EntryPointAssembly.Write (Context.GetAssemblyLocation (EntryPointAssembly) + Random.Shared.GetHexString(4) + ".injected.dll"); + + AssemblyToInjectTypeMap.Write (Context.GetAssemblyLocation (AssemblyToInjectTypeMap) + Random.Shared.GetHexString (4) + ".injected.dll"); // JNIEnvInit sets Mono.Android as the entrypoint assembly. Forward the typemap logic to the user/custom assembly; CustomAttribute targetAssembly = new (TypeMapAssemblyTargetAttributeCtor); - targetAssembly.ConstructorArguments.Add (new (SystemStringType, EntryPointAssembly.Name.FullName)); + targetAssembly.ConstructorArguments.Add (new (SystemStringType, AssemblyToInjectTypeMap.Name.FullName)); MonoAndroidAssembly.CustomAttributes.Add (targetAssembly); - MonoAndroidAssembly.Write (Path.Combine( - Path.GetDirectoryName(Context.GetAssemblyLocation (EntryPointAssembly)), + MonoAndroidAssembly.Write (Path.Combine ( + Path.GetDirectoryName (Context.GetAssemblyLocation (AssemblyToInjectTypeMap)), "Mono.Android." + Random.Shared.GetHexString (4) + ".injected.dll")); } @@ -200,14 +248,13 @@ TypeDefinition GenerateTypeMapProxyType (string javaClassName, TypeDefinition ma } var proxyType = new TypeDefinition ( mappedType.Module.Assembly.Name.Name + "._." + declaringType.Namespace, - mappedName.ToString() + "_", + mappedName.ToString () + "_", TypeAttributes.Class | TypeAttributes.NotPublic | TypeAttributes.Sealed, - EntryPointAssembly.MainModule.TypeSystem.Object); + AssemblyToInjectTypeMap.MainModule.TypeSystem.Object); var ca = new CustomAttribute (TypeMapProxyAttributeCtor); ca.ConstructorArguments.Add (new CustomAttributeArgument (SystemStringType, javaClassName)); proxyType.CustomAttributes.Add (ca); - return proxyType; } } diff --git a/src/Mono.Android/Java.Interop/TypeMapTypeManager.cs b/src/Mono.Android/Java.Interop/TypeMapTypeManager.cs new file mode 100644 index 00000000000..24a6228fca5 --- /dev/null +++ b/src/Mono.Android/Java.Interop/TypeMapTypeManager.cs @@ -0,0 +1,474 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Android.Runtime; +using Microsoft.Android.Runtime; + +namespace Java.Interop +{ + public static class TypeManager + { + static IReadOnlyDictionary ProxyTypeMap { get; } = TypeMapping.GetOrCreateProxyTypeMapping (); + static IReadOnlyDictionary ExternalTypeMap { get; } = TypeMapping.GetOrCreateExternalTypeMapping (); + + internal static string GetClassName (IntPtr class_ptr) + { + IntPtr ptr = RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name (class_ptr); // goes through levels to call Class_getName on objecct + string ret = Marshal.PtrToStringAnsi (ptr)!; + RuntimeNativeMethods.monodroid_free (ptr); + + return ret; + } + + internal static string? GetJniTypeName (Type type) + { + if (!ProxyTypeMap.TryGetValue (type, out Type? proxyType)) + return null; + if (proxyType.GetCustomAttribute () is not { } attr) + return null; + return attr.JniName; + } + + class TypeNameComparer : IComparer + { + public int Compare (string x, string y) + { + if (object.ReferenceEquals (x, y)) + return 0; + if (x == null) + return -1; + if (y == null) + return 1; + + int xe = x.IndexOf (':'); + int ye = y.IndexOf (':'); + + int r = string.CompareOrdinal (x, 0, y, 0, System.Math.Max (xe, ye)); + if (r != 0) + return r; + + if (xe >= 0 && ye >= 0) + return xe - ye; + + if (xe < 0) + return x.Length - ye; + + return xe - y.Length; + } + } + + static readonly TypeNameComparer JavaNameComparer = new TypeNameComparer (); + + public static string? LookupTypeMapping (string [] mappings, string javaType) + { + int i = Array.BinarySearch (mappings, javaType, JavaNameComparer); + if (i < 0) + return null; + int c = mappings [i].IndexOf (':'); + return mappings [i].Substring (c + 1); + } + + static _JniMarshal_PPLLLL_V? cb_activate; + internal static Delegate GetActivateHandler () + { + return cb_activate ??= new _JniMarshal_PPLLLL_V (n_Activate); + } + +#if JAVA_INTEROP + internal static bool ActivationEnabled { + get { return !JniEnvironment.WithinNewObjectScope; } + } +#else // !JAVA_INTEROP + [ThreadStatic] + static bool activation_disabled; + + internal static bool ActivationEnabled { + get { return !activation_disabled; } + set { activation_disabled = !value; } + } +#endif // !JAVA_INTEROP + + [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value from parameter 'signature'.")] + static Type [] GetParameterTypes (string? signature) + { + if (String.IsNullOrEmpty (signature)) + return Array.Empty (); + string [] typenames = signature!.Split (':'); + Type [] result = new Type [typenames.Length]; + for (int i = 0; i < typenames.Length; i++) + result [i] = Type.GetType (typenames [i], throwOnError: true)!; + return result; + } + + [global::System.Diagnostics.DebuggerDisableUserUnhandledExceptions] + [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value from parameter 'typename_ptr'.")] + static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) + { + if (!global::Java.Interop.JniEnvironment.BeginMarshalMethod (jnienv, out var __envp, out var __r)) + return; + + try { + var o = Java.Lang.Object.PeekObject (jobject); + var ex = o as IJavaPeerable; + if (ex != null) { + var state = ex.JniManagedPeerState; + if (!state.HasFlag (JniManagedPeerStates.Activatable) && !state.HasFlag (JniManagedPeerStates.Replaceable)) + return; + } + if (!ActivationEnabled) { + if (Logger.LogGlobalRef) { + Logger.Log (LogLevel.Info, "monodroid-gref", + FormattableString.Invariant ($"warning: Skipping managed constructor invocation for handle 0x{jobject:x} (key_handle 0x{JNIEnv.IdentityHash (jobject):x}). Please use JNIEnv.StartCreateInstance() + JNIEnv.FinishCreateInstance() instead of JNIEnv.NewObject() and/or JNIEnv.CreateInstance().")); + } + return; + } + + Type type = Type.GetType (JNIEnv.GetString (typename_ptr, JniHandleOwnership.DoNotTransfer)!, throwOnError: true)!; + if (type.IsGenericTypeDefinition) { + throw new NotSupportedException ( + "Constructing instances of generic types from Java is not supported, as the type parameters cannot be determined.", + CreateJavaLocationException ()); + } + Type [] ptypes = GetParameterTypes (JNIEnv.GetString (signature_ptr, JniHandleOwnership.DoNotTransfer)); + var parms = JNIEnv.GetObjectArray (parameters_ptr, ptypes); + var cinfo = type.GetConstructor (ptypes); + if (cinfo == null) { + throw CreateMissingConstructorException (type, ptypes); + } + if (o != null) { + cinfo.Invoke (o, parms); + return; + } + + Activate (jobject, cinfo, parms); + } catch (global::System.Exception __e) { + __r.OnUserUnhandledException (ref __envp, __e); + return; + } finally { + global::Java.Interop.JniEnvironment.EndMarshalMethod (ref __envp); + } + } + + [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "RuntimeHelpers.GetUninitializedObject() does not statically know the return value from ConstructorInfo.DeclaringType.")] + internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? []? parms) + { + try { + var newobj = RuntimeHelpers.GetUninitializedObject (cinfo.DeclaringType!); + if (newobj is IJavaPeerable peer) { + peer.SetPeerReference (new JniObjectReference (jobject)); + } else { + throw new InvalidOperationException ($"Unsupported type: '{newobj}'"); + } + cinfo.Invoke (newobj, parms); + } catch (Exception e) { + var m = FormattableString.Invariant ( + $"Could not activate JNI Handle 0x{jobject:x} (key_handle 0x{JNIEnv.IdentityHash (jobject):x}) of Java type '{JNIEnv.GetClassNameFromInstance (jobject)}' as managed type '{cinfo?.DeclaringType?.FullName}'."); + Logger.Log (LogLevel.Warn, "monodroid", m); + Logger.Log (LogLevel.Warn, "monodroid", CreateJavaLocationException ().ToString ()); + + throw new NotSupportedException (m, e); + } + } + + static Exception CreateMissingConstructorException (Type type, Type [] ptypes) + { + var message = new System.Text.StringBuilder (); + message.Append ("Unable to find "); + if (ptypes.Length == 0) + message.Append ("the default constructor"); + else { + message.Append ("a constructor with signature (") + .Append (ptypes [0].FullName); + for (int i = 1; i < ptypes.Length; ++i) + message.Append (", ").Append (ptypes [i].FullName); + message.Append (")"); + } + message.Append (" on type ").Append (type.FullName) + .Append (". Please provide the missing constructor."); + return new NotSupportedException (message.ToString (), CreateJavaLocationException ()); + } + + static Exception CreateJavaLocationException () + { + using (var loc = new Java.Lang.Error ("Java callstack:")) + return new JavaLocationException (loc.ToString ()); + } + + [MethodImplAttribute (MethodImplOptions.InternalCall)] + static extern Type monodroid_typemap_java_to_managed (string java_type_name); + + static Type monovm_typemap_java_to_managed (string java_type_name) + { + return monodroid_typemap_java_to_managed (java_type_name); + } + + [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Value of java_type_name isn't statically known.")] + static Type? clr_typemap_java_to_managed (string java_type_name) + { + ExternalTypeMap.TryGetValue (java_type_name, out Type? value); + Console.WriteLine ($"Getting typemap for '{java_type_name}': '{value?.FullName ?? "null"}'"); + return value; + } + + internal static Type? GetJavaToManagedType (string class_name) + { + lock (TypeManagerMapDictionaries.AccessLock) { + return GetJavaToManagedTypeCore (class_name); + } + } + + static Type? GetJavaToManagedTypeCore (string class_name) + { + if (TypeManagerMapDictionaries.JniToManaged.TryGetValue (class_name, out Type? type)) { + return type; + } + + if (RuntimeFeature.IsMonoRuntime) { + type = monovm_typemap_java_to_managed (class_name); + } else if (RuntimeFeature.IsCoreClrRuntime) { + type = clr_typemap_java_to_managed (class_name); + } else { + throw new NotSupportedException ("Internal error: unknown runtime not supported"); + } + + if (type != null) { + TypeManagerMapDictionaries.JniToManaged.Add (class_name, type); + return type; + } + + // Miss message is logged in the native runtime + if (Logger.LogAssembly) + JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); + return null; + } + + internal static IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer) + { + return CreateInstance (handle, transfer, null); + } + + [UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "TypeManager.CreateProxy() does not statically know the value of the 'type' local variable.")] + [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "TypeManager.CreateProxy() does not statically know the value of the 'type' local variable.")] + internal static IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer, Type? targetType) + { + Type? type = null; + IntPtr class_ptr = JNIEnv.GetObjectClass (handle); + string class_name = GetClassName (class_ptr); + Debug.Assert (class_name == JNIEnv.GetClassNameFromInstance (handle)); + lock (TypeManagerMapDictionaries.AccessLock) { + string currentClassName = class_name; + while (class_ptr != IntPtr.Zero) { + type = GetJavaToManagedTypeCore (currentClassName); + if (type != null) { + break; + } + + IntPtr super_class_ptr = JNIEnv.GetSuperclass (class_ptr); + JNIEnv.DeleteLocalRef (class_ptr); + class_ptr = super_class_ptr; + if (class_ptr != IntPtr.Zero) { + currentClassName = GetClassName (class_ptr); + } + } + } + + if (class_ptr != IntPtr.Zero) { + JNIEnv.DeleteLocalRef (class_ptr); + class_ptr = IntPtr.Zero; + } + + if (targetType != null && + (type == null || + !targetType.IsAssignableFrom (type))) { + type = targetType; + } + + if (type == null) { + JNIEnv.DeleteRef (handle, transfer); + throw new NotSupportedException ( + FormattableString.Invariant ($"Internal error finding wrapper class for '{class_name}'. (Where is the Java.Lang.Object wrapper?!)"), + CreateJavaLocationException ()); + } + + if (type.IsInterface || type.IsAbstract) { + var invokerType = JavaObjectExtensions.GetInvokerType (type); + if (invokerType == null) + throw new NotSupportedException ("Unable to find Invoker for type '" + type.FullName + "'. Was it linked away?", + CreateJavaLocationException ()); + type = invokerType; + } + + var typeSig = JNIEnvInit.androidRuntime?.TypeManager.GetTypeSignature (type) ?? default; + if (!typeSig.IsValid || typeSig.SimpleReference == null) { + throw new ArgumentException ($"Could not determine Java type corresponding to `{type.AssemblyQualifiedName}`.", nameof (targetType)); + } + + JniObjectReference typeClass = default; + JniObjectReference handleClass = default; + try { + try { + typeClass = JniEnvironment.Types.FindClass (typeSig.SimpleReference); + } catch (Exception e) { + throw new ArgumentException ($"Could not find Java class `{typeSig.SimpleReference}`.", + nameof (targetType), + e); + } + + handleClass = JniEnvironment.Types.GetObjectClass (new JniObjectReference (handle)); + if (!JniEnvironment.Types.IsAssignableFrom (handleClass, typeClass)) { + return null; + } + } finally { + JniObjectReference.Dispose (ref handleClass); + JniObjectReference.Dispose (ref typeClass); + } + + IJavaPeerable? result = null; + + try { + result = (IJavaPeerable) CreateProxy (type, handle, transfer); + if (Runtime.IsGCUserPeer (result.PeerReference.Handle)) { + result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + } + } catch (MissingMethodException e) { + var key_handle = JNIEnv.IdentityHash (handle); + JNIEnv.DeleteRef (handle, transfer); + throw new NotSupportedException (FormattableString.Invariant ( + $"Unable to activate instance of type {type} from native handle 0x{handle:x} (key_handle 0x{key_handle:x})."), e); + } + return result; + } + + static readonly Type [] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; + static readonly Type [] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) }; + + internal static object CreateProxy ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type, + IntPtr handle, + JniHandleOwnership transfer) + { + // Skip Activator.CreateInstance() as that requires public constructors, + // and we want to hide some constructors for sanity reasons. + var peer = GetUninitializedObject (type); + BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var c = type.GetConstructor (flags, null, XAConstructorSignature, null); + if (c != null) { + c.Invoke (peer, new object [] { handle, transfer }); + return peer; + } + c = type.GetConstructor (flags, null, JIConstructorSignature, null); + if (c != null) { + JniObjectReference r = new JniObjectReference (handle); + JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; + c.Invoke (peer, new object [] { r, o }); + JNIEnv.DeleteRef (handle, transfer); + return peer; + } + GC.SuppressFinalize (peer); + throw new MissingMethodException ( + "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", + CreateJavaLocationException ()); + + static IJavaPeerable GetUninitializedObject ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type) + { + var v = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); + v.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + return v; + } + } + + public static void RegisterType (string java_class, Type t) + { + string jniFromType = JNIEnv.GetJniName (t); + lock (TypeManagerMapDictionaries.AccessLock) { + if (!TypeManagerMapDictionaries.JniToManaged.TryGetValue (java_class, out var lookup)) { + TypeManagerMapDictionaries.JniToManaged.Add (java_class, t); + if (String.Compare (jniFromType, java_class, StringComparison.OrdinalIgnoreCase) != 0) { + TypeManagerMapDictionaries.ManagedToJni.Add (t, java_class); + } + } else if (t != typeof (Java.Interop.TypeManager)) { + // skip the registration and output a warning + Logger.Log (LogLevel.Warn, "monodroid", FormattableString.Invariant ($"Type Registration Skipped for {java_class} to {t} ")); + } + + } + } + + static Dictionary>>? packageLookup; + + [MemberNotNull (nameof (packageLookup))] + static void LazyInitPackageLookup () + { + if (packageLookup == null) + packageLookup = new Dictionary>> (StringComparer.Ordinal); + } + + public static void RegisterPackage (string package, Converter lookup) + { + LazyInitPackageLookup (); + + lock (packageLookup!) { + if (!packageLookup.TryGetValue (package, out var lookups)) + packageLookup.Add (package, lookups = new List> ()); + lookups.Add (lookup); + } + } + + public static void RegisterPackages (string [] packages, Converter [] lookups) + { + LazyInitPackageLookup (); + + if (packages == null) + throw new ArgumentNullException ("packages"); + if (lookups == null) + throw new ArgumentNullException ("lookups"); + if (packages.Length != lookups.Length) + throw new ArgumentException ("`packages` and `lookups` arrays must have same number of elements."); + + lock (packageLookup!) { + for (int i = 0; i < packages.Length; ++i) { + string package = packages [i]; + var lookup = lookups [i]; + + if (!packageLookup.TryGetValue (package, out var _lookups)) + packageLookup.Add (package, _lookups = new List> ()); + _lookups.Add (lookup); + } + } + } + + [Register ("mono/android/TypeManager", DoNotGenerateAcw = true)] + internal class JavaTypeManager : Java.Lang.Object + { + [Register ("activate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V", "")] + static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) + { + TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + } + + [UnmanagedCallersOnly] + static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) + { + // TODO: need a full wrapper code here, a'la JNINativeWrapper.CreateDelegate + try { + TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + } catch (Exception ex) { + AndroidEnvironment.UnhandledException (ex); + } + } + + internal static Delegate GetActivateHandler () + { + return TypeManager.GetActivateHandler (); + } + } + } +} From 0fe20a28fe60aaad78eae560f72fe4675f24393b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:15:24 -0700 Subject: [PATCH 06/10] refactored --- .../Android.Runtime/JNIEnvInit.cs | 68 +-- src/Mono.Android/Java.Interop/TypeManager.cs | 9 +- .../TypeMapAttributeTypeManager.cs | 387 ++++++++++++++ .../TypeMapAttributeValueManager.cs | 376 ++++++++++++++ .../Java.Interop/TypeMapProxyAttribute.cs | 24 + .../Java.Interop/TypeMapTypeManager.cs | 474 ------------------ .../RuntimeFeature.cs | 4 + 7 files changed, 834 insertions(+), 508 deletions(-) create mode 100644 src/Mono.Android/Java.Interop/TypeMapAttributeTypeManager.cs create mode 100644 src/Mono.Android/Java.Interop/TypeMapAttributeValueManager.cs create mode 100644 src/Mono.Android/Java.Interop/TypeMapProxyAttribute.cs delete mode 100644 src/Mono.Android/Java.Interop/TypeMapTypeManager.cs diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 6aac6b8ed25..db80c3295ee 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -17,24 +17,25 @@ static internal class JNIEnvInit { #pragma warning disable 0649 // NOTE: Keep this in sync with the native side in src/native/common/include/managed-interface.hh - internal struct JnienvInitializeArgs { - public IntPtr javaVm; - public IntPtr env; - public IntPtr grefLoader; - public IntPtr Loader_loadClass; - public IntPtr grefClass; // TODO: remove, not needed anymore - public uint logCategories; - public int version; // TODO: remove, not needed anymore - public int grefGcThreshold; - public IntPtr grefIGCUserPeer; - public byte brokenExceptionTransitions; - public int packageNamingPolicy; - public byte ioExceptionType; - public int jniAddNativeMethodRegistrationAttributePresent; - public bool jniRemappingInUse; - public bool marshalMethodsEnabled; - public IntPtr grefGCUserPeerable; - public bool managedMarshalMethodsLookupEnabled; + internal struct JnienvInitializeArgs + { + public IntPtr javaVm; + public IntPtr env; + public IntPtr grefLoader; + public IntPtr Loader_loadClass; + public IntPtr grefClass; // TODO: remove, not needed anymore + public uint logCategories; + public int version; // TODO: remove, not needed anymore + public int grefGcThreshold; + public IntPtr grefIGCUserPeer; + public byte brokenExceptionTransitions; + public int packageNamingPolicy; + public byte ioExceptionType; + public int jniAddNativeMethodRegistrationAttributePresent; + public bool jniRemappingInUse; + public bool marshalMethodsEnabled; + public IntPtr grefGCUserPeerable; + public bool managedMarshalMethodsLookupEnabled; } #pragma warning restore 0649 @@ -62,8 +63,8 @@ static Type TypeGetType (string typeName) => var type = TypeGetType (typeName); if (type == null) { RuntimeNativeMethods.monodroid_log (LogLevel.Error, - LogCategories.Default, - $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); + LogCategories.Default, + $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); return; } @@ -96,7 +97,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) IntPtr total_timing_sequence = IntPtr.Zero; IntPtr partial_timing_sequence = IntPtr.Zero; - Logger.SetLogCategories ((LogCategories)args->logCategories); + Logger.SetLogCategories ((LogCategories) args->logCategories); gref_gc_threshold = args->grefGcThreshold; @@ -104,20 +105,27 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) MarshalMethodsEnabled = args->marshalMethodsEnabled; java_class_loader = args->grefLoader; - BoundExceptionType = (BoundExceptionType)args->ioExceptionType; + BoundExceptionType = (BoundExceptionType) args->ioExceptionType; JniRuntime.JniTypeManager typeManager; JniRuntime.JniValueManager valueManager; if (RuntimeFeature.ManagedTypeMap) { - typeManager = new ManagedTypeManager (); + typeManager = new ManagedTypeManager (); + } else if (RuntimeFeature.TypeMapAttributeTypeMap) { + typeManager = new TypeMapAttributeTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0); + } else { - typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0); + typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0); } if (RuntimeFeature.IsMonoRuntime) { valueManager = new AndroidValueManager (); } else if (RuntimeFeature.IsCoreClrRuntime) { - // Set the entrypoint assembly to Mono.Android.dll for the InteropServices.TypeMapping logic - Assembly.SetEntryAssembly (Assembly.GetExecutingAssembly ()); - valueManager = ManagedValueManager.GetOrCreateInstance (); + if (RuntimeFeature.TypeMapAttributeTypeMap) { + // Set the entrypoint assembly to Mono.Android.dll for the TypeMapAttributeTypeManager logic + Assembly.SetEntryAssembly (Assembly.GetExecutingAssembly ()); + valueManager = new TypeMapAttributeValueManager(); + } else { + valueManager = ManagedValueManager.GetOrCreateInstance (); + } } else { throw new NotSupportedException ("Internal error: unknown runtime not supported"); } @@ -135,10 +143,10 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) PropagateExceptions = args->brokenExceptionTransitions == 0; - JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; + JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy) args->packageNamingPolicy; if (args->managedMarshalMethodsLookupEnabled) { - delegate* unmanaged getFunctionPointer = &ManagedMarshalMethodsLookupTable.GetFunctionPointer; + delegate* unmanaged getFunctionPointer = &ManagedMarshalMethodsLookupTable.GetFunctionPointer; xamarin_app_init (args->env, getFunctionPointer); } @@ -146,7 +154,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) } [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern unsafe void xamarin_app_init (IntPtr env, delegate* unmanaged get_function_pointer); + static extern unsafe void xamarin_app_init (IntPtr env, delegate* unmanaged get_function_pointer); static void SetSynchronizationContext () => SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext); diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index d02c915a040..22d99ac355d 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -39,7 +39,7 @@ public static Dictionary ManagedToJni { } } - public static partial class _TypeManager { + public static partial class TypeManager { internal static string GetClassName (IntPtr class_ptr) { IntPtr ptr = RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name (class_ptr); @@ -216,7 +216,7 @@ static Exception CreateMissingConstructorException (Type type, Type[] ptypes) return new NotSupportedException (message.ToString (), CreateJavaLocationException ()); } - static Exception CreateJavaLocationException () + internal static Exception CreateJavaLocationException () { using (var loc = new Java.Lang.Error ("Java callstack:")) return new JavaLocationException (loc.ToString ()); @@ -299,6 +299,7 @@ static Type monovm_typemap_java_to_managed (string java_type_name) Type? type = null; IntPtr class_ptr = JNIEnv.GetObjectClass (handle); string? class_name = GetClassName (class_ptr); + System.Diagnostics.Debug.Assert (class_name == JNIEnv.GetClassNameFromInstance (handle)); lock (TypeManagerMapDictionaries.AccessLock) { while (class_ptr != IntPtr.Zero) { type = GetJavaToManagedTypeCore (class_name); @@ -491,7 +492,7 @@ internal class _JavaTypeManager : Java.Lang.Object [Register ("activate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V", "")] static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) { - _TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); } [UnmanagedCallersOnly] @@ -499,7 +500,7 @@ static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, In { // TODO: need a full wrapper code here, a'la JNINativeWrapper.CreateDelegate try { - _TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); } catch (Exception ex) { AndroidEnvironment.UnhandledException (ex); } diff --git a/src/Mono.Android/Java.Interop/TypeMapAttributeTypeManager.cs b/src/Mono.Android/Java.Interop/TypeMapAttributeTypeManager.cs new file mode 100644 index 00000000000..ee26be20a7e --- /dev/null +++ b/src/Mono.Android/Java.Interop/TypeMapAttributeTypeManager.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using Android.Runtime; +using Java.Interop.Tools.TypeNameMappings; + +namespace Java.Interop +{ + class TypeMapAttributeTypeManager : JniRuntime.JniTypeManager + { + struct JniRemappingReplacementMethod + { + public string target_type; + public string target_name; + public bool is_static; + }; + + bool jniAddNativeMethodRegistrationAttributePresent; + static IReadOnlyDictionary ProxyTypeMap { get; } = TypeMapping.GetOrCreateProxyTypeMapping (); + static IReadOnlyDictionary ExternalTypeMap { get; } = TypeMapping.GetOrCreateExternalTypeMapping (); + + const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; + const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; + + public TypeMapAttributeTypeManager (bool jniAddNativeMethodRegistrationAttributePresent) + { + this.jniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent; + } + + protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) + { + if (!ExternalTypeMap.TryGetValue (jniSimpleReference, out Type? value)) { + if (Logger.LogAssembly) { + // Miss message is logged in the native runtime + JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); + } + } else { + yield return value; + } + + foreach (var ti in base.GetTypesForSimpleReference (jniSimpleReference)) + yield return ti; + } + + protected override string? GetSimpleReference (Type type) + { + if (!ProxyTypeMap.TryGetValue (type, out Type? proxyType)) + return null; + if (proxyType.GetCustomAttribute () is { } attr) + return GetReplacementTypeCore(attr.JniName) ?? attr.JniName; + return null; + } + + protected override IEnumerable GetSimpleReferences (Type type) + { + if (GetSimpleReference (type) is { } str) + return [str]; + return []; + } + + protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) + { + ReadOnlySpan name = jniSimpleReference; + int slash = name.LastIndexOf ('/'); + var desugarType = new StringBuilder (jniSimpleReference.Length + "Desugar".Length); + if (slash > 0) { + desugarType.Append (name.Slice (0, slash + 1)) + .Append ("Desugar") + .Append (name.Slice (slash + 1)); + } else { + desugarType.Append ("Desugar").Append (name); + } + + var typeWithPrefix = desugarType.ToString (); + var typeWithSuffix = $"{jniSimpleReference}$-CC"; + + var replacements = new []{ + GetReplacementTypeCore (typeWithPrefix) ?? typeWithPrefix, + GetReplacementTypeCore (typeWithSuffix) ?? typeWithSuffix, + }; + + if (Logger.LogAssembly) { + var message = $"Remapping type `{jniSimpleReference}` to one one of {{ `{replacements [0]}`, `{replacements [1]}` }}"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + return replacements; + } + + protected override string? GetReplacementTypeCore (string jniSimpleReference) + { + if (!JNIEnvInit.jniRemappingInUse) { + return null; + } + + IntPtr ret = RuntimeNativeMethods._monodroid_lookup_replacement_type (jniSimpleReference); + if (ret == IntPtr.Zero) { + return null; + } + + return Marshal.PtrToStringAnsi (ret); + } + + protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) + { + if (!JNIEnvInit.jniRemappingInUse) { + return null; + } + + IntPtr retInfo = RuntimeNativeMethods._monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); + if (retInfo == IntPtr.Zero) { + return null; + } + + var method = new JniRemappingReplacementMethod (); + method = Marshal.PtrToStructure (retInfo); + var newSignature = jniMethodSignature; + + int? paramCount = null; + if (method.is_static) { + paramCount = JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature) + 1; + newSignature = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length); + } + + if (Logger.LogAssembly) { + var message = $"Remapping method `{jniSourceType}.{jniMethodName}{jniMethodSignature}` to " + + $"`{method.target_type}.{method.target_name}{newSignature}`; " + + $"param-count: {paramCount}; instance-to-static? {method.is_static}"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + + return new JniRuntime.ReplacementMethodInfo { + SourceJniType = jniSourceType, + SourceJniMethodName = jniMethodName, + SourceJniMethodSignature = jniMethodSignature, + TargetJniType = method.target_type, + TargetJniMethodName = method.target_name, + TargetJniMethodSignature = newSignature, + TargetJniMethodParameterCount = paramCount, + TargetJniMethodInstanceToStatic = method.is_static, + }; + } + + [return: DynamicallyAccessedMembers (Constructors)] + protected override Type? GetInvokerTypeCore ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + if (type.IsInterface || type.IsAbstract) { + return JavaObjectExtensions.GetInvokerType (type) + ?? base.GetInvokerTypeCore (type); + } + + return null; + } + + delegate Delegate GetCallbackHandler (); + + static MethodInfo? dynamic_callback_gen; + + // See ExportAttribute.cs + [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")] + [UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = "Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")] + static Delegate CreateDynamicCallback (MethodInfo method) + { + if (dynamic_callback_gen == null) { + var assembly = Assembly.Load ("Mono.Android.Export"); + if (assembly == null) + throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application"); + var type = assembly.GetType ("Java.Interop.DynamicCallbackCodeGenerator"); + if (type == null) + throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required type was not found."); + dynamic_callback_gen = type.GetMethod ("Create"); + if (dynamic_callback_gen == null) + throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required method was not found."); + } + return (Delegate) dynamic_callback_gen.Invoke (null, new object [] { method })!; + } + + static List sharedRegistrations = new List (); + + static bool FastRegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) + { + if (!MagicRegistrationMap.Filled) + return false; + + bool lockTaken = false; + bool rv = false; + + try { + Monitor.TryEnter (sharedRegistrations, ref lockTaken); + List registrations; + if (lockTaken) { + sharedRegistrations.Clear (); + registrations = sharedRegistrations; + } else { + registrations = new List (); + } + JniNativeMethodRegistrationArguments arguments = new JniNativeMethodRegistrationArguments (registrations, methods.ToString ()); + rv = MagicRegistrationMap.CallRegisterMethod (arguments, type.FullName!); + + if (registrations.Count > 0) + nativeClass.RegisterNativeMethods (registrations.ToArray ()); + } finally { + if (lockTaken) { + Monitor.Exit (sharedRegistrations); + } + } + + return rv; + } + + class MagicRegistrationMap + { +#pragma warning disable CS0649 // Field is never assigned to; + // assigned to in generated IL: https://github.com/xamarin/xamarin-android/blob/cbfa7e20acebd37b52ec4de9d5c1a4a66ddda799/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/MonoDroidMarkStep.cs#L204 + static Dictionary? typesMap; +#pragma warning restore CS0649 + + static void Prefill () + { + // fill code added by the linker + } + + static MagicRegistrationMap () + { + Prefill (); + } + + static public bool Filled { + get { + return typesMap != null && typesMap.Count > 0; + } + } + + internal static bool CallRegisterMethod (JniNativeMethodRegistrationArguments arguments, string typeName) + { + int idx = 0; + + if (typeName == null || !(typesMap?.TryGetValue (typeName, out idx) == true)) + return false; + + return CallRegisterMethodByIndex (arguments, idx); + } + + static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments arguments, int? typeIdx) + { + // updated by the linker to register known types + return false; + } + } + + [Obsolete ("Use RegisterNativeMembers(JniType, Type, ReadOnlySpan) instead.")] + public override void RegisterNativeMembers ( + JniType nativeClass, + [DynamicallyAccessedMembers (MethodsAndPrivateNested)] + Type type, + string? methods) => + RegisterNativeMembers (nativeClass, type, methods.AsSpan ()); + + [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)] Type type, + ReadOnlySpan methods) + { + try { + if (methods.IsEmpty) { + if (jniAddNativeMethodRegistrationAttributePresent) + base.RegisterNativeMembers (nativeClass, type, methods); + return; + } else if (FastRegisterNativeMembers (nativeClass, type, methods)) { + return; + } + + int methodCount = CountMethods (methods); + if (methodCount < 1) { + if (jniAddNativeMethodRegistrationAttributePresent) { + base.RegisterNativeMembers (nativeClass, type, methods); + } + return; + } + + JniNativeMethodRegistration [] natives = new JniNativeMethodRegistration [methodCount]; + int nativesIndex = 0; + MethodInfo []? typeMethods = null; + + ReadOnlySpan methodsSpan = methods; + bool needToRegisterNatives = false; + + while (!methodsSpan.IsEmpty) { + int newLineIndex = methodsSpan.IndexOf ('\n'); + + ReadOnlySpan methodLine = methodsSpan.Slice (0, newLineIndex != -1 ? newLineIndex : methodsSpan.Length); + if (!methodLine.IsEmpty) { + SplitMethodLine (methodLine, + out ReadOnlySpan name, + out ReadOnlySpan signature, + out ReadOnlySpan callbackString, + out ReadOnlySpan callbackDeclaringTypeString); + + Delegate? callback = null; + if (callbackString.SequenceEqual ("__export__")) { + var mname = name.Slice (2); + MethodInfo? minfo = null; + typeMethods ??= type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + foreach (var mi in typeMethods) + if (mname.SequenceEqual (mi.Name) && signature.SequenceEqual (JavaNativeTypeManager.GetJniSignature (mi))) { + minfo = mi; + break; + } + + if (minfo == null) + throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}")); + callback = CreateDynamicCallback (minfo); + needToRegisterNatives = true; + } else { + Type callbackDeclaringType = type; + if (!callbackDeclaringTypeString.IsEmpty) { + callbackDeclaringType = Type.GetType (callbackDeclaringTypeString.ToString (), throwOnError: true)!; + } + while (callbackDeclaringType.ContainsGenericParameters) { + callbackDeclaringType = callbackDeclaringType.BaseType!; + } + + GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), + callbackDeclaringType, callbackString.ToString ()); + callback = connector (); + } + + if (callback != null) { + needToRegisterNatives = true; + natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); + } + } + + methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; + } + + if (needToRegisterNatives) { + JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); + } + } catch (Exception e) { + JniEnvironment.Runtime.RaisePendingException (e); + } + } + + static int CountMethods (ReadOnlySpan methodsSpan) + { + int count = 0; + while (!methodsSpan.IsEmpty) { + count++; + + int newLineIndex = methodsSpan.IndexOf ('\n'); + methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; + } + return count; + } + + static void SplitMethodLine ( + ReadOnlySpan methodLine, + out ReadOnlySpan name, + out ReadOnlySpan signature, + out ReadOnlySpan callback, + out ReadOnlySpan callbackDeclaringType) + { + int colonIndex = methodLine.IndexOf (':'); + name = methodLine.Slice (0, colonIndex); + methodLine = methodLine.Slice (colonIndex + 1); + + colonIndex = methodLine.IndexOf (':'); + signature = methodLine.Slice (0, colonIndex); + methodLine = methodLine.Slice (colonIndex + 1); + + colonIndex = methodLine.IndexOf (':'); + callback = methodLine.Slice (0, colonIndex != -1 ? colonIndex : methodLine.Length); + + callbackDeclaringType = colonIndex != -1 ? methodLine.Slice (colonIndex + 1) : default; + } + } +} diff --git a/src/Mono.Android/Java.Interop/TypeMapAttributeValueManager.cs b/src/Mono.Android/Java.Interop/TypeMapAttributeValueManager.cs new file mode 100644 index 00000000000..d6a0c56a29f --- /dev/null +++ b/src/Mono.Android/Java.Interop/TypeMapAttributeValueManager.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; +using Android.Runtime; + +namespace Java.Interop +{ + class TypeMapAttributeValueManager : JniRuntime.JniValueManager + { + Dictionary instances = new Dictionary (); + + public override void WaitForGCBridgeProcessing () + { + if (!AndroidRuntimeInternal.BridgeProcessing) + return; + RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing (); + } + + public override IJavaPeerable? CreatePeer ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? targetType) + { + if (!reference.IsValid) + return null; + + var peer = CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType) as IJavaPeerable; + JniObjectReference.Dispose (ref reference, options); + return peer; + } + + internal static IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]Type? targetType) + { + Type? type = null; + IntPtr class_ptr = JNIEnv.GetObjectClass (handle); + string? class_name = TypeManager.GetClassName (class_ptr); + System.Diagnostics.Debug.Assert (class_name == JNIEnv.GetClassNameFromInstance (handle)); + lock (TypeManagerMapDictionaries.AccessLock) { + while (class_ptr != IntPtr.Zero) { + if (class_name != null) { + var class_signature = JniTypeSignature.Parse (class_name); + type = JNIEnvInit.androidRuntime!.TypeManager.GetType (class_signature); + if (type != null) { + break; + } + } + + IntPtr super_class_ptr = JNIEnv.GetSuperclass (class_ptr); + JNIEnv.DeleteLocalRef (class_ptr); + class_name = null; + class_ptr = super_class_ptr; + if (class_ptr != IntPtr.Zero) { + class_name = TypeManager.GetClassName (class_ptr); + } + } + } + + if (class_ptr != IntPtr.Zero) { + JNIEnv.DeleteLocalRef (class_ptr); + class_ptr = IntPtr.Zero; + } + + if (targetType != null && + (type == null || + !targetType.IsAssignableFrom (type))) { + type = targetType; + } + + if (type == null) { + class_name = JNIEnv.GetClassNameFromInstance (handle); + JNIEnv.DeleteRef (handle, transfer); + throw new NotSupportedException ( + FormattableString.Invariant ($"Internal error finding wrapper class for '{class_name}'. (Where is the Java.Lang.Object wrapper?!)"), + TypeManager.CreateJavaLocationException ()); + } + + if (type.IsInterface || type.IsAbstract) { + var invokerType = JavaObjectExtensions.GetInvokerType (type); + if (invokerType == null) + throw new NotSupportedException ("Unable to find Invoker for type '" + type.FullName + "'. Was it linked away?", + TypeManager.CreateJavaLocationException ()); + type = invokerType; + } + + var typeSig = JNIEnvInit.androidRuntime?.TypeManager.GetTypeSignature (type) ?? default; + if (!typeSig.IsValid || typeSig.SimpleReference == null) { + throw new ArgumentException ($"Could not determine Java type corresponding to `{type.AssemblyQualifiedName}`.", nameof (targetType)); + } + + JniObjectReference typeClass = default; + JniObjectReference handleClass = default; + try { + try { + typeClass = JniEnvironment.Types.FindClass (typeSig.SimpleReference); + } catch (Exception e) { + throw new ArgumentException ($"Could not find Java class `{typeSig.SimpleReference}`.", + nameof (targetType), + e); + } + + handleClass = JniEnvironment.Types.GetObjectClass (new JniObjectReference (handle)); + if (!JniEnvironment.Types.IsAssignableFrom (handleClass, typeClass)) { + return null; + } + } finally { + JniObjectReference.Dispose (ref handleClass); + JniObjectReference.Dispose (ref typeClass); + } + + IJavaPeerable? result = null; + + try { + result = (IJavaPeerable) TypeManager.CreateProxy (type, handle, transfer); + if (Java.Interop.Runtime.IsGCUserPeer (result.PeerReference.Handle)) { + result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + } + } catch (MissingMethodException e) { + var key_handle = JNIEnv.IdentityHash (handle); + JNIEnv.DeleteRef (handle, transfer); + throw new NotSupportedException (FormattableString.Invariant ( + $"Unable to activate instance of type {type} from native handle 0x{handle:x} (key_handle 0x{key_handle:x})."), e); + } + return result; + } + + public override void AddPeer (IJavaPeerable value) + { + if (value == null) + throw new ArgumentNullException (nameof (value)); + if (!value.PeerReference.IsValid) + throw new ArgumentException ("Must have a valid JNI object reference!", nameof (value)); + + var reference = value.PeerReference; + var hash = JNIEnv.IdentityHash (reference.Handle); + + AddPeer (value, reference, hash); + } + + internal void AddPeer (IJavaPeerable value, JniObjectReference reference, IntPtr hash) + { + lock (instances) { + if (!instances.TryGetValue (hash, out var targets)) { + targets = new IdentityHashTargets (value); + instances.Add (hash, targets); + return; + } + bool found = false; + for (int i = 0; i < targets.Count; ++i) { + IJavaPeerable? target; + var wref = targets [i]; + if (ShouldReplaceMapping (wref!, reference, value, out target)) { + found = true; + targets [i] = IdentityHashTargets.CreateWeakReference (value); + break; + } + if (JniEnvironment.Types.IsSameObject (value.PeerReference, target!.PeerReference)) { + found = true; + if (Logger.LogGlobalRef) { + Logger.Log (LogLevel.Info, "monodroid-gref", FormattableString.Invariant ( + $"warning: not replacing previous registered handle {target.PeerReference} with handle {reference} for key_handle 0x{hash:x}")); + } + } + } + if (!found) { + targets.Add (value); + } + } + } + + internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership transfer, out IntPtr handleField) + { + if (handle == IntPtr.Zero) { + handleField = handle; + return; + } + + var transferType = transfer & (JniHandleOwnership.DoNotTransfer | JniHandleOwnership.TransferLocalRef | JniHandleOwnership.TransferGlobalRef); + switch (transferType) { + case JniHandleOwnership.DoNotTransfer: + handleField = JNIEnv.NewGlobalRef (handle); + break; + case JniHandleOwnership.TransferLocalRef: + handleField = JNIEnv.NewGlobalRef (handle); + JNIEnv.DeleteLocalRef (handle); + break; + case JniHandleOwnership.TransferGlobalRef: + handleField = handle; + break; + default: + throw new ArgumentOutOfRangeException ("transfer", transfer, + "Invalid `transfer` value: " + transfer + " on type " + value.GetType ()); + } + if (handleField == IntPtr.Zero) + throw new InvalidOperationException ("Unable to allocate Global Reference for object '" + value.ToString () + "'!"); + + IntPtr hash = JNIEnv.IdentityHash (handleField); + value.SetJniIdentityHashCode ((int) hash); + if ((transfer & JniHandleOwnership.DoNotRegister) == 0) { + AddPeer (value, new JniObjectReference (handleField, JniObjectReferenceType.Global), hash); + } + + if (Logger.LogGlobalRef) { + RuntimeNativeMethods._monodroid_gref_log ( + FormattableString.Invariant ( + $"handle 0x{handleField:x}; key_handle 0x{hash:x}: Java Type: `{JNIEnv.GetClassNameFromInstance (handleField)}`; MCW type: `{value.GetType ().FullName}`\n")); + } + } + + bool ShouldReplaceMapping (WeakReference current, JniObjectReference reference, IJavaPeerable value, out IJavaPeerable? target) + { + target = null; + + if (current == null) + return true; + + // Target has been GC'd; see also FIXME, above, in finalizer + if (!current.TryGetTarget (out target) || target == null) + return true; + + // It's possible that the instance was GC'd, but the finalizer + // hasn't executed yet, so the `instances` entry is stale. + if (!target.PeerReference.IsValid) + return true; + + if (!JniEnvironment.Types.IsSameObject (target.PeerReference, reference)) + return false; + + // JNIEnv.NewObject/JNIEnv.CreateInstance() compatibility. + // When two MCW's are created for one Java instance [0], + // we want the 2nd MCW to replace the 1st, as the 2nd is + // the one the dev created; the 1st is an implicit intermediary. + // + // Meanwhile, a new "replaceable" instance should *not* replace an + // existing "replaceable" instance; see dotnet/android#9862. + // + // [0]: If Java ctor invokes overridden virtual method, we'll + // transition into managed code w/o a registered instance, and + // thus will create an "intermediary" via + // (IntPtr, JniHandleOwnership) .ctor. + if (target.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable) && + !value.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable)) { + return true; + } + + return false; + } + + public override void RemovePeer (IJavaPeerable value) + { + if (value == null) + throw new ArgumentNullException (nameof (value)); + + var reference = value.PeerReference; + if (!reference.IsValid) { + // Likely an idempotent DIspose(); ignore. + return; + } + var hash = JNIEnv.IdentityHash (reference.Handle); + + RemovePeer (value, hash); + } + + internal void RemovePeer (IJavaPeerable value, IntPtr hash) + { + lock (instances) { + if (!instances.TryGetValue (hash, out var targets)) { + return; + } + for (int i = targets.Count - 1; i >= 0; i--) { + var wref = targets [i]; + if (!wref!.TryGetTarget (out var target)) { + // wref is invalidated; remove it. + targets.RemoveAt (i); + continue; + } + if (!object.ReferenceEquals (target, value)) { + continue; + } + targets.RemoveAt (i); + } + if (targets.Count == 0) { + instances.Remove (hash); + } + } + } + + public override IJavaPeerable? PeekPeer (JniObjectReference reference) + { + if (!reference.IsValid) + return null; + + var hash = JNIEnv.IdentityHash (reference.Handle); + lock (instances) { + if (instances.TryGetValue (hash, out var targets)) { + for (int i = targets.Count - 1; i >= 0; i--) { + var wref = targets [i]; + if (!wref!.TryGetTarget (out var result) || !result.PeerReference.IsValid) { + targets.RemoveAt (i); + continue; + } + if (!JniEnvironment.Types.IsSameObject (reference, result.PeerReference)) + continue; + return result; + } + } + } + return null; + } + + public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object? []? argumentValues) + { + TypeManager.Activate (reference.Handle, cinfo, argumentValues); + } + + protected override bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (true)] out object? result) + { + var proxy = value as JavaProxyThrowable; + if (proxy != null) { + result = proxy.InnerException; + return true; + } + return base.TryUnboxPeerObject (value, out result); + } + + public override void CollectPeers () + { + GC.Collect (); + } + + public override void FinalizePeer (IJavaPeerable value) + { + if (value == null) + throw new ArgumentNullException (nameof (value)); + + if (Logger.LogGlobalRef) { + RuntimeNativeMethods._monodroid_gref_log ( + string.Format (CultureInfo.InvariantCulture, + "Finalizing Instance.Type={0} PeerReference={1} IdentityHashCode=0x{2:x} Instance=0x{3:x}", + value.GetType ().ToString (), + value.PeerReference.ToString (), + value.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (value))); + } + + // FIXME: need hash cleanup mechanism. + // Finalization occurs after a test of java persistence. If the + // handle still contains a java reference, we can't finalize the + // object and should "resurrect" it. + if (value.PeerReference.IsValid) { + GC.ReRegisterForFinalize (value); + } else { + RemovePeer (value, (IntPtr) value.JniIdentityHashCode); + value.SetPeerReference (new JniObjectReference ()); + value.Finalized (); + } + } + + public override List GetSurfacedPeers () + { + lock (instances) { + var surfacedPeers = new List (instances.Count); + foreach (var e in instances) { + for (int i = 0; i < e.Value.Count; i++) { + var value = e.Value [i]; + surfacedPeers.Add (new JniSurfacedPeerInfo (e.Key.ToInt32 (), value!)); + } + } + return surfacedPeers; + } + } + } +} diff --git a/src/Mono.Android/Java.Interop/TypeMapProxyAttribute.cs b/src/Mono.Android/Java.Interop/TypeMapProxyAttribute.cs new file mode 100644 index 00000000000..24d87c69e7f --- /dev/null +++ b/src/Mono.Android/Java.Interop/TypeMapProxyAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace Java.Interop +{ + public abstract class TypeMapProxyBaseAttribute : Attribute + { + public abstract string JniName { get; } + } + + public abstract class TypeMapProxyAttribute : TypeMapProxyBaseAttribute + { + public TypeMapProxyAttribute (string jniName) + { + if (string.IsNullOrEmpty (jniName)) + throw new ArgumentException ("must not be null or empty", nameof (jniName)); + JniName = jniName; + } + + public override string JniName { get; } + } +} diff --git a/src/Mono.Android/Java.Interop/TypeMapTypeManager.cs b/src/Mono.Android/Java.Interop/TypeMapTypeManager.cs deleted file mode 100644 index 24a6228fca5..00000000000 --- a/src/Mono.Android/Java.Interop/TypeMapTypeManager.cs +++ /dev/null @@ -1,474 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using Android.Runtime; -using Microsoft.Android.Runtime; - -namespace Java.Interop -{ - public static class TypeManager - { - static IReadOnlyDictionary ProxyTypeMap { get; } = TypeMapping.GetOrCreateProxyTypeMapping (); - static IReadOnlyDictionary ExternalTypeMap { get; } = TypeMapping.GetOrCreateExternalTypeMapping (); - - internal static string GetClassName (IntPtr class_ptr) - { - IntPtr ptr = RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name (class_ptr); // goes through levels to call Class_getName on objecct - string ret = Marshal.PtrToStringAnsi (ptr)!; - RuntimeNativeMethods.monodroid_free (ptr); - - return ret; - } - - internal static string? GetJniTypeName (Type type) - { - if (!ProxyTypeMap.TryGetValue (type, out Type? proxyType)) - return null; - if (proxyType.GetCustomAttribute () is not { } attr) - return null; - return attr.JniName; - } - - class TypeNameComparer : IComparer - { - public int Compare (string x, string y) - { - if (object.ReferenceEquals (x, y)) - return 0; - if (x == null) - return -1; - if (y == null) - return 1; - - int xe = x.IndexOf (':'); - int ye = y.IndexOf (':'); - - int r = string.CompareOrdinal (x, 0, y, 0, System.Math.Max (xe, ye)); - if (r != 0) - return r; - - if (xe >= 0 && ye >= 0) - return xe - ye; - - if (xe < 0) - return x.Length - ye; - - return xe - y.Length; - } - } - - static readonly TypeNameComparer JavaNameComparer = new TypeNameComparer (); - - public static string? LookupTypeMapping (string [] mappings, string javaType) - { - int i = Array.BinarySearch (mappings, javaType, JavaNameComparer); - if (i < 0) - return null; - int c = mappings [i].IndexOf (':'); - return mappings [i].Substring (c + 1); - } - - static _JniMarshal_PPLLLL_V? cb_activate; - internal static Delegate GetActivateHandler () - { - return cb_activate ??= new _JniMarshal_PPLLLL_V (n_Activate); - } - -#if JAVA_INTEROP - internal static bool ActivationEnabled { - get { return !JniEnvironment.WithinNewObjectScope; } - } -#else // !JAVA_INTEROP - [ThreadStatic] - static bool activation_disabled; - - internal static bool ActivationEnabled { - get { return !activation_disabled; } - set { activation_disabled = !value; } - } -#endif // !JAVA_INTEROP - - [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value from parameter 'signature'.")] - static Type [] GetParameterTypes (string? signature) - { - if (String.IsNullOrEmpty (signature)) - return Array.Empty (); - string [] typenames = signature!.Split (':'); - Type [] result = new Type [typenames.Length]; - for (int i = 0; i < typenames.Length; i++) - result [i] = Type.GetType (typenames [i], throwOnError: true)!; - return result; - } - - [global::System.Diagnostics.DebuggerDisableUserUnhandledExceptions] - [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value from parameter 'typename_ptr'.")] - static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) - { - if (!global::Java.Interop.JniEnvironment.BeginMarshalMethod (jnienv, out var __envp, out var __r)) - return; - - try { - var o = Java.Lang.Object.PeekObject (jobject); - var ex = o as IJavaPeerable; - if (ex != null) { - var state = ex.JniManagedPeerState; - if (!state.HasFlag (JniManagedPeerStates.Activatable) && !state.HasFlag (JniManagedPeerStates.Replaceable)) - return; - } - if (!ActivationEnabled) { - if (Logger.LogGlobalRef) { - Logger.Log (LogLevel.Info, "monodroid-gref", - FormattableString.Invariant ($"warning: Skipping managed constructor invocation for handle 0x{jobject:x} (key_handle 0x{JNIEnv.IdentityHash (jobject):x}). Please use JNIEnv.StartCreateInstance() + JNIEnv.FinishCreateInstance() instead of JNIEnv.NewObject() and/or JNIEnv.CreateInstance().")); - } - return; - } - - Type type = Type.GetType (JNIEnv.GetString (typename_ptr, JniHandleOwnership.DoNotTransfer)!, throwOnError: true)!; - if (type.IsGenericTypeDefinition) { - throw new NotSupportedException ( - "Constructing instances of generic types from Java is not supported, as the type parameters cannot be determined.", - CreateJavaLocationException ()); - } - Type [] ptypes = GetParameterTypes (JNIEnv.GetString (signature_ptr, JniHandleOwnership.DoNotTransfer)); - var parms = JNIEnv.GetObjectArray (parameters_ptr, ptypes); - var cinfo = type.GetConstructor (ptypes); - if (cinfo == null) { - throw CreateMissingConstructorException (type, ptypes); - } - if (o != null) { - cinfo.Invoke (o, parms); - return; - } - - Activate (jobject, cinfo, parms); - } catch (global::System.Exception __e) { - __r.OnUserUnhandledException (ref __envp, __e); - return; - } finally { - global::Java.Interop.JniEnvironment.EndMarshalMethod (ref __envp); - } - } - - [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "RuntimeHelpers.GetUninitializedObject() does not statically know the return value from ConstructorInfo.DeclaringType.")] - internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? []? parms) - { - try { - var newobj = RuntimeHelpers.GetUninitializedObject (cinfo.DeclaringType!); - if (newobj is IJavaPeerable peer) { - peer.SetPeerReference (new JniObjectReference (jobject)); - } else { - throw new InvalidOperationException ($"Unsupported type: '{newobj}'"); - } - cinfo.Invoke (newobj, parms); - } catch (Exception e) { - var m = FormattableString.Invariant ( - $"Could not activate JNI Handle 0x{jobject:x} (key_handle 0x{JNIEnv.IdentityHash (jobject):x}) of Java type '{JNIEnv.GetClassNameFromInstance (jobject)}' as managed type '{cinfo?.DeclaringType?.FullName}'."); - Logger.Log (LogLevel.Warn, "monodroid", m); - Logger.Log (LogLevel.Warn, "monodroid", CreateJavaLocationException ().ToString ()); - - throw new NotSupportedException (m, e); - } - } - - static Exception CreateMissingConstructorException (Type type, Type [] ptypes) - { - var message = new System.Text.StringBuilder (); - message.Append ("Unable to find "); - if (ptypes.Length == 0) - message.Append ("the default constructor"); - else { - message.Append ("a constructor with signature (") - .Append (ptypes [0].FullName); - for (int i = 1; i < ptypes.Length; ++i) - message.Append (", ").Append (ptypes [i].FullName); - message.Append (")"); - } - message.Append (" on type ").Append (type.FullName) - .Append (". Please provide the missing constructor."); - return new NotSupportedException (message.ToString (), CreateJavaLocationException ()); - } - - static Exception CreateJavaLocationException () - { - using (var loc = new Java.Lang.Error ("Java callstack:")) - return new JavaLocationException (loc.ToString ()); - } - - [MethodImplAttribute (MethodImplOptions.InternalCall)] - static extern Type monodroid_typemap_java_to_managed (string java_type_name); - - static Type monovm_typemap_java_to_managed (string java_type_name) - { - return monodroid_typemap_java_to_managed (java_type_name); - } - - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Value of java_type_name isn't statically known.")] - static Type? clr_typemap_java_to_managed (string java_type_name) - { - ExternalTypeMap.TryGetValue (java_type_name, out Type? value); - Console.WriteLine ($"Getting typemap for '{java_type_name}': '{value?.FullName ?? "null"}'"); - return value; - } - - internal static Type? GetJavaToManagedType (string class_name) - { - lock (TypeManagerMapDictionaries.AccessLock) { - return GetJavaToManagedTypeCore (class_name); - } - } - - static Type? GetJavaToManagedTypeCore (string class_name) - { - if (TypeManagerMapDictionaries.JniToManaged.TryGetValue (class_name, out Type? type)) { - return type; - } - - if (RuntimeFeature.IsMonoRuntime) { - type = monovm_typemap_java_to_managed (class_name); - } else if (RuntimeFeature.IsCoreClrRuntime) { - type = clr_typemap_java_to_managed (class_name); - } else { - throw new NotSupportedException ("Internal error: unknown runtime not supported"); - } - - if (type != null) { - TypeManagerMapDictionaries.JniToManaged.Add (class_name, type); - return type; - } - - // Miss message is logged in the native runtime - if (Logger.LogAssembly) - JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); - return null; - } - - internal static IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer) - { - return CreateInstance (handle, transfer, null); - } - - [UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "TypeManager.CreateProxy() does not statically know the value of the 'type' local variable.")] - [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "TypeManager.CreateProxy() does not statically know the value of the 'type' local variable.")] - internal static IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer, Type? targetType) - { - Type? type = null; - IntPtr class_ptr = JNIEnv.GetObjectClass (handle); - string class_name = GetClassName (class_ptr); - Debug.Assert (class_name == JNIEnv.GetClassNameFromInstance (handle)); - lock (TypeManagerMapDictionaries.AccessLock) { - string currentClassName = class_name; - while (class_ptr != IntPtr.Zero) { - type = GetJavaToManagedTypeCore (currentClassName); - if (type != null) { - break; - } - - IntPtr super_class_ptr = JNIEnv.GetSuperclass (class_ptr); - JNIEnv.DeleteLocalRef (class_ptr); - class_ptr = super_class_ptr; - if (class_ptr != IntPtr.Zero) { - currentClassName = GetClassName (class_ptr); - } - } - } - - if (class_ptr != IntPtr.Zero) { - JNIEnv.DeleteLocalRef (class_ptr); - class_ptr = IntPtr.Zero; - } - - if (targetType != null && - (type == null || - !targetType.IsAssignableFrom (type))) { - type = targetType; - } - - if (type == null) { - JNIEnv.DeleteRef (handle, transfer); - throw new NotSupportedException ( - FormattableString.Invariant ($"Internal error finding wrapper class for '{class_name}'. (Where is the Java.Lang.Object wrapper?!)"), - CreateJavaLocationException ()); - } - - if (type.IsInterface || type.IsAbstract) { - var invokerType = JavaObjectExtensions.GetInvokerType (type); - if (invokerType == null) - throw new NotSupportedException ("Unable to find Invoker for type '" + type.FullName + "'. Was it linked away?", - CreateJavaLocationException ()); - type = invokerType; - } - - var typeSig = JNIEnvInit.androidRuntime?.TypeManager.GetTypeSignature (type) ?? default; - if (!typeSig.IsValid || typeSig.SimpleReference == null) { - throw new ArgumentException ($"Could not determine Java type corresponding to `{type.AssemblyQualifiedName}`.", nameof (targetType)); - } - - JniObjectReference typeClass = default; - JniObjectReference handleClass = default; - try { - try { - typeClass = JniEnvironment.Types.FindClass (typeSig.SimpleReference); - } catch (Exception e) { - throw new ArgumentException ($"Could not find Java class `{typeSig.SimpleReference}`.", - nameof (targetType), - e); - } - - handleClass = JniEnvironment.Types.GetObjectClass (new JniObjectReference (handle)); - if (!JniEnvironment.Types.IsAssignableFrom (handleClass, typeClass)) { - return null; - } - } finally { - JniObjectReference.Dispose (ref handleClass); - JniObjectReference.Dispose (ref typeClass); - } - - IJavaPeerable? result = null; - - try { - result = (IJavaPeerable) CreateProxy (type, handle, transfer); - if (Runtime.IsGCUserPeer (result.PeerReference.Handle)) { - result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); - } - } catch (MissingMethodException e) { - var key_handle = JNIEnv.IdentityHash (handle); - JNIEnv.DeleteRef (handle, transfer); - throw new NotSupportedException (FormattableString.Invariant ( - $"Unable to activate instance of type {type} from native handle 0x{handle:x} (key_handle 0x{key_handle:x})."), e); - } - return result; - } - - static readonly Type [] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; - static readonly Type [] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) }; - - internal static object CreateProxy ( - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type type, - IntPtr handle, - JniHandleOwnership transfer) - { - // Skip Activator.CreateInstance() as that requires public constructors, - // and we want to hide some constructors for sanity reasons. - var peer = GetUninitializedObject (type); - BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - var c = type.GetConstructor (flags, null, XAConstructorSignature, null); - if (c != null) { - c.Invoke (peer, new object [] { handle, transfer }); - return peer; - } - c = type.GetConstructor (flags, null, JIConstructorSignature, null); - if (c != null) { - JniObjectReference r = new JniObjectReference (handle); - JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; - c.Invoke (peer, new object [] { r, o }); - JNIEnv.DeleteRef (handle, transfer); - return peer; - } - GC.SuppressFinalize (peer); - throw new MissingMethodException ( - "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", - CreateJavaLocationException ()); - - static IJavaPeerable GetUninitializedObject ( - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type type) - { - var v = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); - v.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); - return v; - } - } - - public static void RegisterType (string java_class, Type t) - { - string jniFromType = JNIEnv.GetJniName (t); - lock (TypeManagerMapDictionaries.AccessLock) { - if (!TypeManagerMapDictionaries.JniToManaged.TryGetValue (java_class, out var lookup)) { - TypeManagerMapDictionaries.JniToManaged.Add (java_class, t); - if (String.Compare (jniFromType, java_class, StringComparison.OrdinalIgnoreCase) != 0) { - TypeManagerMapDictionaries.ManagedToJni.Add (t, java_class); - } - } else if (t != typeof (Java.Interop.TypeManager)) { - // skip the registration and output a warning - Logger.Log (LogLevel.Warn, "monodroid", FormattableString.Invariant ($"Type Registration Skipped for {java_class} to {t} ")); - } - - } - } - - static Dictionary>>? packageLookup; - - [MemberNotNull (nameof (packageLookup))] - static void LazyInitPackageLookup () - { - if (packageLookup == null) - packageLookup = new Dictionary>> (StringComparer.Ordinal); - } - - public static void RegisterPackage (string package, Converter lookup) - { - LazyInitPackageLookup (); - - lock (packageLookup!) { - if (!packageLookup.TryGetValue (package, out var lookups)) - packageLookup.Add (package, lookups = new List> ()); - lookups.Add (lookup); - } - } - - public static void RegisterPackages (string [] packages, Converter [] lookups) - { - LazyInitPackageLookup (); - - if (packages == null) - throw new ArgumentNullException ("packages"); - if (lookups == null) - throw new ArgumentNullException ("lookups"); - if (packages.Length != lookups.Length) - throw new ArgumentException ("`packages` and `lookups` arrays must have same number of elements."); - - lock (packageLookup!) { - for (int i = 0; i < packages.Length; ++i) { - string package = packages [i]; - var lookup = lookups [i]; - - if (!packageLookup.TryGetValue (package, out var _lookups)) - packageLookup.Add (package, _lookups = new List> ()); - _lookups.Add (lookup); - } - } - } - - [Register ("mono/android/TypeManager", DoNotGenerateAcw = true)] - internal class JavaTypeManager : Java.Lang.Object - { - [Register ("activate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V", "")] - static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) - { - TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); - } - - [UnmanagedCallersOnly] - static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) - { - // TODO: need a full wrapper code here, a'la JNINativeWrapper.CreateDelegate - try { - TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); - } catch (Exception ex) { - AndroidEnvironment.UnhandledException (ex); - } - } - - internal static Delegate GetActivateHandler () - { - return TypeManager.GetActivateHandler (); - } - } - } -} diff --git a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs index a25462d9b29..19ceb6a87e3 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs @@ -15,6 +15,10 @@ static class RuntimeFeature internal static bool ManagedTypeMap { get; } = AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (ManagedTypeMap)}", out bool isEnabled) ? isEnabled : ManagedTypeMapEnabledByDefault; + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (TypeMapAttributeTypeMap)}")] + internal static bool TypeMapAttributeTypeMap { get; } = + AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (TypeMapAttributeTypeMap)}", out bool isEnabled) ? isEnabled : ManagedTypeMapEnabledByDefault; + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (IsMonoRuntime)}")] internal static bool IsMonoRuntime { get; } = AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (IsMonoRuntime)}", out bool isEnabled) ? isEnabled : IsMonoRuntimeEnabledByDefault; From e47dc0b7abeba3a0212d6d5d3b1de34fe443e1b5 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:45:39 -0700 Subject: [PATCH 07/10] Use TypeMapTypeManager by default on CoreClr --- src/Mono.Android/Android.Runtime/JNIEnvInit.cs | 17 ++++++----------- src/Mono.Android/Mono.Android.csproj | 3 ++- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index db80c3295ee..c62c612ebcc 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -108,24 +108,19 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) BoundExceptionType = (BoundExceptionType) args->ioExceptionType; JniRuntime.JniTypeManager typeManager; JniRuntime.JniValueManager valueManager; - if (RuntimeFeature.ManagedTypeMap) { - typeManager = new ManagedTypeManager (); - } else if (RuntimeFeature.TypeMapAttributeTypeMap) { + if (RuntimeFeature.IsCoreClrRuntime) { typeManager = new TypeMapAttributeTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0); - + } else if (RuntimeFeature.ManagedTypeMap) { + typeManager = new ManagedTypeManager (); } else { typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0); } if (RuntimeFeature.IsMonoRuntime) { valueManager = new AndroidValueManager (); } else if (RuntimeFeature.IsCoreClrRuntime) { - if (RuntimeFeature.TypeMapAttributeTypeMap) { - // Set the entrypoint assembly to Mono.Android.dll for the TypeMapAttributeTypeManager logic - Assembly.SetEntryAssembly (Assembly.GetExecutingAssembly ()); - valueManager = new TypeMapAttributeValueManager(); - } else { - valueManager = ManagedValueManager.GetOrCreateInstance (); - } + // Set the entrypoint assembly to Mono.Android.dll for the TypeMapAttributeTypeManager logic + Assembly.SetEntryAssembly (Assembly.GetExecutingAssembly ()); + valueManager = new TypeMapAttributeValueManager (); } else { throw new NotSupportedException ("Internal error: unknown runtime not supported"); } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 706c2f4a7ac..8fa376c171d 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -101,8 +101,9 @@ + + - From 99064b1018eef1d33752e09900555cc248fa57b3 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:55:51 -0700 Subject: [PATCH 08/10] Add GenerateTypeMapAttributesStep --- .../targets/Microsoft.Android.Sdk.ILLink.targets | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 c620d36ee53..b1009845edd 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 @@ -46,11 +46,11 @@ This file contains the .NET 5-specific targets to customize ILLink --> - <_TrimmerCustomSteps - Include="$(_AndroidLinkerCustomStepAssembly)" - BeforeStep="MarkStep" - Type="Microsoft.Android.Sdk.ILLink.PreserveSubStepDispatcher" /> + <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" + Type="Microsoft.Android.Sdk.ILLink.GenerateTypeMapAttributesStep" + BeforeStep="MarkStep" /> + <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveSubStepDispatcher" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.MarkJavaObjects" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.PreserveJavaExceptions" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.PreserveApplications" /> From 42755277d80045a3cfb7315bb94197b16cab9f4b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:05:26 -0700 Subject: [PATCH 09/10] Go back to old Java.Interop --- external/Java.Interop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Java.Interop b/external/Java.Interop index 56937c6d9fd..f21b003d0c6 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 56937c6d9fd62742ca5d9adfec7fce00ece9f93c +Subproject commit f21b003d0c6325d157f681d2ac89221ad5ab17fc From f847823160cae2dcc5be42431673b217b8116cb4 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Sun, 26 Oct 2025 22:28:39 -0700 Subject: [PATCH 10/10] Add comments to GenerateTypeMapAttributesStep --- .../GenerateTypeMapAttributesStep.cs | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs index 1409406a876..02491ffee2c 100644 --- a/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs +++ b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs @@ -14,7 +14,17 @@ namespace Microsoft.Android.Sdk.ILLink; /// -/// Generate TypeMap attributes using the .NET 10 TypeMapAttribute and TypeMapAssociationAttribute +/// Generates TypeMap attributes using the .NET 10 TypeMapAttribute and TypeMapAssociationAttribute +/// Find the best .NET type that maps to each Java type, and create the following code: +/// +/// [assembly: TypeMapAttribute("java/lang/JavaClas", typeof(Java.Lang.JavaClass), typeof(Java.Lang.JavaClass))] +/// [assembly: TypeMapAssociationAttribute(typeof(Java.Lang.JavaClass), typeof(Java.Lang.JavaClassProxy))] +/// +/// [TypeMapProxy("java/lang/JavaClass")] +/// class JavaClassProxy { +/// Target +/// } +/// /// public class GenerateTypeMapAttributesStep : BaseStep { @@ -40,6 +50,10 @@ public class GenerateTypeMapAttributesStep : BaseStep AssemblyDefinition AssemblyToInjectTypeMap { get; set; } AssemblyDefinition MonoAndroidAssembly { get; set; } + /// + /// Generates the MethodReference for the given TypeMap attribute constructor, + /// adding the necessary TypeRefs into the given assembly. + /// void GetTypeMapAttributeReferences ( string attributeTypeName, Func ctorSelector, @@ -183,23 +197,6 @@ private void ProcessType (AssemblyDefinition assembly, TypeDefinition type) ProcessType (assembly, nested); } - CustomAttribute GenerateTypeMapAssociationAttribute (TypeDefinition type, TypeDefinition proxyType) - { - var ca = new CustomAttribute (TypeMapAssociationAttributeCtor); - ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); - ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(proxyType))); - return ca; - } - - CustomAttribute GenerateTypeMapAttribute (TypeDefinition type, string javaName) - { - CustomAttribute ca = new (TypeMapAttributeCtor); - ca.ConstructorArguments.Add (new (SystemStringType, javaName)); - ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); - ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); - return ca; - } - protected override void EndProcess () { // HACK ALERT @@ -235,6 +232,38 @@ protected override void EndProcess () "Mono.Android." + Random.Shared.GetHexString (4) + ".injected.dll")); } + /// + /// Generates [TypeMapAssociation(typeof(type), typeof(proxyType))] + /// + CustomAttribute GenerateTypeMapAssociationAttribute (TypeDefinition type, TypeDefinition proxyType) + { + var ca = new CustomAttribute (TypeMapAssociationAttributeCtor); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(proxyType))); + return ca; + } + + /// + /// Generates [TypeMap("javaName", typeof(type), typeof(type))] + /// + CustomAttribute GenerateTypeMapAttribute (TypeDefinition type, string javaName) + { + CustomAttribute ca = new (TypeMapAttributeCtor); + ca.ConstructorArguments.Add (new (SystemStringType, javaName)); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); + ca.ConstructorArguments.Add (new (SystemTypeType, AssemblyToInjectTypeMap.MainModule.ImportReference(type))); + return ca; + } + + /// + /// Generates + /// + /// [TypeMapProxy("javaClassName")] + /// sealed class AssemblyName._.mappedTypeFullName_ + /// { + /// } + /// + /// TypeDefinition GenerateTypeMapProxyType (string javaClassName, TypeDefinition mappedType) { StringBuilder mappedName = new (mappedType.Name);