Skip to content

Commit

Permalink
[NativeAOT] introduce Microsoft.Android.Runtime.NativeAOT.dll (#9760)
Browse files Browse the repository at this point in the history
This moves:

  * C# code from `samples/NativeAOT` to a new assembly,
    `Microsoft.Android.Runtime.NativeAOT.dll`.

  * Java code from `samples/NativeAOT` to
    `Xamarin.Android.Build.Tasks`, to be generated at build time.

  * C# code from `Java.Interop.Environment.csproj` has been trimmed
    down, copied into `Microsoft.Android.Runtime.NativeAOT.dll`.

  * Minimum Viable Product™ "typemap" implementation.

  * Change namespace to `Microsoft.Android.Runtime`

## Minimum Viable Product™ typemaps

A quick-and-dirty -- and totally unoptimized -- typemap implementation
has been added as a new `TypeMappingStep` into
`Microsoft.Android.Sdk.ILLink`.

Given this initial starting point within `NativeAotTypeManager`:

	partial class NativeAotTypeManager {
	    IDictionary<string, Type> TypeMappings = new Dictionary<string, Type>(StringComparer.Ordinal);

	    NativeAotTypeManager ()
	    {
		InitializeTypeMappings ();
	    }

	    void InitializeTypeMappings ()
	    {
	        throw new InvalidOperationException ("Should be replaced at build time!");
	    }
	}

then at build time `TypeMappingStep` will *replace* the body of
`NativeAotTypeManager.InitializeTypeMappings()` with repeated
`IDictionary<string, Type>.Add()` calls for each type which survived
linking:

	// Cecil-generated code, C# equivalent
	void InitializeTypeMappings ()
	{
	    TypeMappings.Add ("java/lang/Object", typeof (Java.Lang.Object));
	    TypeMappings.Add ("android/app/Application", typeof (Android.App.Application));
	    // …
	}
  • Loading branch information
jonathanpeppers authored Feb 12, 2025
1 parent 7acf328 commit 70bd636
Show file tree
Hide file tree
Showing 23 changed files with 593 additions and 83 deletions.
7 changes: 7 additions & 0 deletions Xamarin.Android.sln
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Sdk.Analy
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "proguard-android", "src\proguard-android\proguard-android.csproj", "{5FD0133B-69E5-4474-9B67-9FD1D0150C70}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Runtime.NativeAOT", "src\Microsoft.Android.Runtime.NativeAOT\Microsoft.Android.Runtime.NativeAOT.csproj", "{E8831F32-11D7-D42C-E43C-711998BC357A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|AnyCPU = Debug|AnyCPU
Expand Down Expand Up @@ -347,6 +349,10 @@ Global
{5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{5FD0133B-69E5-4474-9B67-9FD1D0150C70}.Release|AnyCPU.Build.0 = Release|Any CPU
{E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
{E8831F32-11D7-D42C-E43C-711998BC357A}.Debug|AnyCPU.Build.0 = Debug|Any CPU
{E8831F32-11D7-D42C-E43C-711998BC357A}.Release|AnyCPU.ActiveCfg = Release|Any CPU
{E8831F32-11D7-D42C-E43C-711998BC357A}.Release|AnyCPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -406,6 +412,7 @@ Global
{A39B6D7C-6616-40D6-8AE4-C6CEE93D2708} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483}
{5E806C9F-1B67-4B6B-A6AB-258834250DBB} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723}
{5FD0133B-69E5-4474-9B67-9FD1D0150C70} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723}
{E8831F32-11D7-D42C-E43C-711998BC357A} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {53A1F287-EFB2-4D97-A4BB-4A5E145613F6}
Expand Down
6 changes: 5 additions & 1 deletion build-tools/create-packs/Microsoft.Android.Runtime.proj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ projects that use the Microsoft.Android framework in .NET 6+.
<_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll" />
<_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll" />
<_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.Runtime.dll" />
<!-- Always include stable Mono.Android.Export.dll -->
<!-- Always include stable versions of the following assemblies -->
<_AndroidRuntimePackAssemblies
Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Microsoft.Android.Runtime.NativeAOT.dll"
Condition=" '$(AndroidRuntime)' == 'NativeAOT' "
/>
<_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Mono.Android.Export.dll" />
</ItemGroup>

Expand Down
7 changes: 0 additions & 7 deletions samples/NativeAOT/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:label="@string/app_name" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true">
<!-- Temporary, to eventually be included in .NET Android infrastructure -->
<provider
android:name="net.dot.jni.nativeaot.NativeAotRuntimeProvider"
android:exported="false"
android:initOrder="1999999999"
android:authorities="net.dot.jni.nativeaot.NativeAotRuntimeProvider.__init__"
/>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
8 changes: 0 additions & 8 deletions samples/NativeAOT/NativeAOT.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
<ApplicationVersion>1</ApplicationVersion>
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<AndroidPackageFormat>apk</AndroidPackageFormat>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Temporary for InternalsVisibleTo -->
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
<!-- Default to arm64 device -->
<RuntimeIdentifier>android-arm64</RuntimeIdentifier>
<!-- Current required properties for NativeAOT -->
Expand All @@ -29,8 +25,4 @@
<_FastDeploymentDiagnosticLogging>true</_FastDeploymentDiagnosticLogging>
</PropertyGroup>

<ItemGroup>
<AndroidJavaSource Update="*.java" Bind="false" />
<ProjectReference Include="..\..\external\Java.Interop\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Java.Interop;
using System.Runtime.InteropServices;

namespace NativeAOT;
namespace Microsoft.Android.Runtime;

static class JavaInteropRuntime
{
Expand Down Expand Up @@ -35,7 +35,7 @@ static void init (IntPtr jnienv, IntPtr klass)
{
try {
var typeManager = new NativeAotTypeManager ();
var options = new JreRuntimeOptions {
var options = new NativeAotRuntimeOptions {
EnvironmentPointer = jnienv,
TypeManager = typeManager,
ValueManager = new NativeAotValueManager (typeManager),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Runtime.InteropServices;
using System.Text;

namespace NativeAOT;
namespace Microsoft.Android.Runtime;

internal sealed class LogcatTextWriter : TextWriter {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,35 @@
using Java.Interop;
using Java.Interop.Tools.TypeNameMappings;

namespace NativeAOT;
namespace Microsoft.Android.Runtime;

partial class NativeAotTypeManager : JniRuntime.JniTypeManager {

const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods;
internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;

// TODO: list of types specific to this application
Dictionary<string, Type> typeMappings = new () {
["android/app/Activity"] = typeof (Android.App.Activity),
["android/app/Application"] = typeof (Android.App.Application),
["android/content/Context"] = typeof (Android.Content.Context),
["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper),
["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle),
["android/os/Bundle"] = typeof (Android.OS.Bundle),
["android/view/ContextThemeWrapper"] = typeof (Android.Views.ContextThemeWrapper),
["my/MainActivity"] = typeof (MainActivity),
["my/MainApplication"] = typeof (MainApplication),
};
readonly IDictionary<string, Type> TypeMappings = new Dictionary<string, Type> (StringComparer.Ordinal);

public NativeAotTypeManager ()
{
AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: NativeAotTypeManager()");
AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"NativeAotTypeManager()");
var startTicks = global::System.Environment.TickCount;
InitializeTypeMappings ();
var endTicks = global::System.Environment.TickCount;
AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"InitializeTypeMappings() took {endTicks - startTicks}ms");
}

protected override Type? GetInvokerTypeCore (Type type)
void InitializeTypeMappings ()
{
// Should be replaced by src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs
throw new InvalidOperationException ("TypeMappings should be replaced during trimming!");
}

[return: DynamicallyAccessedMembers (Constructors)]
protected override Type? GetInvokerTypeCore (
[DynamicallyAccessedMembers (Constructors)]
Type type)
{
const string suffix = "Invoker";

Expand Down Expand Up @@ -68,6 +70,10 @@ static Type MakeGenericType (
return MakeGenericType (suffixDefinition, arguments);
}

// NOTE: suppressions below also in `src/Mono.Android/Android.Runtime/AndroidRuntime.cs`
[UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value parsed from parameter 'methods'.")]
[UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
[UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
public override void RegisterNativeMembers (
JniType nativeClass,
[DynamicallyAccessedMembers (MethodsAndPrivateNested)]
Expand Down Expand Up @@ -143,7 +149,7 @@ public override void RegisterNativeMembers (
protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
{
AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}`");
if (typeMappings.TryGetValue (jniSimpleReference, out var target)) {
if (TypeMappings.TryGetValue (jniSimpleReference, out var target)) {
Console.WriteLine ($"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{target}`");
yield return target;
}
Expand All @@ -161,9 +167,7 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)

IEnumerable<string> CreateSimpleReferencesEnumerator (Type type)
{
if (typeMappings == null)
yield break;
foreach (var e in typeMappings) {
foreach (var e in TypeMappings) {
if (e.Value == type)
yield return e.Key;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
Expand All @@ -13,9 +14,9 @@
using Android.Runtime;
using Java.Interop;

namespace NativeAOT;
namespace Microsoft.Android.Runtime;

internal class NativeAotValueManager : JniRuntime.JniValueManager
class NativeAotValueManager : JniRuntime.JniValueManager
{
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

Expand Down Expand Up @@ -113,12 +114,12 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
"Warning: Not registering PeerReference={0} IdentityHashCode=0x{1} Instance={2} Instance.Type={3} Java.Type={4}; " +
"keeping previously registered PeerReference={5} Instance={6} Instance.Type={7} Java.Type={8}.",
ignoreValue.PeerReference.ToString (),
key.ToString ("x"),
RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x"),
key.ToString ("x", CultureInfo.InvariantCulture),
RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x", CultureInfo.InvariantCulture),
ignoreValue.GetType ().FullName,
JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference),
keepValue.PeerReference.ToString (),
RuntimeHelpers.GetHashCode (keepValue).ToString ("x"),
RuntimeHelpers.GetHashCode (keepValue).ToString ("x", CultureInfo.InvariantCulture),
keepValue.GetType ().FullName,
JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference));
}
Expand Down Expand Up @@ -186,8 +187,8 @@ public override void FinalizePeer (IJavaPeerable value)
if (o.LogGlobalReferenceMessages) {
o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
h.ToString (),
value.JniIdentityHashCode.ToString ("x"),
RuntimeHelpers.GetHashCode (value).ToString ("x"),
value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
value.GetType ().ToString ());
}
RemovePeer (value);
Expand All @@ -200,8 +201,8 @@ public override void FinalizePeer (IJavaPeerable value)
if (o.LogGlobalReferenceMessages) {
o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
h.ToString (),
value.JniIdentityHashCode.ToString ("x"),
RuntimeHelpers.GetHashCode (value).ToString ("x"),
value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
value.GetType ().ToString ());
}
value.SetPeerReference (new JniObjectReference ());
Expand All @@ -214,9 +215,11 @@ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference refer
try {
ActivateViaReflection (reference, cinfo, argumentValues);
} catch (Exception e) {
var m = string.Format ("Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.",
var m = string.Format (
CultureInfo.InvariantCulture,
"Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.",
reference,
GetJniIdentityHashCode (reference).ToString ("x"),
GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture),
JniEnvironment.Types.GetJniTypeNameFromInstance (reference),
cinfo.DeclaringType?.FullName);
Debug.WriteLine (m);
Expand Down Expand Up @@ -257,7 +260,11 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()

static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };

protected override IJavaPeerable? TryCreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type type)
protected override IJavaPeerable? TryCreatePeer (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (Constructors)]
Type type)
{
var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null);
if (c != null) {
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.Android.Runtime.NativeAOT/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Runtime.Versioning;

// NOTE: silences the CA1416 analyzer about supported Android APIs
[assembly: TargetPlatformAttribute("Android35.0")]
[assembly: SupportedOSPlatformAttribute("Android21.0")]
98 changes: 98 additions & 0 deletions src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Originally from: https://github.com/dotnet/java-interop/blob/dd3c1d0514addfe379f050627b3e97493e985da6/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Android.Runtime;

namespace Java.Interop {

struct JavaVMInitArgs {
#pragma warning disable CS0649 // Field is never assigned to;
public JniVersion version; /* use JNI_VERSION_1_2 or later */

public int nOptions;
public IntPtr /* JavaVMOption[] */ options;
public byte ignoreUnrecognized;
#pragma warning restore CS0649
}

class NativeAotRuntimeOptions : JniRuntime.CreationOptions {

public bool IgnoreUnrecognizedOptions {get; set;}

public TextWriter? JniGlobalReferenceLogWriter {get; set;}
public TextWriter? JniLocalReferenceLogWriter {get; set;}

public NativeAotRuntimeOptions ()
{
JniVersion = JniVersion.v1_2;
}

public JreRuntime CreateJreVM ()
{
return new JreRuntime (this);
}
}

class JreRuntime : JniRuntime
{
static JreRuntime ()
{
}

static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder)
{
if (builder == null)
throw new ArgumentNullException ("builder");
if (builder.InvocationPointer == IntPtr.Zero &&
builder.EnvironmentPointer == IntPtr.Zero &&
string.IsNullOrEmpty (builder.JvmLibraryPath))
throw new InvalidOperationException ($"Member `{nameof (NativeAotRuntimeOptions)}.{nameof (NativeAotRuntimeOptions.JvmLibraryPath)}` must be set.");

#if NET
builder.TypeManager ??= new NativeAotTypeManager ();
#endif // NET

builder.ValueManager ??= new NativeAotValueManager (builder.TypeManager);
builder.ObjectReferenceManager ??= new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter);

if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero)
return builder;

throw new NotImplementedException ();
}

[UnconditionalSuppressMessage ("Trimming", "IL3000", Justification = "We check for a null Assembly.Location value!")]
internal static string? GetAssemblyLocation (Assembly assembly)
{
var location = assembly.Location;
if (!string.IsNullOrEmpty (location))
return location;
return null;
}

internal protected JreRuntime (NativeAotRuntimeOptions builder)
: base (CreateJreVM (builder))
{
}

public override string? GetCurrentManagedThreadName ()
{
return Thread.CurrentThread.Name;
}

public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool fNeedFileInfo)
{
return new StackTrace (skipFrames, fNeedFileInfo)
.ToString ();
}
}
}
Loading

0 comments on commit 70bd636

Please sign in to comment.