Skip to content

Commit 7a772f0

Browse files
[NativeAOT] add sample that runs successfully (#9636)
Context: https://github.com/dotnet/java-interop/tree/9b1d8781e8e322849d05efac32119c913b21c192/samples/Hello-NativeAOTFromAndroid "Import" the [Hello-NativeAOTFromAndroid][0] from dotnet/java-interop, and update it to use `Mono.Android.dll` and parts of the .NET for Android build system. It currently relies on `[InternalsVisibleTo("NativeAOT")]` within `Mono.Android.dll` for access to things like: * `Android.Runtime.JNIEnvInit.InitializeJniRuntime()` There are a couple MSBuild changes still left: TODO: * `$(_ExtraTrimmerArgs)` needs to be specified for trimmer warnings to not be displayed twice. This is the same thing done in xamarin/xamarin-macios. * `@(TrimmerRootAssembly)` needs to be set for illink's "already trimmed" output for ILC. We exclude `System.Private.CoreLib.dll` from this list. * Remove use of `Java.Runtime.Environment.dll`, and otherwise allow the sample to be built from the .NET for Android workload packs. * "MOAR Integration": sample depends upon a manually specified "type map" dictionary. This needs to be automagic to be useful. * Figure out what to do about C++: do we dynamically link against and bundle `libc++_shared.so`? Statically link against `libc++_static.a`? [0]: dotnet/java-interop@78d5937
1 parent 80bb5d7 commit 7a772f0

34 files changed

+521
-3
lines changed

build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ stages:
4949
artifactName: $(TestAssembliesArtifactName)
5050
downloadPath: ${{ parameters.xaSourcePath }}/bin/Test$(XA.Build.Configuration)
5151

52+
# Currently needed for samples/NativeAOT
53+
- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self
54+
parameters:
55+
project: Xamarin.Android.sln
56+
arguments: -t:PrepareJavaInterop -c $(XA.Build.Configuration) --no-restore
57+
displayName: prepare java.interop $(XA.Build.Configuration)
58+
continueOnError: false
59+
5260
- template: /build-tools/automation/yaml-templates/start-stop-emulator.yaml
5361
parameters:
5462
xaSourcePath: ${{ parameters.xaSourcePath }}

samples/NativeAOT/AndroidManifest.xml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:label="@string/app_name" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true">
4+
<!-- Temporary, to eventually be included in .NET Android infrastructure -->
5+
<provider
6+
android:name="net.dot.jni.nativeaot.NativeAotRuntimeProvider"
7+
android:exported="false"
8+
android:initOrder="1999999999"
9+
android:authorities="net.dot.jni.nativeaot.NativeAotRuntimeProvider.__init__"
10+
/>
11+
</application>
12+
<uses-permission android:name="android.permission.INTERNET" />
13+
</manifest>
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Android.Runtime;
2+
using Java.Interop;
3+
using System.Runtime.InteropServices;
4+
5+
namespace NativeAOT;
6+
7+
static class JavaInteropRuntime
8+
{
9+
static JniRuntime? runtime;
10+
11+
[UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")]
12+
static int JNI_OnLoad (IntPtr vm, IntPtr reserved)
13+
{
14+
try {
15+
AndroidLog.Print (AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnLoad()");
16+
LogcatTextWriter.Init ();
17+
return (int) JniVersion.v1_6;
18+
}
19+
catch (Exception e) {
20+
AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JNI_OnLoad() failed: {e}");
21+
return 0;
22+
}
23+
}
24+
25+
[UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")]
26+
static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
27+
{
28+
AndroidLog.Print(AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnUnload");
29+
runtime?.Dispose ();
30+
}
31+
32+
// symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h`
33+
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_nativeaot_JavaInteropRuntime_init")]
34+
static void init (IntPtr jnienv, IntPtr klass)
35+
{
36+
try {
37+
var options = new JreRuntimeOptions {
38+
EnvironmentPointer = jnienv,
39+
TypeManager = new NativeAotTypeManager (),
40+
ValueManager = new NativeAotValueManager (),
41+
UseMarshalMemberBuilder = false,
42+
JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"),
43+
JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"),
44+
};
45+
runtime = options.CreateJreVM ();
46+
47+
// Entry point into Mono.Android.dll
48+
JNIEnvInit.InitializeJniRuntime (runtime);
49+
}
50+
catch (Exception e) {
51+
AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JavaInteropRuntime.init: error: {e}");
52+
}
53+
}
54+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package net.dot.jni.nativeaot;
2+
3+
import android.util.Log;
4+
5+
public class JavaInteropRuntime {
6+
static {
7+
Log.d("JavaInteropRuntime", "Loading NativeAOT.so...");
8+
System.loadLibrary("NativeAOT");
9+
}
10+
11+
private JavaInteropRuntime() {
12+
}
13+
14+
public static native void init();
15+
}

samples/NativeAOT/Logging.cs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// NOTE: logging methods below are need temporarily due to:
2+
// 1) linux-bionic BCL doesn't redirect stdout/stderr to logcat
3+
// 2) Android.Util.Log won't work until we initialize the Java.Interop.JreRuntime
4+
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
using System.Text;
8+
9+
namespace NativeAOT;
10+
11+
internal sealed class LogcatTextWriter : TextWriter {
12+
13+
public static void Init ()
14+
{
15+
// This method is a no-op, but it's necessary to ensure the static
16+
// constructor is executed.
17+
}
18+
19+
static LogcatTextWriter ()
20+
{
21+
Console.SetOut (new LogcatTextWriter (AndroidLogLevel.Info));
22+
Console.SetError (new LogcatTextWriter (AndroidLogLevel.Error));
23+
}
24+
25+
AndroidLogLevel Level;
26+
string Tag;
27+
28+
internal LogcatTextWriter (AndroidLogLevel level, string tag = "NativeAotFromAndroid")
29+
{
30+
Level = level;
31+
Tag = tag;
32+
}
33+
34+
public override Encoding Encoding => Encoding.UTF8;
35+
public override string NewLine => "\n";
36+
37+
public override void WriteLine (string? value)
38+
{
39+
if (value == null) {
40+
AndroidLog.Print (Level, Tag, "");
41+
return;
42+
}
43+
ReadOnlySpan<char> span = value;
44+
while (!span.IsEmpty) {
45+
if (span.IndexOf ('\n') is int n && n < 0) {
46+
break;
47+
}
48+
var line = span.Slice (0, n);
49+
AndroidLog.Print (Level, Tag, line.ToString ());
50+
span = span.Slice (n + 1);
51+
}
52+
AndroidLog.Print (Level, Tag, span.ToString ());
53+
}
54+
}
55+
56+
static class AndroidLog {
57+
58+
[DllImport ("log", EntryPoint = "__android_log_print", CallingConvention = CallingConvention.Cdecl)]
59+
private static extern void __android_log_print(AndroidLogLevel level, string? tag, string format, string args, IntPtr ptr);
60+
61+
internal static void Print(AndroidLogLevel level, string? tag, string message) =>
62+
__android_log_print(level, tag, "%s", message, IntPtr.Zero);
63+
64+
}
65+
66+
internal enum AndroidLogLevel
67+
{
68+
Unknown = 0x00,
69+
Default = 0x01,
70+
Verbose = 0x02,
71+
Debug = 0x03,
72+
Info = 0x04,
73+
Warn = 0x05,
74+
Error = 0x06,
75+
Fatal = 0x07,
76+
Silent = 0x08
77+
}

samples/NativeAOT/MainActivity.cs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Android.Runtime;
2+
using System.Reflection;
3+
using System.Runtime.InteropServices;
4+
5+
namespace NativeAOT;
6+
7+
[Register("my/MainActivity")] // Required for typemap in NativeAotTypeManager
8+
[Activity(Label = "@string/app_name", MainLauncher = true)]
9+
public class MainActivity : Activity
10+
{
11+
protected override void OnCreate(Bundle? savedInstanceState)
12+
{
13+
base.OnCreate(savedInstanceState);
14+
15+
// Set our view from the "main" layout resource
16+
SetContentView(Resource.Layout.activity_main);
17+
}
18+
}

samples/NativeAOT/NativeAOT.csproj

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>$(DotNetAndroidTargetFramework)</TargetFramework>
4+
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
5+
<OutputType>Exe</OutputType>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<ApplicationId>net.dot.hellonativeaot</ApplicationId>
9+
<ApplicationVersion>1</ApplicationVersion>
10+
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
11+
<AndroidPackageFormat>apk</AndroidPackageFormat>
12+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
13+
<!-- Temporary for InternalsVisibleTo -->
14+
<SignAssembly>true</SignAssembly>
15+
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
16+
<!-- Default to arm64 device -->
17+
<RuntimeIdentifier>android-arm64</RuntimeIdentifier>
18+
<!-- Current required properties for NativeAOT -->
19+
<PublishAot>true</PublishAot>
20+
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
21+
</PropertyGroup>
22+
23+
<!-- Settings for CI -->
24+
<PropertyGroup Condition=" '$(RunningOnCI)' == 'true' ">
25+
<!-- x86_64 emulator -->
26+
<RuntimeIdentifier>android-x64</RuntimeIdentifier>
27+
<_NuGetFolderOnCI>..\..\bin\Build$(Configuration)\nuget-unsigned</_NuGetFolderOnCI>
28+
<RestoreAdditionalProjectSources Condition="Exists('$(_NuGetFolderOnCI)')">$(_NuGetFolderOnCI)</RestoreAdditionalProjectSources>
29+
<_FastDeploymentDiagnosticLogging>true</_FastDeploymentDiagnosticLogging>
30+
</PropertyGroup>
31+
32+
<ItemGroup>
33+
<AndroidJavaSource Update="*.java" Bind="false" />
34+
<ProjectReference Include="..\..\external\Java.Interop\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
35+
</ItemGroup>
36+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package net.dot.jni.nativeaot;
2+
3+
import android.util.Log;
4+
5+
public class NativeAotRuntimeProvider
6+
extends android.content.ContentProvider
7+
{
8+
private static final String TAG = "NativeAotRuntimeProvider";
9+
10+
public NativeAotRuntimeProvider() {
11+
Log.d(TAG, "NativeAotRuntimeProvider()");
12+
}
13+
14+
@Override
15+
public boolean onCreate() {
16+
Log.d(TAG, "NativeAotRuntimeProvider.onCreate()");
17+
return true;
18+
}
19+
20+
@Override
21+
public void attachInfo(android.content.Context context, android.content.pm.ProviderInfo info) {
22+
Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…");
23+
JavaInteropRuntime.init();
24+
super.attachInfo (context, info);
25+
}
26+
27+
@Override
28+
public android.database.Cursor query(android.net.Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
29+
throw new RuntimeException ("This operation is not supported.");
30+
}
31+
32+
@Override
33+
public String getType(android.net.Uri uri) {
34+
throw new RuntimeException ("This operation is not supported.");
35+
}
36+
37+
@Override
38+
public android.net.Uri insert(android.net.Uri uri, android.content.ContentValues initialValues) {
39+
throw new RuntimeException ("This operation is not supported.");
40+
}
41+
42+
@Override
43+
public int delete(android.net.Uri uri, String where, String[] whereArgs) {
44+
throw new RuntimeException ("This operation is not supported.");
45+
}
46+
47+
@Override
48+
public int update(android.net.Uri uri, android.content.ContentValues values, String where, String[] whereArgs) {
49+
throw new RuntimeException ("This operation is not supported.");
50+
}
51+
}

0 commit comments

Comments
 (0)