Skip to content

Commit

Permalink
[Hello-NativeAOTFromJNI] Add NativeAOT sample (#1153)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jonpryor authored Feb 22, 2024
1 parent c8fcf70 commit 2197579
Show file tree
Hide file tree
Showing 24 changed files with 613 additions and 49 deletions.
7 changes: 7 additions & 0 deletions Java.Interop.sln
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Java.Interop.Tools.Expressi
EndProject
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello-NativeAOTFromJNI", "samples\Hello-NativeAOTFromJNI\Hello-NativeAOTFromJNI.csproj", "{8DB3842B-73D7-491C-96F9-EBC863E2C917}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Java.Interop.NamingCustomAttributes\Java.Interop.NamingCustomAttributes.projitems*{58b564a1-570d-4da2-b02d-25bddb1a9f4f}*SharedItemsImports = 5
Expand Down Expand Up @@ -320,6 +322,10 @@ Global
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Debug|Any CPU.Build.0 = Debug|Any CPU
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.ActiveCfg = Release|Any CPU
{211BAA88-66B1-41B2-88B2-530DBD8DF702}.Release|Any CPU.Build.0 = Release|Any CPU
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8DB3842B-73D7-491C-96F9-EBC863E2C917}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -374,6 +380,7 @@ Global
{CB05E11B-B96F-4179-A4E9-5D6BDE29A8FC} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{1A0262FE-3CDB-4AF2-AAD8-65C59524FE8A} = {0998E45F-8BCE-4791-A944-962CD54E2D80}
{211BAA88-66B1-41B2-88B2-530DBD8DF702} = {271C9F30-F679-4793-942B-0D9527CB3E2F}
{8DB3842B-73D7-491C-96F9-EBC863E2C917} = {D5A93398-AEB1-49F3-89DC-3904A47DB0C7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {29204E0C-382A-49A0-A814-AD7FBF9774A5}
Expand Down
2 changes: 2 additions & 0 deletions build-tools/automation/azure-pipelines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
- template: templates\core-tests.yaml
parameters:
runNativeDotnetTests: true
nativeAotRid: win-x64
platformName: .NET - Windows

- template: templates\fail-on-issue.yaml
Expand Down Expand Up @@ -87,6 +88,7 @@ jobs:
- template: templates\core-tests.yaml
parameters:
runNativeTests: true
nativeAotRid: osx-x64
platformName: .NET - MacOS

- template: templates\fail-on-issue.yaml
Expand Down
14 changes: 14 additions & 0 deletions build-tools/automation/templates/core-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ parameters:
condition: succeeded()
runNativeTests: false
platformName:
nativeAotRid:

steps:
- task: DotNetCoreCLI@2
Expand Down Expand Up @@ -173,6 +174,19 @@ steps:
arguments: -c $(Build.Configuration) tools/java-source-utils/java-source-utils.csproj -t:RunTests
continueOnError: true

- powershell: >
dotnet publish -c $(Build.Configuration) -r ${{ parameters.nativeAotRid }}
samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj
displayName: 'Tests: publish Hello-NativeAOTFromJNI'
continueOnError: true

- powershell: >
dotnet build -c $(Build.Configuration) -r ${{ parameters.nativeAotRid }}
-t:RunJavaSample
samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj
displayName: 'Tests: run Hello-NativeAOTFromJNI'
continueOnError: true

- task: PublishTestResults@2
displayName: Publish JUnit Test Results
inputs:
Expand Down
31 changes: 31 additions & 0 deletions samples/Hello-NativeAOTFromJNI/App.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Runtime.InteropServices;

using Java.Interop;

namespace Hello_NativeAOTFromJNI;

static class App {

// symbol name from `$(IntermediateOutputPath)obj/Release/osx-x64/h-classes/net_dot_jni_hello_App.h`
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_hello_App_sayHello")]
static IntPtr sayHello (IntPtr jnienv, IntPtr klass)
{
var envp = new JniTransition (jnienv);
try {
var s = $"Hello from .NET NativeAOT!";
Console.WriteLine (s);
var h = JniEnvironment.Strings.NewString (s);
var r = JniEnvironment.References.NewReturnToJniRef (h);
JniObjectReference.Dispose (ref h);
return r;
}
catch (Exception e) {
Console.Error.WriteLine ($"Error in App.sayHello(): {e.ToString ()}");
envp.SetPendingException (e);
}
finally {
envp.Dispose ();
}
return nint.Zero;
}
}
52 changes: 52 additions & 0 deletions samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
</PropertyGroup>

<Import Project="..\..\TargetFrameworkDependentValues.props" />

<PropertyGroup>
<RootNamespace>Hello_NativeAOTFromJNI</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NativeLib>Shared</NativeLib>
<!-- Needed for cross-compilation, e.g. build osx-x64 from osx-arm64 -->
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Java.Interop\Java.Interop.csproj" />
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
<ProjectReference Include="..\..\src\Java.Base\Java.Base.csproj" />
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
<ProjectReference
Include="..\..\tools\jcw-gen\jcw-gen.csproj"
ReferenceOutputAssembly="false"
/>
<ProjectReference
Include="..\..\tools\jnimarshalmethod-gen\Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj"
ReferenceOutputAssembly="false"
/>
</ItemGroup>

<ItemGroup>
<TrimmerRootAssembly Include="Hello-NativeAOTFromJNI" />
</ItemGroup>

<ItemGroup>
<Content Include="$(OutputPath)hello-from-java.jar" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<HelloNativeAOTFromJNIJar Include="$(MSBuildThisFileDirectory)java\**\*.java" />
</ItemGroup>

<ItemGroup Condition=" $([MSBuild]::IsOSPlatform('linux')) Or $([MSBuild]::IsOSPlatform('FreeBSD')) Or $([MSBuild]::IsOSPlatform('Android')) ">
<CustomLinkerArg Include="-Wl,-soname,lib$(NativeBinary)$(NativeBinaryExt)" />
</ItemGroup>

<Import Project="Hello-NativeAOTFromJNI.targets" />
</Project>
106 changes: 106 additions & 0 deletions samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<Project>

<PropertyGroup>
<DotnetToolPath>"$(DOTNET_HOST_PATH)"</DotnetToolPath>
</PropertyGroup>

<Target Name="_CreateJavaCallableWrappers"
Condition=" '$(TargetPath)' != '' "
BeforeTargets="BuildNativeAOTFromJNIJar"
Inputs="$(TargetPath)"
Outputs="$(IntermediateOutputPath)java\.stamp">
<RemoveDir Directories="$(IntermediateOutputPath)java" />
<MakeDir Directories="$(IntermediateOutputPath)java" />
<ItemGroup>
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
<_JcwGenRefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
</ItemGroup>
<PropertyGroup>
<_JcwGen>"$(UtilityOutputFullPath)/jcw-gen.dll"</_JcwGen>
<_Target>--codegen-target JavaInterop1</_Target>
<_Output>-o "$(IntermediateOutputPath)/java"</_Output>
<_Libpath>@(_JcwGenRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
</PropertyGroup>
<Exec Command="$(DotnetToolPath) $(_JcwGen) &quot;$(TargetPath)&quot; $(_Target) $(_Output) $(_Libpath)" />
<Touch Files="$(IntermediateOutputPath)java\.stamp" AlwaysCreate="True" />
</Target>

<Target Name="_AddMarshalMethods"
Condition=" '$(TargetPath)' != '' "
Inputs="$(TargetPath)"
Outputs="$(IntermediateOutputPath).added-marshal-methods"
AfterTargets="_CreateJavaCallableWrappers">
<ItemGroup>
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
<_JnimmRefAsmDirs Include="@(RuntimePackAsset->'%(RootDir)%(Directory).'->Distinct())" />
</ItemGroup>
<PropertyGroup>
<_JnimarshalmethodGen>"$(UtilityOutputFullPath)/jnimarshalmethod-gen.dll"</_JnimarshalmethodGen>
<_Verbosity>-v -v --keeptemp</_Verbosity>
<_Libpath>-L "$(TargetDir)" @(_JnimmRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
<!-- <_Output>-o "$(IntermediateOutputPath)/jonp"</_Output> -->
</PropertyGroup>

<Exec Command="$(DotnetToolPath) $(_JnimarshalmethodGen) &quot;$(TargetPath)&quot; $(_Verbosity) $(_Libpath)" />

<!-- the IlcCompile target uses files from `$(IntermediateOutputPath)`, not `$(TargetPath)`, so… update both? -->
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(IntermediateOutputPath)" />

<Touch Files="$(IntermediateOutputPath).added-marshal-methods" AlwaysCreate="True" />
</Target>

<Target Name="BuildNativeAOTFromJNIJar"
AfterTargets="Build"
Inputs="@(HelloNativeAOTFromJNIJar)"
Outputs="$(OutputPath)hello-from-java.jar">
<MakeDir Directories="$(IntermediateOutputPath)h-classes" />
<ItemGroup>
<_JcwSource Include="$(IntermediateOutputPath)java\**\*.java" />
</ItemGroup>
<ItemGroup>
<_Source Include="@(_JcwSource->Replace('%5c', '/'))" />
<_Source Include="@(HelloNativeAOTFromJNIJar->Replace('%5c', '/'))" />
</ItemGroup>
<WriteLinesToFile
File="$(IntermediateOutputPath)_java_sources.txt"
Lines="@(_Source)"
Overwrite="True"
/>
<ItemGroup>
<_JavacOpt Include="$(_JavacSourceOptions)" />
<_JavacOpt Include="-d &quot;$(IntermediateOutputPath)h-classes&quot; " />
<_JavacOpt Include="-classpath &quot;$(OutputPath)java-interop.jar&quot; " />
<_JavacOpt Include="&quot;@$(IntermediateOutputPath)_java_sources.txt&quot;" />
<_JavacOpt Include="-h &quot;$(IntermediateOutputPath)h-classes&quot; " />
</ItemGroup>
<Exec Command="&quot;$(JavaCPath)&quot; @(_JavacOpt, ' ')" />
<Delete Files="$(IntermediateOutputPath)_java_sources.txt" />
<Exec Command="&quot;$(JarPath)&quot; cf &quot;$(OutputPath)hello-from-java.jar&quot; -C &quot;$(IntermediateOutputPath)h-classes&quot; ." />
</Target>

<Target Name="_NativeLibRequiresLibPrefix"
AfterTargets="Publish">
<Copy
Condition=" '$(OS)' != 'Windows_NT' "
SourceFiles="$(PublishDir)$(AssemblyName).dylib"
DestinationFiles="$(PublishDir)lib$(AssemblyName).dylib"
/>
<Copy SourceFiles="$(OutputPath)hello-from-java.jar" DestinationFolder="$(PublishDir)" />
</Target>

<Target Name="RunJavaSample">
<ItemGroup>
<_Classpath Include="hello-from-java.jar" />
<_Classpath Include="java-interop.jar" />
</ItemGroup>
<PropertyGroup>
<_CPSep Condition=" '$(OS)' == 'Windows_NT' ">;</_CPSep>
<_CPSep Condition=" '$(_CPSep)' == '' ">:</_CPSep>
<_CP>@(_Classpath, '$(_CPSep)')</_CP>
</PropertyGroup>
<Exec
Command="&quot;$(JavaPath)&quot; -classpath &quot;$(_CP)&quot; net/dot/jni/hello/App"
WorkingDirectory="$(MSBuildThisFileDirectory)$(PublishDir)"
/>
</Target>
</Project>
40 changes: 40 additions & 0 deletions samples/Hello-NativeAOTFromJNI/JavaInteropRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Runtime.InteropServices;

using Java.Interop;

namespace Hello_NativeAOTFromJNI;

static class JavaInteropRuntime
{
static JniRuntime? runtime;

[UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")]
static int JNI_OnLoad (IntPtr vm, IntPtr reserved)
{
return (int) JniVersion.v1_6;
}

[UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")]
static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
{
runtime?.Dispose ();
}

// symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h`
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_hello_JavaInteropRuntime_init")]
static void init (IntPtr jnienv, IntPtr klass)
{
Console.WriteLine ($"C# init()");
try {
var options = new JreRuntimeOptions {
EnvironmentPointer = jnienv,
TypeManager = new NativeAotTypeManager (),
UseMarshalMemberBuilder = false,
};
runtime = options.CreateJreVM ();
}
catch (Exception e) {
Console.Error.WriteLine ($"JavaInteropRuntime.init: error: {e}");
}
}
}
22 changes: 22 additions & 0 deletions samples/Hello-NativeAOTFromJNI/ManagedType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Example;

using Java.Interop;

[JniTypeSignature (JniTypeName)]
class ManagedType : Java.Lang.Object {
internal const string JniTypeName = "example/ManagedType";

[JavaCallableConstructor(SuperConstructorExpression="")]
public ManagedType (int value)
{
this.value = value;
}

int value;

[JavaCallable ("getString")]
public Java.Lang.String GetString ()
{
return new Java.Lang.String ($"Hello from C#, via Java.Interop! Value={value}");
}
}
37 changes: 37 additions & 0 deletions samples/Hello-NativeAOTFromJNI/NativeAotTypeManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Java.Interop;

namespace Hello_NativeAOTFromJNI;

class NativeAotTypeManager : JniRuntime.JniTypeManager {

#pragma warning disable IL2026
Dictionary<string, Type> typeMappings = new () {
[Example.ManagedType.JniTypeName] = typeof (Example.ManagedType),
};
#pragma warning restore IL2026


protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
{
if (typeMappings.TryGetValue (jniSimpleReference, out var target))
yield return target;
foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference))
yield return t;
}

protected override IEnumerable<string> GetSimpleReferences (Type type)
{
return base.GetSimpleReferences (type)
.Concat (CreateSimpleReferencesEnumerator (type));
}

IEnumerable<string> CreateSimpleReferencesEnumerator (Type type)
{
if (typeMappings == null)
yield break;
foreach (var e in typeMappings) {
if (e.Value == type)
yield return e.Key;
}
}
}
Loading

0 comments on commit 2197579

Please sign in to comment.