Skip to content

Commit 28849ec

Browse files
authored
[Java.Interop] Allow JniRuntime init from JavaVM* and JNIEnv* (#1158)
Context: #1153 [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. Java.Interop samples and unit tests rely on the first approach, e.g. `TestJVM` subclasses `JreRuntime`, which is responsible for calling `JNI_CreateJavaVM()` so that Java code can be used. PR #1153 is exploring the use of [.NET Native AOT][4] to produce a native library which is used with Java-originated initialization. In order to make Java-originated initialization *work*, we need to be able to initialize `JniRuntime` and `JreRuntime` around existing JVM-provided pointers: * The `JavaVM*` provided to `JNI_OnLoad()`, which can be used to set `JniRuntime.CreationOptions.InvocationPointer`: [UnmanagedCallersOnly(EntryPoint="JNI_OnLoad")] int JNI_OnLoad(IntPtr vm, IntPtr reserved) { var options = new JreRuntimeOptions { InvocationPointer = vm, }; var runtime = options.CreateJreVM (); return runtime.JniVersion; return JNI_VERSION_1_6; } * The [`JNIEnv*` value provided to Java `native` methods][5] when they are invoked, which can be used to set `JniRuntime.CreationOptions.EnvironmentPointer`: [UnmanagedCallersOnly(EntryPoint="Java_example_Whatever_init")] void Whatever_init(IntPtr jnienv, IntPtr Whatever_class) { var options = new JreRuntimeOptions { EnvironmentPointer = jnienv, }; var runtime = options.CreateJreVM (); } Update `JniRuntime` and `JreRuntime` to support these Java-originated initialization strategies. In particular, don't require that `JreRuntimeOptions.JvmLibraryPath` be set, avoiding: System.InvalidOperationException: Member `JreRuntimeOptions.JvmLibraryPath` must be set. at Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions builder) at Java.Interop.JreRuntime..ctor(JreRuntimeOptions builder) at Java.Interop.JreRuntimeOptions.CreateJreVM() [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 [4]: https://learn.microsoft.com/dotnet/core/deploying/native-aot/ [5]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#native_method_arguments
1 parent 38c8a82 commit 28849ec

File tree

3 files changed

+62
-10
lines changed

3 files changed

+62
-10
lines changed

src/Java.Interop/Java.Interop/JniRuntime.cs

+28-3
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ protected JniRuntime (CreationOptions options)
165165
{
166166
if (options == null)
167167
throw new ArgumentNullException (nameof (options));
168-
if (options.InvocationPointer == IntPtr.Zero)
169-
throw new ArgumentException ("options.InvocationPointer is null", nameof (options));
168+
if (options.InvocationPointer == IntPtr.Zero && options.EnvironmentPointer == IntPtr.Zero)
169+
throw new ArgumentException ("Need either options.InvocationPointer or options.EnvironmentPointer!", nameof (options));
170170

171171
TrackIDs = options.TrackIDs;
172172
DestroyRuntimeOnDispose = options.DestroyRuntimeOnDispose;
@@ -175,7 +175,12 @@ protected JniRuntime (CreationOptions options)
175175
NewObjectRequired = options.NewObjectRequired;
176176

177177
JniVersion = options.JniVersion;
178-
InvocationPointer = options.InvocationPointer;
178+
179+
if (options.InvocationPointer == IntPtr.Zero && options.EnvironmentPointer != IntPtr.Zero) {
180+
InvocationPointer = GetInvocationPointerFromEnvironmentPointer (options.EnvironmentPointer);
181+
} else {
182+
InvocationPointer = options.InvocationPointer;
183+
}
179184
Invoker = CreateInvoker (InvocationPointer);
180185

181186
SetValueManager (options);
@@ -230,6 +235,26 @@ protected JniRuntime (CreationOptions options)
230235
#endif // !XA_JI_EXCLUDE
231236
}
232237

238+
static unsafe IntPtr GetInvocationPointerFromEnvironmentPointer (IntPtr envp)
239+
{
240+
IntPtr vm = IntPtr.Zero;
241+
#if FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS
242+
if (JniNativeMethods.GetJavaVM (envp, &vm) is int r &&
243+
r != JNI_OK) {
244+
throw new InvalidOperationException ($"Could not obtain JavaVM* from JNIEnv*; JNIEnv::GetJavaVM() returned {r}!");
245+
}
246+
#elif FEATURE_JNIENVIRONMENT_JI_PINVOKES
247+
if (NativeMethods.java_interop_jnienv_get_java_vm (envp, out vm) is int r &&
248+
r != JNI_OK) {
249+
throw new InvalidOperationException ($"Could not obtain JavaVM* from JNIEnv*; JNIEnv::GetJavaVM() returned {r}!");
250+
}
251+
#else // !FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS && !FEATURE_JNIENVIRONMENT_JI_PINVOKES
252+
throw new NotSupportedException ("Cannot obtain JavaVM* from JNIEnv*! " +
253+
"Rebuild with FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS or FEATURE_JNIENVIRONMENT_JI_PINVOKES set!");
254+
#endif // !FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS && !FEATURE_JNIENVIRONMENT_JI_PINVOKES
255+
return vm;
256+
}
257+
233258
T SetRuntime<T> (T value)
234259
where T : class, ISetRuntime
235260
{

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

+15-7
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,9 @@ 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 &&
80+
builder.EnvironmentPointer == IntPtr.Zero &&
81+
string.IsNullOrEmpty (builder.JvmLibraryPath))
8482
throw new InvalidOperationException ($"Member `{nameof (JreRuntimeOptions)}.{nameof (JreRuntimeOptions.JvmLibraryPath)}` must be set.");
8583

8684
builder.LibraryHandler = JvmLibraryHandler.Create ();
@@ -99,11 +97,21 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
9997
builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter);
10098
}
10199

102-
if (builder.InvocationPointer != IntPtr.Zero)
100+
if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero)
103101
return builder;
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 FileNotFoundException ($"`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,

tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs

+19
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ public void JDK_OnlySupportsOneVM ()
3636
Assert.Fail ("Expected NotSupportedException; got: {0}", e);
3737
}
3838
}
39+
40+
[Test]
41+
public void UseInvocationPointerOnNewThread ()
42+
{
43+
var InvocationPointer = JniRuntime.CurrentRuntime.InvocationPointer;
44+
45+
var t = new Thread (() => {
46+
try {
47+
var second = new JreRuntimeOptions () {
48+
InvocationPointer = InvocationPointer,
49+
}.CreateJreVM ();
50+
}
51+
catch (Exception e) {
52+
Assert.Fail ("Expected no exception, got: {0}", e);
53+
}
54+
});
55+
t.Start ();
56+
t.Join ();
57+
}
3958
#endif // !__ANDROID__
4059

4160
[Test]

0 commit comments

Comments
 (0)