Skip to content

Commit db84cc3

Browse files
committed
Try to create a JreRuntime
BROKEN BROKEN BROKEN BROKEN What do we want? To create a `JreRuntime` instance! …which is shockingly difficult. But first, *why* do we want to create a `JreRuntime` instance? (It's not like we've needed it so far!) Force the issue by changing `NativeAOTInit.sayHello()` to return a `String` instance, thus requiring that for `NativeAOTInit.sayHello()` to return a non-`null` value, it needs a `JreRuntime` instance! This is where everything falls apart. `JreRuntime` and `JreRuntimeOptions` were not designed with this scenario in mind! Update `JreRuntime` & co so that it doesn't require `JvmLibraryPath` when `InvocationPointer` isn't null, and don't require that `JreRuntimeOptions.ClassPath` contain the path to `java-interop.jar` when `InvocationPointer` isn't null. This allows us to create the `JreRuntimeOptions` instance. Yay! The next problem is that setting `%(ProjectReference.AdditionalProperties)` to include `Standalone=true` doesn't appear to work, in that the `Java.Interop.dll` that is included still contains P/Invokes instead of the `JniNativeMethods` class. Update `Java.Interop.csproj` so that `$(Standalone)`=true is now the default. (This *shouldn't* break anybody, but… it now means the P/Invoke backend is getting NO testing. ¯\_(ツ)_/¯ ) The next part is where it blows up GOOD: `options.CreateJreVM()` throws, which aborts everything: % (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App) Hello from Java! # jonp: JNI_OnLoad: vm=10a9c0b00 # jonp: JNI_OnLoad: created options… # jonp: builder.InvocationPointer=10a9c0b00 JNI_OnLoad: error: System.TypeInitializationException: A type initializer threw an exception. To determine which type, inspect the InnerException's StackTrace property. ---> System.NotSupportedException: 'Java.Interop.ManagedPeer+ConstructMarshalMethod' is missing delegate marshalling data. This can happen for code that is not compatible with AOT. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility at Internal.Runtime.CompilerHelpers.RuntimeInteropData.GetDelegateMarshallingStub(RuntimeTypeHandle, Boolean) + 0x78 at System.Runtime.InteropServices.PInvokeMarshal.AllocateThunk(Delegate del) + 0x6b at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValueLocked(TKey, ConditionalWeakTable`2.CreateValueCallback) + 0x27 at System.Runtime.CompilerServices.ConditionalWeakTable`2.GetValue(TKey, ConditionalWeakTable`2.CreateValueCallback) + 0x41 at System.Runtime.InteropServices.PInvokeMarshal.GetFunctionPointerForDelegate(Delegate) + 0xd5 at libHello-NativeAOTFromJNI!<BaseAddress>+0x8adeb at Java.Interop.JniEnvironment.Types._RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], Int32) + 0x90 at Java.Interop.JniEnvironment.Types.RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], Int32) + 0x65 at Java.Interop.JniType.RegisterNativeMethods(JniNativeMethodRegistration[]) + 0x30 at Java.Interop.ManagedPeer..cctor() + 0x175 at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xb4 --- End of inner exception stack trace --- at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x147 at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnGCStaticBase(StaticClassConstructionContext*, Object) + 0x9 at Java.Interop.JniRuntime..ctor(JniRuntime.CreationOptions) + 0x5a2 at Java.Interop.JreRuntime..ctor(JreRuntimeOptions) + 0x22 at Hello_NativeAOTFromJNI.JNIEnvInit.JNI_OnLoad(IntPtr, IntPtr) + 0x9e Exception in thread "main" java.lang.UnsatisfiedLinkError: unsupported JNI version 0x00000000 required by /Volumes/Xamarin-Work/src/xamarin/Java.Interop/samples/Hello-NativeAOTFromJNI/bin/Release/osx-x64/publish/libHello-NativeAOTFromJNI.dylib at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(NativeLibraries.java:388) at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:232) at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:174) at java.base/jdk.internal.loader.NativeLibraries.findFromPaths(NativeLibraries.java:315) at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:287) at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2427) at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:818) at java.base/java.lang.System.loadLibrary(System.java:1989) at com.microsoft.hello_from_jni.NativeAOTInit.<clinit>(NativeAOTInit.java:5) at com.microsoft.hello_from_jni.App.main(App.java:7) Within the `JniRuntime` constructor, we're hitting: #if !XA_JI_EXCLUDE ManagedPeer.Init (); #endif // !XA_JI_EXCLUDE This invokes the `ManagedPeer` static constructor, which attempts to call `JniType.RegisterNativeMethods()`, which attempts to call `JNIEnv::RegisterNatives()`, but before it can get that far it needs to marshal things: 1. The `JniNativeMethodRegistration` struct, which in turn requires 2. The `JniNativeMethodRegistration.Marshaler` delegate field. This apparently isn't supported by NativeAOT, at least not without additional work that I am not currently privy to. Given that `JniNativeMethodRegistration` structure marshaling is how *all* JNI method registration is done in .NET Android, this is a bit of a blocker for this sample. TODO? Figure out how to allow NativeAOT to marshal `JniNativeMethodRegistration` with a minimum of effort? TODO? *Require* `jnimarshalmethod-gen`, and update it so that it emits `[UnmanagedCallersOnlyAttribute]` on all generated marshal methods *and* emits `UnmanagedCallersOnlyAttribute.EntryPoint` to a `Java_…` symbol name so that we *don't* hit the `JniNativeMethodRegistration` struct marshaling codepath. (See also 77800dd). [0]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives
1 parent 007317b commit db84cc3

File tree

6 files changed

+42
-15
lines changed

6 files changed

+42
-15
lines changed

samples/Hello-NativeAOTFromJNI/JNIEnvInit.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@ namespace Hello_NativeAOTFromJNI;
66

77
static class JNIEnvInit
88
{
9-
// static JniRuntime? runtime;
9+
static JniRuntime? runtime;
1010

1111
[UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")]
1212
static int JNI_OnLoad (IntPtr vm, IntPtr reserved)
1313
{
14+
Console.WriteLine ($"# jonp: JNI_OnLoad: vm={vm.ToString("x2")}");
1415
try {
15-
// runtime = new JniRuntime (null);
16-
// return runtime.JniVersion;
17-
return (int) JniVersion.v1_2;
16+
var options = new JreRuntimeOptions {
17+
InvocationPointer = vm,
18+
};
19+
Console.WriteLine ($"# jonp: JNI_OnLoad: created options…");
20+
runtime = options.CreateJreVM ();
21+
Console.WriteLine ($"# jonp: JNI_OnLoad: created runtime…");
22+
return (int) runtime.JniVersion;
1823
}
1924
catch (Exception e) {
20-
Console.Error.WriteLine ($"JNI_OnLoad: {e}");
25+
Console.Error.WriteLine ($"JNI_OnLoad: error: {e}");
2126
return 0;
2227
}
2328
}
@@ -30,8 +35,13 @@ static void JNI_Onload (IntPtr vm, IntPtr reserved)
3035

3136
// symbol name from `$(IntermediateOutputPath)/h-classes/com_microsoft_hello_from_jni_NativeAOTInit.h`
3237
[UnmanagedCallersOnly (EntryPoint="Java_com_microsoft_hello_1from_1jni_NativeAOTInit_sayHello")]
33-
static void sayHello (IntPtr jnienv, IntPtr klass)
38+
static IntPtr sayHello (IntPtr jnienv, IntPtr klass)
3439
{
35-
Console.WriteLine ($"Hello from .NET NativeAOT!");
40+
var s = $"Hello from .NET NativeAOT!";
41+
Console.WriteLine (s);
42+
var h = JniEnvironment.Strings.NewString (s);
43+
var r = JniEnvironment.References.NewReturnToJniRef (h);
44+
JniObjectReference.Dispose (ref h);
45+
return r;
3646
}
3747
}

samples/Hello-NativeAOTFromJNI/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,15 @@ Build succeeded.
5252
0 Error(s)
5353

5454
Time Elapsed 00:00:00.83
55+
56+
% (cd bin/Release/osx-x64/publish ; java -cp hello-from-java.jar:java-interop.jar com/microsoft/hello_from_jni/App)
57+
Hello from Java!
58+
Hello from .NET NativeAOT!
5559
```
5660

61+
Note the use of `(cd …; java …)` so that `libHello-NativeAOTFromJNI.dylib` is
62+
in the current working directory, so that it can be found.
63+
5764
[0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
5865
[1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm
5966
[2]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad

samples/Hello-NativeAOTFromJNI/java/com/microsoft/hello_from_jni/App.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class App {
44

55
public static void main(String[] args) {
66
System.out.println("Hello from Java!");
7-
NativeAOTInit.sayHello();
7+
String s = NativeAOTInit.sayHello();
8+
System.out.println("String returned to Java: " + s);
89
}
910
}

samples/Hello-NativeAOTFromJNI/java/com/microsoft/hello_from_jni/NativeAOTInit.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ class NativeAOTInit {
55
System.loadLibrary("Hello-NativeAOTFromJNI");
66
}
77

8-
public static native void sayHello();
8+
public static native String sayHello();
99
}

src/Java.Interop/Java.Interop.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<LangVersion Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">9.0</LangVersion>
3030
<LangVersion Condition=" '$(LangVersion)' == '' ">8.0</LangVersion>
3131
<Version>$(JICoreLibVersion)</Version>
32+
<Standalone Condition=" '$(Standalone)' == '' ">true</Standalone>
3233
</PropertyGroup>
3334
<PropertyGroup Condition=" '$(Standalone)' == 'True' ">
3435
<DefineConstants Condition=" '$(JIBuildingForNetCoreApp)' == 'True' ">FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS;$(DefineConstants)</DefineConstants>

src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,7 @@ public class JreRuntimeOptions : JniRuntime.CreationOptions {
3939
public JreRuntimeOptions ()
4040
{
4141
JniVersion = JniVersion.v1_2;
42-
ClassPath = new Collection<string> () {
43-
Path.Combine (
44-
Path.GetDirectoryName (typeof (JreRuntimeOptions).Assembly.Location) ?? throw new NotSupportedException (),
45-
"java-interop.jar"),
46-
};
42+
ClassPath = new Collection<string> ();
4743
}
4844

4945
public JreRuntimeOptions AddOption (string option)
@@ -80,7 +76,7 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
8076
{
8177
if (builder == null)
8278
throw new ArgumentNullException ("builder");
83-
if (string.IsNullOrEmpty (builder.JvmLibraryPath))
79+
if (builder.InvocationPointer == IntPtr.Zero && string.IsNullOrEmpty (builder.JvmLibraryPath))
8480
throw new InvalidOperationException ($"Member `{nameof (JreRuntimeOptions)}.{nameof (JreRuntimeOptions.JvmLibraryPath)}` must be set.");
8581

8682
builder.LibraryHandler = JvmLibraryHandler.Create ();
@@ -99,11 +95,23 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
9995
builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter);
10096
}
10197

98+
Console.WriteLine($"# jonp: builder.InvocationPointer={builder.InvocationPointer.ToString("x2")}");
10299
if (builder.InvocationPointer != IntPtr.Zero)
103100
return builder;
101+
Console.WriteLine($"# jonp: loading {builder.JvmLibraryPath}...");
104102

105103
builder.LibraryHandler.LoadJvmLibrary (builder.JvmLibraryPath!);
106104

105+
if (!builder.ClassPath.Any (p => p.EndsWith ("java-interop.jar", StringComparison.OrdinalIgnoreCase))) {
106+
var loc = typeof (JreRuntimeOptions).Assembly.Location;
107+
var dir = string.IsNullOrEmpty (loc) ? null : Path.GetDirectoryName (loc);
108+
var jij = string.IsNullOrEmpty (dir) ? null : Path.Combine (dir, "java-interop.jar");
109+
if (!File.Exists (jij)) {
110+
throw new InvalidOperationException ($"`java-interop.jar` is required. Please add to `JreRuntimeOptions.ClassPath`. Tried to find it in `{jij}`.");
111+
}
112+
builder.ClassPath.Add (jij);
113+
}
114+
107115
var args = new JavaVMInitArgs () {
108116
version = builder.JniVersion,
109117
nOptions = builder.Options.Count + 1,

0 commit comments

Comments
 (0)