Skip to content

Commit 007317b

Browse files
committed
[Hello-NativeAOTFromJNI] Add NativeAOT sample
Instead of a managed app creating a Java VM and calling into Java, do the opposite: have a Java app call into Native Library built using NativeAOT, using `[UnmanagedCallersOnly]` to provide the entry points.
1 parent 3c83179 commit 007317b

File tree

6 files changed

+199
-0
lines changed

6 files changed

+199
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<RootNamespace>Hello_NativeAOTFromJNI</RootNamespace>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<PublishAot>true</PublishAot>
9+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
10+
<NativeLib>Shared</NativeLib>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\..\src\Java.Interop\Java.Interop.csproj"
15+
AdditionalProperties="Standalone=True"
16+
/>
17+
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj"
18+
AdditionalProperties="Standalone=True"
19+
/>
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<Content Include="$(OutputPath)hello-from-java.jar" CopyToPublishDirectory="PreserveNewest" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<HelloNativeAOTFromJNIJar Include="$(MSBuildThisFileDirectory)java\**\*.java" />
28+
</ItemGroup>
29+
30+
<Import Project="Hello-NativeAOTFromJNI.targets" />
31+
</Project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<Project>
2+
3+
<Target Name="BuildNativeAOTFromJNIJar"
4+
BeforeTargets="Build"
5+
Inputs="@(HelloNativeAOTFromJNIJar)"
6+
Outputs="$(OutputPath)hello-from-java.jar">
7+
<MakeDir Directories="$(IntermediateOutputPath)h-classes" />
8+
<ItemGroup>
9+
<_Source Include="@(HelloNativeAOTFromJNIJar->Replace('%5c', '/'))" />
10+
</ItemGroup>
11+
<WriteLinesToFile
12+
File="$(IntermediateOutputPath)_java_sources.txt"
13+
Lines="@(_Source)"
14+
Overwrite="True"
15+
/>
16+
<ItemGroup>
17+
<_JavacOpt Include="$(_JavacSourceOptions)" />
18+
<_JavacOpt Include="-d &quot;$(IntermediateOutputPath)h-classes&quot; " />
19+
<_JavacOpt Include="-classpath &quot;$(OutputPath)java-interop.jar&quot; " />
20+
<_JavacOpt Include="&quot;@$(IntermediateOutputPath)_java_sources.txt&quot;" />
21+
<_JavacOpt Include="-h &quot;$(IntermediateOutputPath)h-classes&quot; " />
22+
</ItemGroup>
23+
<Exec Command="&quot;$(JavaCPath)&quot; @(_JavacOpt, ' ')" />
24+
<Delete Files="$(IntermediateOutputPath)_java_sources.txt" />
25+
<Exec Command="&quot;$(JarPath)&quot; cf &quot;$(OutputPath)hello-from-java.jar&quot; -C &quot;$(IntermediateOutputPath)h-classes&quot; ." />
26+
</Target>
27+
28+
<Target Name="_NativeLibRequiresLibPrefix"
29+
AfterTargets="Publish">
30+
<Copy
31+
Condition=" '$(OS)' != 'Windows_NT' "
32+
SourceFiles="$(PublishDir)$(AssemblyName).dylib"
33+
DestinationFiles="$(PublishDir)lib$(AssemblyName).dylib"
34+
/>
35+
<Copy SourceFiles="$(OutputPath)hello-from-java.jar" DestinationFolder="$(PublishDir)" />
36+
</Target>
37+
38+
<Target Name="RunJavaSample">
39+
<ItemGroup>
40+
<_Classpath Include="hello-from-java.jar" />
41+
<_Classpath Include="java-interop.jar" />
42+
</ItemGroup>
43+
<PropertyGroup>
44+
<_CPSep Condition=" '$(OS)' == 'Windows_NT' ">;</_CPSep>
45+
<_CPSep Condition=" '$(_CPSep)' == '' ">:</_CPSep>
46+
<_CP>@(_Classpath, '$(_CPSep)')</_CP>
47+
</PropertyGroup>
48+
<Exec
49+
Command="&quot;$(JavaPath)&quot; -classpath &quot;$(_CP)&quot; com/microsoft/hello_from_jni/App"
50+
WorkingDirectory="$(PublishDir)"
51+
/>
52+
</Target>
53+
</Project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Runtime.InteropServices;
2+
3+
using Java.Interop;
4+
5+
namespace Hello_NativeAOTFromJNI;
6+
7+
static class JNIEnvInit
8+
{
9+
// static JniRuntime? runtime;
10+
11+
[UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")]
12+
static int JNI_OnLoad (IntPtr vm, IntPtr reserved)
13+
{
14+
try {
15+
// runtime = new JniRuntime (null);
16+
// return runtime.JniVersion;
17+
return (int) JniVersion.v1_2;
18+
}
19+
catch (Exception e) {
20+
Console.Error.WriteLine ($"JNI_OnLoad: {e}");
21+
return 0;
22+
}
23+
}
24+
25+
[UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")]
26+
static void JNI_Onload (IntPtr vm, IntPtr reserved)
27+
{
28+
// runtime?.Dispose ();
29+
}
30+
31+
// symbol name from `$(IntermediateOutputPath)/h-classes/com_microsoft_hello_from_jni_NativeAOTInit.h`
32+
[UnmanagedCallersOnly (EntryPoint="Java_com_microsoft_hello_1from_1jni_NativeAOTInit_sayHello")]
33+
static void sayHello (IntPtr jnienv, IntPtr klass)
34+
{
35+
Console.WriteLine ($"Hello from .NET NativeAOT!");
36+
}
37+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Hello From JNI
2+
3+
[JNI][0] supports *two* modes of operation:
4+
5+
1. Native code creates the JVM, e.g. via [`JNI_CreateJavaVM()`][1], or
6+
2. The JVM already exists, and calls [`JNI_OnLoad()`][2] when loading a native library.
7+
8+
Java.Interop samples and unit tests rely on the first approach.
9+
10+
.NET Android / neé Xamarin.Android is the second approach.
11+
12+
Bring an example of the latter into a Java.Interop sample, using [NativeAOT][3].
13+
14+
## Building
15+
16+
Building a native library with NativeAOT requires a Release configuration build.
17+
For in-repo use, that means that xamarin/Java.Interop itself needs to be built in
18+
Release configuration:
19+
20+
```sh
21+
% dotnet build -c Release -t:Prepare
22+
% dotnet build -c Release
23+
```
24+
25+
Once Java.Interop itself is built, you can build the sample:
26+
27+
```sh
28+
% dotnet build -c Release
29+
% dotnet publish -r osx-x64
30+
```
31+
32+
The resulting native library contains the desired symbols:
33+
34+
```sh
35+
% nm bin/Release/osx-x64/publish/Hello-NativeAOTFromJNI.dylib | grep ' S '
36+
00000000000cb710 S _JNI_OnLoad
37+
00000000000cb820 S _JNI_OnUnload
38+
00000000000cb840 S _Java_com_microsoft_hello_1from_1jni_NativeAOTInit_sayHello
39+
```
40+
41+
Use the `RunJavaSample` target to run Java, which will run
42+
`System.loadLibrary("Hello-NativeAOTFromJNI")`, which will cause the
43+
NativeAOT-generated `libHello-NativeAOTFromJNI.dylib` to be run:
44+
45+
```sh
46+
% dotnet build -c Release -r osx-x64 -t:RunJavaSample -v m --nologo --no-restore
47+
Hello from Java!
48+
Hello from .NET NativeAOT!
49+
50+
Build succeeded.
51+
0 Warning(s)
52+
0 Error(s)
53+
54+
Time Elapsed 00:00:00.83
55+
```
56+
57+
[0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
58+
[1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm
59+
[2]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad
60+
[3]: https://github.com/dotnet/samples/blob/main/core/nativeaot/NativeLibrary/README.md
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.microsoft.hello_from_jni;
2+
3+
class App {
4+
5+
public static void main(String[] args) {
6+
System.out.println("Hello from Java!");
7+
NativeAOTInit.sayHello();
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.microsoft.hello_from_jni;
2+
3+
class NativeAOTInit {
4+
static {
5+
System.loadLibrary("Hello-NativeAOTFromJNI");
6+
}
7+
8+
public static native void sayHello();
9+
}

0 commit comments

Comments
 (0)