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

Lines changed: 521 additions & 3 deletions

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

Lines changed: 8 additions & 0 deletions
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 }}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifestxmlns:android="http://schemas.android.com/apk/res/android">
3+
<applicationandroid: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-permissionandroid:name="android.permission.INTERNET" />
13+
</manifest>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
usingAndroid.Runtime;
2+
usingJava.Interop;
3+
usingSystem.Runtime.InteropServices;
4+
5+
namespaceNativeAOT;
6+
7+
staticclassJavaInteropRuntime
8+
{
9+
staticJniRuntime?runtime;
10+
11+
[UnmanagedCallersOnly(EntryPoint="JNI_OnLoad")]
12+
staticintJNI_OnLoad(IntPtrvm,IntPtrreserved)
13+
{
14+
try{
15+
AndroidLog.Print(AndroidLogLevel.Info,"JavaInteropRuntime","JNI_OnLoad()");
16+
LogcatTextWriter.Init();
17+
return(int)JniVersion.v1_6;
18+
}
19+
catch(Exceptione){
20+
AndroidLog.Print(AndroidLogLevel.Error,"JavaInteropRuntime",$"JNI_OnLoad() failed: {e}");
21+
return0;
22+
}
23+
}
24+
25+
[UnmanagedCallersOnly(EntryPoint="JNI_OnUnload")]
26+
staticvoidJNI_OnUnload(IntPtrvm,IntPtrreserved)
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+
staticvoidinit(IntPtrjnienv,IntPtrklass)
35+
{
36+
try{
37+
varoptions=newJreRuntimeOptions{
38+
EnvironmentPointer=jnienv,
39+
TypeManager=newNativeAotTypeManager(),
40+
ValueManager=newNativeAotValueManager(),
41+
UseMarshalMemberBuilder=false,
42+
JniGlobalReferenceLogWriter=newLogcatTextWriter(AndroidLogLevel.Debug,"NativeAot:GREF"),
43+
JniLocalReferenceLogWriter=newLogcatTextWriter(AndroidLogLevel.Debug,"NativeAot:LREF"),
44+
};
45+
runtime=options.CreateJreVM();
46+
47+
// Entry point into Mono.Android.dll
48+
JNIEnvInit.InitializeJniRuntime(runtime);
49+
}
50+
catch(Exceptione){
51+
AndroidLog.Print(AndroidLogLevel.Error,"JavaInteropRuntime",$"JavaInteropRuntime.init: error: {e}");
52+
}
53+
}
54+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
packagenet.dot.jni.nativeaot;
2+
3+
importandroid.util.Log;
4+
5+
publicclassJavaInteropRuntime {
6+
static {
7+
Log.d("JavaInteropRuntime", "Loading NativeAOT.so...");
8+
System.loadLibrary("NativeAOT");
9+
}
10+
11+
privateJavaInteropRuntime() {
12+
}
13+
14+
publicstaticnativevoidinit();
15+
}

‎samples/NativeAOT/Logging.cs‎

Lines changed: 77 additions & 0 deletions
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+
usingSystem.IO;
6+
usingSystem.Runtime.InteropServices;
7+
usingSystem.Text;
8+
9+
namespaceNativeAOT;
10+
11+
internalsealedclassLogcatTextWriter:TextWriter{
12+
13+
publicstaticvoidInit()
14+
{
15+
// This method is a no-op, but it's necessary to ensure the static
16+
// constructor is executed.
17+
}
18+
19+
staticLogcatTextWriter()
20+
{
21+
Console.SetOut(newLogcatTextWriter(AndroidLogLevel.Info));
22+
Console.SetError(newLogcatTextWriter(AndroidLogLevel.Error));
23+
}
24+
25+
AndroidLogLevelLevel;
26+
stringTag;
27+
28+
internalLogcatTextWriter(AndroidLogLevellevel,stringtag="NativeAotFromAndroid")
29+
{
30+
Level=level;
31+
Tag=tag;
32+
}
33+
34+
publicoverrideEncodingEncoding=>Encoding.UTF8;
35+
publicoverridestringNewLine=>"\n";
36+
37+
publicoverridevoidWriteLine(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')isintn&&n<0){
46+
break;
47+
}
48+
varline=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+
staticclassAndroidLog{
57+
58+
[DllImport("log",EntryPoint="__android_log_print",CallingConvention=CallingConvention.Cdecl)]
59+
privatestaticexternvoid__android_log_print(AndroidLogLevellevel,string?tag,stringformat,stringargs,IntPtrptr);
60+
61+
internalstaticvoidPrint(AndroidLogLevellevel,string?tag,stringmessage)=>
62+
__android_log_print(level,tag,"%s",message,IntPtr.Zero);
63+
64+
}
65+
66+
internalenumAndroidLogLevel
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‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
usingAndroid.Runtime;
2+
usingSystem.Reflection;
3+
usingSystem.Runtime.InteropServices;
4+
5+
namespaceNativeAOT;
6+
7+
[Register("my/MainActivity")]// Required for typemap in NativeAotTypeManager
8+
[Activity(Label="@string/app_name",MainLauncher=true)]
9+
publicclassMainActivity:Activity
10+
{
11+
protectedoverridevoidOnCreate(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‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<ProjectSdk="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+
<PropertyGroupCondition=" '$(RunningOnCI)' == 'true' ">
25+
<!-- x86_64 emulator -->
26+
<RuntimeIdentifier>android-x64</RuntimeIdentifier>
27+
<_NuGetFolderOnCI>..\..\bin\Build$(Configuration)\nuget-unsigned</_NuGetFolderOnCI>
28+
<RestoreAdditionalProjectSourcesCondition="Exists('$(_NuGetFolderOnCI)')">$(_NuGetFolderOnCI)</RestoreAdditionalProjectSources>
29+
<_FastDeploymentDiagnosticLogging>true</_FastDeploymentDiagnosticLogging>
30+
</PropertyGroup>
31+
32+
<ItemGroup>
33+
<AndroidJavaSourceUpdate="*.java"Bind="false" />
34+
<ProjectReferenceInclude="..\..\external\Java.Interop\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
35+
</ItemGroup>
36+
</Project>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
packagenet.dot.jni.nativeaot;
2+
3+
importandroid.util.Log;
4+
5+
publicclassNativeAotRuntimeProvider
6+
extendsandroid.content.ContentProvider
7+
{
8+
privatestaticfinalStringTAG = "NativeAotRuntimeProvider";
9+
10+
publicNativeAotRuntimeProvider() {
11+
Log.d(TAG, "NativeAotRuntimeProvider()");
12+
}
13+
14+
@Override
15+
publicbooleanonCreate() {
16+
Log.d(TAG, "NativeAotRuntimeProvider.onCreate()");
17+
returntrue;
18+
}
19+
20+
@Override
21+
publicvoidattachInfo(android.content.Contextcontext, android.content.pm.ProviderInfoinfo) {
22+
Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…");
23+
JavaInteropRuntime.init();
24+
super.attachInfo (context, info);
25+
}
26+
27+
@Override
28+
publicandroid.database.Cursorquery(android.net.Uriuri, String[] projection, Stringselection, String[] selectionArgs, StringsortOrder) {
29+
thrownewRuntimeException ("This operation is not supported.");
30+
}
31+
32+
@Override
33+
publicStringgetType(android.net.Uriuri) {
34+
thrownewRuntimeException ("This operation is not supported.");
35+
}
36+
37+
@Override
38+
publicandroid.net.Uriinsert(android.net.Uriuri, android.content.ContentValuesinitialValues) {
39+
thrownewRuntimeException ("This operation is not supported.");
40+
}
41+
42+
@Override
43+
publicintdelete(android.net.Uriuri, Stringwhere, String[] whereArgs) {
44+
thrownewRuntimeException ("This operation is not supported.");
45+
}
46+
47+
@Override
48+
publicintupdate(android.net.Uriuri, android.content.ContentValuesvalues, Stringwhere, String[] whereArgs) {
49+
thrownewRuntimeException ("This operation is not supported.");
50+
}
51+
}

0 commit comments

Comments
 (0)