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/GenerateTypeMapAttributesStep.cs b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs
new file mode 100644
index 00000000000..02491ffee2c
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.ILLink/GenerateTypeMapAttributesStep.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Java.Interop.Tools.Cecil;
+using Java.Interop.Tools.TypeNameMappings;
+using Mono.Cecil;
+using Mono.Cecil.Rocks;
+using Mono.Linker;
+using Mono.Linker.Steps;
+
+namespace Microsoft.Android.Sdk.ILLink;
+
+///
+/// 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
+{
+ const string TypeMapAttributeTypeName = "System.Runtime.InteropServices.TypeMapAttribute`1";
+ MethodReference TypeMapAttributeCtor;
+
+ const string TypeMapAssociationAttributeTypeName = "System.Runtime.InteropServices.TypeMapAssociationAttribute`1";
+ MethodReference TypeMapAssociationAttributeCtor;
+
+ const string TypeMapProxyAttributeTypeName = "Java.Interop.TypeMapProxyAttribute";
+ //TypeReference TypeMapProxyAttribute;
+ MethodReference TypeMapProxyAttributeCtor;
+
+ const string TypeMapAssemblyTargetAttributeTypeName = "System.Runtime.InteropServices.TypeMapAssemblyTargetAttribute`1";
+ MethodReference TypeMapAssemblyTargetAttributeCtor;
+
+ const string JavaTypeMapUniverseTypeName = "Java.Lang.Object";
+ TypeReference JavaTypeMapUniverseType { get; set; }
+
+ TypeReference SystemTypeType { get; set; }
+ TypeReference SystemStringType { get; set; }
+
+ 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,
+ AssemblyDefinition addReferencesTo,
+ TypeReference typeMapUniverse,
+ out MethodReference ctor)
+ {
+ var typeMapAttributeDefinition = Context.GetType (attributeTypeName);
+ var attributeType = addReferencesTo.MainModule.ImportReference (typeMapAttributeDefinition.MakeGenericInstanceType (typeMapUniverse));
+
+ var typeMapAttributeCtorDefinition = typeMapAttributeDefinition.Methods
+ .FirstOrDefault (ctorSelector) ?? throw new InvalidOperationException ($"Couldn't find {attributeTypeName}..ctor()");
+ var typeMapAttributeCtor = new MethodReference (
+ typeMapAttributeCtorDefinition.Name,
+ typeMapAttributeCtorDefinition.ReturnType,
+ attributeType) {
+ HasThis = typeMapAttributeCtorDefinition.HasThis,
+ ExplicitThis = typeMapAttributeCtorDefinition.ExplicitThis,
+ CallingConvention = typeMapAttributeCtorDefinition.CallingConvention,
+ };
+ foreach (var param in typeMapAttributeCtorDefinition.Parameters) {
+ typeMapAttributeCtor.Parameters.Add (new ParameterDefinition (
+ param.Name,
+ param.Attributes,
+ addReferencesTo.MainModule.ImportReference (param.ParameterType)));
+ }
+ ctor = addReferencesTo.MainModule.ImportReference (typeMapAttributeCtor);
+ }
+
+ protected override void Process ()
+ {
+ AssemblyToInjectTypeMap = Context.Annotations.GetType ().GetMethod ("GetEntryPointAssembly")?.Invoke (Context.Annotations, null) as AssemblyDefinition ?? throw new NotImplementedException ("asdfasdf NoEntryPoint");
+ var javaTypeMapUniverseTypeDefinition = Context.GetType (JavaTypeMapUniverseTypeName);
+ JavaTypeMapUniverseType = AssemblyToInjectTypeMap.MainModule.ImportReference (javaTypeMapUniverseTypeDefinition);
+
+ GetTypeMapAttributeReferences (TypeMapAttributeTypeName,
+ m => m.IsConstructor
+ && m.Parameters is [
+ { ParameterType.FullName: "System.String" },
+ { ParameterType.FullName: "System.Type" },
+ { ParameterType.FullName: "System.Type" }],
+ AssemblyToInjectTypeMap,
+ JavaTypeMapUniverseType,
+ out TypeMapAttributeCtor);
+
+ GetTypeMapAttributeReferences (TypeMapAssociationAttributeTypeName,
+ m => m.IsConstructor
+ && m.Parameters is [
+ { ParameterType.FullName: "System.Type" },
+ { ParameterType.FullName: "System.Type" }],
+ AssemblyToInjectTypeMap,
+ JavaTypeMapUniverseType,
+ out TypeMapAssociationAttributeCtor);
+
+ MonoAndroidAssembly = javaTypeMapUniverseTypeDefinition.Module.Assembly;
+ GetTypeMapAttributeReferences (TypeMapAssemblyTargetAttributeTypeName,
+ m => m.IsConstructor
+ && m.Parameters is [{ ParameterType.FullName: "System.String" }],
+ MonoAndroidAssembly,
+ JavaTypeMapUniverseType,
+ out TypeMapAssemblyTargetAttributeCtor);
+
+ var typeMapProxyAttrTypeDef = Context.GetType (TypeMapProxyAttributeTypeName);
+ var typeMapProxyAttribute = AssemblyToInjectTypeMap.MainModule.ImportReference (typeMapProxyAttrTypeDef);
+ var typeMapProxyAttrCtor = typeMapProxyAttrTypeDef.Methods.Single (m => m.IsConstructor);
+ TypeMapProxyAttributeCtor = AssemblyToInjectTypeMap.MainModule.ImportReference (typeMapProxyAttrCtor);
+
+ SystemTypeType = AssemblyToInjectTypeMap.MainModule.ImportReference (Context.GetType ("System.Type"));
+ SystemStringType = AssemblyToInjectTypeMap.MainModule.ImportReference (Context.GetType ("System.String"));
+ }
+
+
+ protected override void ProcessAssembly (AssemblyDefinition assembly)
+ {
+ foreach (var type in assembly.MainModule.Types) {
+ ProcessType (assembly, type);
+ }
+ }
+
+ // 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}'"));
+ var proxyType = GenerateTypeMapProxyType (javaName, type);
+ typesToInject.Add (proxyType);
+ proxyMappings.Add (type, proxyType);
+ } else {
+ Context.LogMessage (MessageContainer.CreateInfoMessage ($"Type '{type.FullName}' has no peer"));
+ }
+
+ if (!type.HasNestedTypes)
+ return;
+
+ foreach (TypeDefinition nested in type.NestedTypes)
+ ProcessType (assembly, nested);
+ }
+
+ 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 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 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);
+ }
+
+ 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, AssemblyToInjectTypeMap.Name.FullName));
+ MonoAndroidAssembly.CustomAttributes.Add (targetAssembly);
+ MonoAndroidAssembly.Write (Path.Combine (
+ Path.GetDirectoryName (Context.GetAssemblyLocation (AssemblyToInjectTypeMap)),
+ "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);
+ 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,
+ 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/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/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) { }
- }
-}
diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
index 6ed13d5e73b..6d163110ffd 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;
}
@@ -105,7 +106,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;
@@ -113,18 +114,22 @@ 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 ();
+ if (RuntimeFeature.IsCoreClrRuntime) {
+ typeManager = new TypeMapAttributeTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0);
+ } else if (RuntimeFeature.ManagedTypeMap) {
+ typeManager = new ManagedTypeManager ();
} else {
- typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0);
+ typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0);
}
if (RuntimeFeature.IsMonoRuntime) {
valueManager = new AndroidValueManager ();
} else if (RuntimeFeature.IsCoreClrRuntime) {
- 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");
}
@@ -142,10 +147,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);
}
@@ -153,7 +158,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 379558e3265..8f1d29d9bc4 100644
--- a/src/Mono.Android/Java.Interop/TypeManager.cs
+++ b/src/Mono.Android/Java.Interop/TypeManager.cs
@@ -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);
@@ -492,7 +493,7 @@ 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)
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/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/Microsoft.Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs
index 4924b259ef4..e8b1757a0b7 100644
--- a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs
+++ b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs
@@ -16,6 +16,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;
diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj
index 138f11f683e..7129a4967ea 100644
--- a/src/Mono.Android/Mono.Android.csproj
+++ b/src/Mono.Android/Mono.Android.csproj
@@ -104,6 +104,9 @@
+
+
+
@@ -424,26 +427,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 c0c7ff7de09..55a26deac1e 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
@@ -49,6 +49,10 @@ 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)"
+ Type="Microsoft.Android.Sdk.ILLink.GenerateTypeMapAttributesStep"
+ BeforeStep="MarkStep" />
<_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveSubStepDispatcher" />
<_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.MarkJavaObjects" />