Skip to content

Commit 2197579

Browse files
authored
[Hello-NativeAOTFromJNI] Add NativeAOT sample (#1153)
Context: 28849ec Context: bc5bcf4 Context: 25850ba Context: 56955d9 Context: #1157 Context: c6c487b Context: dotnet/android@180dd52 Commit 28849ec noted: > [JNI][0] supports *two* modes of operation: > > 1. Native code creates the JVM, e.g. via [`JNI_CreateJavaVM()`][1] > > 2. The JVM already exists, and when Java code calls > [`System.loadLibrary()`][3], the JVM calls the > [`JNI_OnLoad()`][2] function on the specified library. > > [0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html > [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm > [2]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runtime.html#loadLibrary(java.lang.String) > [3]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad Our existing unit tests, e.g. `tests/Java.Interop-Tests`, exercise the first mode of operation. The second mode of operation is how .NET Android executes. Add `samples/Hello-NativeAOTFromJNI`, a new sample which exercises the second mode of operation: 1. `Hello-NativeAOTFromJNI` contains C# methods which use [`UnmanagedCallersOnlyAttribute`][4] to export `JNI_OnLoad()` and various other native symbols for JNI to execute. 2. `dotnet publish Hello-NativeAOTFromJNI.csproj` will use [NativeAOT][5] to produce a native library which can be loaded and used by a desktop JVM. 3. `Hello-NativeAOTFromJNI` references `Java.Base.dll` (bc5bcf4), and contains a C# `Java.Lang.Object` subclass, which contains a `[JavaCallable]` method. 4. `jcw-gen` is called during the build process to generate Java Callable Wrappers for (3), containing the `[JavaCallable]` method. 5. `jnimarshalmethod-gen` (25850ba) is called during the build process to generate marshal methods for (3), as `generator` output for JavaInterop1 doesn't contain marshal methods (unlike XAJavaInterop1, used by .NET Android), and NativeAOT doesn't support System.Reflection.Emit. 6. The type in (3) is instantiated *from Java*, and Java invokes the `[JavaCallable]` method. 7. `Hello-NativeAOTFromJNI` also contains Java code including a `main()` method (to start execution from Java) Conceptually straightforward! See `samples/Hello-NativeAOTFromJNI/README.md` for more details. ~~ `$(Standalone)`=true is now the default build config ~~ The `$(Standalone)` build config (c6c487b) is now enabled by default. This means that neither `Java.Interop.dll` nor `Java.Runtime.Environment.dll` *require* a `java-interop` native library, which (1) was already the case for xamarin-android as of dotnet/android@180dd520, and (2) simplifies NativeAOT support immensely. ~~ Avoid Adding Public API ~~ In order to avoid adding new public API to `Java.Interop.dll`, update `Java.Interop.dll` to have `[InternalsVisibleTo]` for `Java.Runtime.Environment.dll`. This allows `JreTypeManager` to use `JniRuntime.UseMarshalMemberBuilder`, without needing to worry about `try`/`catch` blocks. This in turn requires renaming `NativeMethods` within `Java.Runtime.Environment.dll` to `JreNativeMethods`, as `Java.Interop.dll` *also* has a `NativeMethods` type. ~~ NativeAOT Integration ~~ Conceptually straightforward, but not without lots of pitfalls. In order for Java code to call into C# code, there must be a Java Callable Wrapper, which contains Java `native` methods, and those Java `native` methods must be *registered with Java* before they are executed. Method registration involves lots of delegates, which need to be stored in the `JniNativeMethodRegistration.Delegate` field, of type `System.Delegate`. NativeAOT doesn't like this by default. In order to convince NativeAOT to support this, every delegate instance provided to `JniNativeMethodRegistration.Delegate` must be of a delegate type which has [`UnmanagedFunctionPointerAttribute`][6]. This coerces NativeAOT to emit "stubs" for the referenced method, allowing things to work with fewer changes. Linux builds require `-Wl,-soname` to be set manually in order for the resulting `.so` to work properly. ~~ Trimmer Warnings ~~ Fix additional trimmer warnings, a'la #1157. `Type.MakeGenericType()` needs to ensure constructors are preserved: src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs(378): Trim analysis warning IL2068: Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[]): 'Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[])' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' requirements. The parameter 'type' of method 'Java.Interop.JniRuntime.JniValueManager.<GetInvokerType>g__MakeGenericType|31_1(Type,Type[])' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. `Expression.New()` requires the constructor be kept, so "fake it" by wrapping `Type.GetType()` w/ `[DynamicallyAccessedMembers]`: src/Java.Interop/Java.Interop/JniValueMarshaler.cs(175): Trim analysis warning IL2072: Java.Interop.JniValueMarshaler.CreateSelf(JniValueMarshalerContext,ParameterExpression): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Linq.Expressions.Expression.New(Type)'. The return value of method 'System.Object.GetType()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. `[DynamicallyAccessedMembers]` should be on properties, not getters: src/Java.Interop/Java.Interop/JniValueMarshalerAttribute.cs(33): Trim analysis warning IL2078: Java.Interop.JniValueMarshalerAttribute.MarshalerType.get: 'Java.Interop.JniValueMarshalerAttribute.MarshalerType.get' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor', 'DynamicallyAccessedMemberTypes.Interfaces' requirements. The field 'Java.Interop.JniValueMarshalerAttribute.<MarshalerType>k__BackingField' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. Silence use of `Assembly.Location`, when it's optional: src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs(106): warning IL3000: Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions): 'System.Reflection.Assembly.Location.get' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs(323): warning IL3000: Java.Interop.JreNativeMethods..cctor(): 'System.Reflection.Assembly.Location.get' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. ~~ Future Work ~~ Make `JniRuntime.UseMarshalMemberBuilder` public for .NET 9, and remove the `InternalsVisibleTo` from `Java.Interop.dll` to `Java.Runtime.Environment.dll`. `generator` should be updated to apply `UnmanagedFunctionPointerAttribute` onto all `_JniMarshal_*` delegate type declarations (56955d9). [4]: https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-8.0 [5]: https://learn.microsoft.com/dotnet/core/deploying/native-aot [6]: https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.unmanagedfunctionpointerattribute?view=net-8.0
1 parent c8fcf70 commit 2197579

24 files changed

+613
-49
lines changed

Java.Interop.sln

+7
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressi
113113
EndProject
114114
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressions-Tests", "tests\Java.Interop.Tools.Expressions-Tests\Java.Interop.Tools.Expressions-Tests.csproj", "{211BAA88-66B1-41B2-88B2-530DBD8DF702}"
115115
EndProject
116+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello-NativeAOTFromJNI", "samples\Hello-NativeAOTFromJNI\Hello-NativeAOTFromJNI.csproj", "{8DB3842B-73D7-491C-96F9-EBC863E2C917}"
117+
EndProject
116118
Global
117119
GlobalSection(SharedMSBuildProjectFiles) = preSolution
118120
src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5
@@ -320,6 +322,10 @@ Global
320322
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Debug|Any CPU.Build.0 = Debug|Any CPU
321323
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.ActiveCfg = Release|Any CPU
322324
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.Build.0 = Release|Any CPU
325+
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
326+
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.Build.0 = Debug|Any CPU
327+
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Release|Any CPU.ActiveCfg = Release|Any CPU
328+
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Release|Any CPU.Build.0 = Release|Any CPU
323329
EndGlobalSection
324330
GlobalSection(SolutionProperties) = preSolution
325331
HideSolutionNode = FALSE
@@ -374,6 +380,7 @@ Global
374380
{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
375381
{1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
376382
{211BAA88-66B1-41B2-88B2-530DBD8DF702} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
383+
{8DB3842B-73D7-491C-96F9-EBC863E2C917} = {D5A93398-AEB1-49F3-89DC-3904A47DB0C7}
377384
EndGlobalSection
378385
GlobalSection(ExtensibilityGlobals) = postSolution
379386
SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5}

build-tools/automation/azure-pipelines.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
- template: templates\core-tests.yaml
5858
parameters:
5959
runNativeDotnetTests: true
60+
nativeAotRid: win-x64
6061
platformName: .NET - Windows
6162

6263
- template: templates\fail-on-issue.yaml
@@ -87,6 +88,7 @@ jobs:
8788
- template: templates\core-tests.yaml
8889
parameters:
8990
runNativeTests: true
91+
nativeAotRid: osx-x64
9092
platformName: .NET - MacOS
9193

9294
- template: templates\fail-on-issue.yaml

build-tools/automation/templates/core-tests.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ parameters:
22
condition: succeeded()
33
runNativeTests: false
44
platformName:
5+
nativeAotRid:
56

67
steps:
78
- task: DotNetCoreCLI@2
@@ -173,6 +174,19 @@ steps:
173174
arguments: -c $(Build.Configuration) tools/java-source-utils/java-source-utils.csproj -t:RunTests
174175
continueOnError: true
175176

177+
- powershell: >
178+
dotnet publish -c $(Build.Configuration) -r ${{ parameters.nativeAotRid }}
179+
samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj
180+
displayName: 'Tests: publish Hello-NativeAOTFromJNI'
181+
continueOnError: true
182+
183+
- powershell: >
184+
dotnet build -c $(Build.Configuration) -r ${{ parameters.nativeAotRid }}
185+
-t:RunJavaSample
186+
samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj
187+
displayName: 'Tests: run Hello-NativeAOTFromJNI'
188+
continueOnError: true
189+
176190
- task: PublishTestResults@2
177191
displayName: Publish JUnit Test Results
178192
inputs:

samples/Hello-NativeAOTFromJNI/App.cs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Runtime.InteropServices;
2+
3+
using Java.Interop;
4+
5+
namespace Hello_NativeAOTFromJNI;
6+
7+
static class App {
8+
9+
// symbol name from `$(IntermediateOutputPath)obj/Release/osx-x64/h-classes/net_dot_jni_hello_App.h`
10+
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_hello_App_sayHello")]
11+
static IntPtr sayHello (IntPtr jnienv, IntPtr klass)
12+
{
13+
var envp = new JniTransition (jnienv);
14+
try {
15+
var s = $"Hello from .NET NativeAOT!";
16+
Console.WriteLine (s);
17+
var h = JniEnvironment.Strings.NewString (s);
18+
var r = JniEnvironment.References.NewReturnToJniRef (h);
19+
JniObjectReference.Dispose (ref h);
20+
return r;
21+
}
22+
catch (Exception e) {
23+
Console.Error.WriteLine ($"Error in App.sayHello(): {e.ToString ()}");
24+
envp.SetPendingException (e);
25+
}
26+
finally {
27+
envp.Dispose ();
28+
}
29+
return nint.Zero;
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
5+
</PropertyGroup>
6+
7+
<Import Project="..\..\TargetFrameworkDependentValues.props" />
8+
9+
<PropertyGroup>
10+
<RootNamespace>Hello_NativeAOTFromJNI</RootNamespace>
11+
<ImplicitUsings>enable</ImplicitUsings>
12+
<Nullable>enable</Nullable>
13+
<PublishAot>true</PublishAot>
14+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
15+
<NativeLib>Shared</NativeLib>
16+
<!-- Needed for cross-compilation, e.g. build osx-x64 from osx-arm64 -->
17+
<PlatformTarget>AnyCPU</PlatformTarget>
18+
</PropertyGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\..\src\Java.Interop\Java.Interop.csproj" />
22+
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
23+
<ProjectReference Include="..\..\src\Java.Base\Java.Base.csproj" />
24+
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
25+
<ProjectReference
26+
Include="..\..\tools\jcw-gen\jcw-gen.csproj"
27+
ReferenceOutputAssembly="false"
28+
/>
29+
<ProjectReference
30+
Include="..\..\tools\jnimarshalmethod-gen\Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj"
31+
ReferenceOutputAssembly="false"
32+
/>
33+
</ItemGroup>
34+
35+
<ItemGroup>
36+
<TrimmerRootAssembly Include="Hello-NativeAOTFromJNI" />
37+
</ItemGroup>
38+
39+
<ItemGroup>
40+
<Content Include="$(OutputPath)hello-from-java.jar" CopyToPublishDirectory="PreserveNewest" />
41+
</ItemGroup>
42+
43+
<ItemGroup>
44+
<HelloNativeAOTFromJNIJar Include="$(MSBuildThisFileDirectory)java\**\*.java" />
45+
</ItemGroup>
46+
47+
<ItemGroup Condition=" $([MSBuild]::IsOSPlatform('linux')) Or $([MSBuild]::IsOSPlatform('FreeBSD')) Or $([MSBuild]::IsOSPlatform('Android')) ">
48+
<CustomLinkerArg Include="-Wl,-soname,lib$(NativeBinary)$(NativeBinaryExt)" />
49+
</ItemGroup>
50+
51+
<Import Project="Hello-NativeAOTFromJNI.targets" />
52+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<Project>
2+
3+
<PropertyGroup>
4+
<DotnetToolPath>"$(DOTNET_HOST_PATH)"</DotnetToolPath>
5+
</PropertyGroup>
6+
7+
<Target Name="_CreateJavaCallableWrappers"
8+
Condition=" '$(TargetPath)' != '' "
9+
BeforeTargets="BuildNativeAOTFromJNIJar"
10+
Inputs="$(TargetPath)"
11+
Outputs="$(IntermediateOutputPath)java\.stamp">
12+
<RemoveDir Directories="$(IntermediateOutputPath)java" />
13+
<MakeDir Directories="$(IntermediateOutputPath)java" />
14+
<ItemGroup>
15+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
16+
<_JcwGenRefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
17+
</ItemGroup>
18+
<PropertyGroup>
19+
<_JcwGen>"$(UtilityOutputFullPath)/jcw-gen.dll"</_JcwGen>
20+
<_Target>--codegen-target JavaInterop1</_Target>
21+
<_Output>-o "$(IntermediateOutputPath)/java"</_Output>
22+
<_Libpath>@(_JcwGenRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
23+
</PropertyGroup>
24+
<Exec Command="$(DotnetToolPath) $(_JcwGen) &quot;$(TargetPath)&quot; $(_Target) $(_Output) $(_Libpath)" />
25+
<Touch Files="$(IntermediateOutputPath)java\.stamp" AlwaysCreate="True" />
26+
</Target>
27+
28+
<Target Name="_AddMarshalMethods"
29+
Condition=" '$(TargetPath)' != '' "
30+
Inputs="$(TargetPath)"
31+
Outputs="$(IntermediateOutputPath).added-marshal-methods"
32+
AfterTargets="_CreateJavaCallableWrappers">
33+
<ItemGroup>
34+
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
35+
<_JnimmRefAsmDirs Include="@(RuntimePackAsset->'%(RootDir)%(Directory).'->Distinct())" />
36+
</ItemGroup>
37+
<PropertyGroup>
38+
<_JnimarshalmethodGen>"$(UtilityOutputFullPath)/jnimarshalmethod-gen.dll"</_JnimarshalmethodGen>
39+
<_Verbosity>-v -v --keeptemp</_Verbosity>
40+
<_Libpath>-L "$(TargetDir)" @(_JnimmRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
41+
<!-- <_Output>-o "$(IntermediateOutputPath)/jonp"</_Output> -->
42+
</PropertyGroup>
43+
44+
<Exec Command="$(DotnetToolPath) $(_JnimarshalmethodGen) &quot;$(TargetPath)&quot; $(_Verbosity) $(_Libpath)" />
45+
46+
<!-- the IlcCompile target uses files from `$(IntermediateOutputPath)`, not `$(TargetPath)`, so… update both? -->
47+
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(IntermediateOutputPath)" />
48+
49+
<Touch Files="$(IntermediateOutputPath).added-marshal-methods" AlwaysCreate="True" />
50+
</Target>
51+
52+
<Target Name="BuildNativeAOTFromJNIJar"
53+
AfterTargets="Build"
54+
Inputs="@(HelloNativeAOTFromJNIJar)"
55+
Outputs="$(OutputPath)hello-from-java.jar">
56+
<MakeDir Directories="$(IntermediateOutputPath)h-classes" />
57+
<ItemGroup>
58+
<_JcwSource Include="$(IntermediateOutputPath)java\**\*.java" />
59+
</ItemGroup>
60+
<ItemGroup>
61+
<_Source Include="@(_JcwSource->Replace('%5c', '/'))" />
62+
<_Source Include="@(HelloNativeAOTFromJNIJar->Replace('%5c', '/'))" />
63+
</ItemGroup>
64+
<WriteLinesToFile
65+
File="$(IntermediateOutputPath)_java_sources.txt"
66+
Lines="@(_Source)"
67+
Overwrite="True"
68+
/>
69+
<ItemGroup>
70+
<_JavacOpt Include="$(_JavacSourceOptions)" />
71+
<_JavacOpt Include="-d &quot;$(IntermediateOutputPath)h-classes&quot; " />
72+
<_JavacOpt Include="-classpath &quot;$(OutputPath)java-interop.jar&quot; " />
73+
<_JavacOpt Include="&quot;@$(IntermediateOutputPath)_java_sources.txt&quot;" />
74+
<_JavacOpt Include="-h &quot;$(IntermediateOutputPath)h-classes&quot; " />
75+
</ItemGroup>
76+
<Exec Command="&quot;$(JavaCPath)&quot; @(_JavacOpt, ' ')" />
77+
<Delete Files="$(IntermediateOutputPath)_java_sources.txt" />
78+
<Exec Command="&quot;$(JarPath)&quot; cf &quot;$(OutputPath)hello-from-java.jar&quot; -C &quot;$(IntermediateOutputPath)h-classes&quot; ." />
79+
</Target>
80+
81+
<Target Name="_NativeLibRequiresLibPrefix"
82+
AfterTargets="Publish">
83+
<Copy
84+
Condition=" '$(OS)' != 'Windows_NT' "
85+
SourceFiles="$(PublishDir)$(AssemblyName).dylib"
86+
DestinationFiles="$(PublishDir)lib$(AssemblyName).dylib"
87+
/>
88+
<Copy SourceFiles="$(OutputPath)hello-from-java.jar" DestinationFolder="$(PublishDir)" />
89+
</Target>
90+
91+
<Target Name="RunJavaSample">
92+
<ItemGroup>
93+
<_Classpath Include="hello-from-java.jar" />
94+
<_Classpath Include="java-interop.jar" />
95+
</ItemGroup>
96+
<PropertyGroup>
97+
<_CPSep Condition=" '$(OS)' == 'Windows_NT' ">;</_CPSep>
98+
<_CPSep Condition=" '$(_CPSep)' == '' ">:</_CPSep>
99+
<_CP>@(_Classpath, '$(_CPSep)')</_CP>
100+
</PropertyGroup>
101+
<Exec
102+
Command="&quot;$(JavaPath)&quot; -classpath &quot;$(_CP)&quot; net/dot/jni/hello/App"
103+
WorkingDirectory="$(MSBuildThisFileDirectory)$(PublishDir)"
104+
/>
105+
</Target>
106+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Runtime.InteropServices;
2+
3+
using Java.Interop;
4+
5+
namespace Hello_NativeAOTFromJNI;
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+
return (int) JniVersion.v1_6;
15+
}
16+
17+
[UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")]
18+
static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
19+
{
20+
runtime?.Dispose ();
21+
}
22+
23+
// symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h`
24+
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_hello_JavaInteropRuntime_init")]
25+
static void init (IntPtr jnienv, IntPtr klass)
26+
{
27+
Console.WriteLine ($"C# init()");
28+
try {
29+
var options = new JreRuntimeOptions {
30+
EnvironmentPointer = jnienv,
31+
TypeManager = new NativeAotTypeManager (),
32+
UseMarshalMemberBuilder = false,
33+
};
34+
runtime = options.CreateJreVM ();
35+
}
36+
catch (Exception e) {
37+
Console.Error.WriteLine ($"JavaInteropRuntime.init: error: {e}");
38+
}
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Example;
2+
3+
using Java.Interop;
4+
5+
[JniTypeSignature (JniTypeName)]
6+
class ManagedType : Java.Lang.Object {
7+
internal const string JniTypeName = "example/ManagedType";
8+
9+
[JavaCallableConstructor(SuperConstructorExpression="")]
10+
public ManagedType (int value)
11+
{
12+
this.value = value;
13+
}
14+
15+
int value;
16+
17+
[JavaCallable ("getString")]
18+
public Java.Lang.String GetString ()
19+
{
20+
return new Java.Lang.String ($"Hello from C#, via Java.Interop! Value={value}");
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Java.Interop;
2+
3+
namespace Hello_NativeAOTFromJNI;
4+
5+
class NativeAotTypeManager : JniRuntime.JniTypeManager {
6+
7+
#pragma warning disable IL2026
8+
Dictionary<string, Type> typeMappings = new () {
9+
[Example.ManagedType.JniTypeName] = typeof (Example.ManagedType),
10+
};
11+
#pragma warning restore IL2026
12+
13+
14+
protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
15+
{
16+
if (typeMappings.TryGetValue (jniSimpleReference, out var target))
17+
yield return target;
18+
foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference))
19+
yield return t;
20+
}
21+
22+
protected override IEnumerable<string> GetSimpleReferences (Type type)
23+
{
24+
return base.GetSimpleReferences (type)
25+
.Concat (CreateSimpleReferencesEnumerator (type));
26+
}
27+
28+
IEnumerable<string> CreateSimpleReferencesEnumerator (Type type)
29+
{
30+
if (typeMappings == null)
31+
yield break;
32+
foreach (var e in typeMappings) {
33+
if (e.Value == type)
34+
yield return e.Key;
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)