Skip to content

Commit d1cce19

Browse files
authored
[java-interop] Require dynamic JVM loading (#338)
There is a (*yugely* important) "philosophical" problem with using `Java.Runtime.Environment.dll.config` to specify the location of the `jvm.dll` to load, as originated in commit caca88d: It cannot possibly work *reliably* on any machine *other than* the build machine. This is the case because the `/configuration/dllmap[@dll='jvm.dll']/@target` value comes from the value of `$(JI_JVM_PATH)`, which is a `make` variable generated during `make prepare` (see also 4bd9297). As such, the only way this can work on an end-users machine is if the end user has the same JDK version installed as the machine which generated `Java.Runtime.Environment.dll.config`, which is *highly* unlikely. (Not everyone on the Xamarin.Android team has the same JDK version!) The only sane course of action, then, is to *not* use `Java.Runtime.Environment.dll.config` to specify the location of the `jvm.dll` to load. Commit 34129b6 started implementing this; let's finish the effort, and require it everywhere. *A* reason that commit 34129b6 kept `Java.Runtime.Environment.dll.config` use was so that unit tests would continue to execute, as the `TestJVM` type uses `Java.Runtime.Environment.dll`, which uses `Java.Runtime.Environment.dll.config`. To support this scenario, update the `<JdkInfo/>` task so that the generated `JdkInfo.mk` file *`export`*s the `$(JI_JVM_PATH)` variable. This allows unit tests executed from e.g. `make run-all-tests` to inherit the `$JI_JVM_PATH` environment variable, allowing them to "implicitly explicitly" specify the location of the JVM to use. With unit test support in place, we can rip out all use of `jvm.dll`, requiring that we instead use e.g. `java-interop!java_interop_jvm_load()` to load the JVM. Additionally, reduce the "public API surface" within `java-interop-jvm.h` by moving the declaration of `DylibJVM` into `java-interop-jvm.c`. The `DylibJVM` type doesn't need to be public, and making it private will allow us to more easily change it.
1 parent 7b56ed3 commit d1cce19

File tree

10 files changed

+78
-85
lines changed

10 files changed

+78
-85
lines changed

Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,8 @@ $(JRE_DLL_CONFIG): src/Java.Runtime.Environment/Java.Runtime.Environment.csproj
178178
$(MSBUILD) $(MSBUILD_FLAGS) $<
179179

180180
run-test-jnimarshal: bin/Test$(CONFIGURATION)/Java.Interop.Export-Tests.dll bin/Test$(CONFIGURATION)/$(JAVA_INTEROP_LIB) $(JRE_DLL_CONFIG)
181-
mv -fv "$(JRE_DLL_CONFIG)" "$(JRE_DLL_CONFIG).bak"
182181
MONO_TRACE_LISTENER=Console.Out \
183182
$(RUNTIME) bin/$(CONFIGURATION)/jnimarshalmethod-gen.exe -v --jvm "$(JI_JVM_PATH)" -L "$(JI_MONO_LIB_PATH)mono/4.5" -L "$(JI_MONO_LIB_PATH)mono/4.5/Facades" "$<"
184-
mv -fv "$(JRE_DLL_CONFIG).bak" "$(JRE_DLL_CONFIG)"
185183
$(call RUN_TEST,$<)
186184

187185
# $(call GEN_CORE_OUTPUT, outdir, suffix, extra)

gendarme-ignore.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,6 @@ M: System.IntPtr Java.Interop.NativeMethods::java_interop_jnienv_get_direct_buff
565565
M: System.Int64 Java.Interop.NativeMethods::java_interop_jnienv_get_direct_buffer_capacity(System.IntPtr,System.IntPtr)
566566
M: Java.Interop.JniObjectReferenceType Java.Interop.NativeMethods::java_interop_jnienv_get_object_ref_type(System.IntPtr,System.IntPtr)
567567
M: System.Int32 Java.Interop.NativeMethods::java_interop_jvm_list(System.IntPtr[],System.Int32,System.Int32&)
568-
M: System.Int32 Java.Interop.NativeMethods::java_interop_jvm_load(System.String)
569568

570569

571570
R: Gendarme.Rules.Naming.UseCorrectSuffixRule

src/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks/JdkInfo.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ void WritePropertyFile (string jarPath, string javacPath, string jdkJvmPath, IEn
147147
void WriteMakeFragmentFile (string jarPath, string javacPath, string jdkJvmPath, IEnumerable<string> includes)
148148
{
149149
using (var o = new StreamWriter (MakeFragmentFile.ItemSpec)) {
150-
o.WriteLine ($"JI_JAR_PATH := {jarPath}");
151-
o.WriteLine ($"JI_JAVAC_PATH := {javacPath}");
152-
o.WriteLine ($"JI_JDK_INCLUDE_PATHS := {string.Join (" ", includes)}");
153-
o.WriteLine ($"JI_JVM_PATH := {jdkJvmPath}");
150+
o.WriteLine ($"export JI_JAR_PATH := {jarPath}");
151+
o.WriteLine ($"export JI_JAVAC_PATH := {javacPath}");
152+
o.WriteLine ($"export JI_JDK_INCLUDE_PATHS := {string.Join (" ", includes)}");
153+
o.WriteLine ($"export JI_JVM_PATH := {jdkJvmPath}");
154154
}
155155
}
156156

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

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public partial class CreationOptions {
6060

6161
public JniObjectReferenceManager ObjectReferenceManager {get; set;}
6262
public JniTypeManager TypeManager {get; set;}
63-
public string JvmDllPath {get; set;}
63+
public string JvmLibraryPath {get; set;}
6464

6565
public CreationOptions ()
6666
{
@@ -77,17 +77,10 @@ interface ISetRuntime {
7777
}
7878

7979
partial class NativeMethods {
80-
const string JvmLibrary = "jvm.dll";
8180
const string JavaInteropLibrary = "java-interop";
8281

83-
[DllImport (JvmLibrary)]
84-
internal static extern int JNI_GetCreatedJavaVMs ([Out] IntPtr[] handles, int bufLen, out int nVMs);
85-
8682
[DllImport (JavaInteropLibrary)]
8783
internal static extern int java_interop_jvm_list ([Out] IntPtr[] handles, int bufLen, out int nVMs);
88-
89-
[DllImport (JavaInteropLibrary, CharSet=CharSet.Ansi)]
90-
internal static extern int java_interop_jvm_load (string path);
9184
}
9285

9386
public partial class JniRuntime : IDisposable
@@ -96,8 +89,6 @@ public partial class JniRuntime : IDisposable
9689
const int JNI_EDETACHED = -2;
9790
const int JNI_EVERSION = -3;
9891

99-
static public bool JvmDllLoaded { get; private set; }
100-
10192
static ConcurrentDictionary<IntPtr, JniRuntime> Runtimes = new ConcurrentDictionary<IntPtr, JniRuntime> ();
10293

10394
public static IEnumerable<JniRuntime> GetRegisteredRuntimes ()
@@ -115,10 +106,7 @@ public static JniRuntime GetRegisteredRuntime (IntPtr invocationPointer)
115106

116107
internal static int GetCreatedJavaVMs (IntPtr[] handles, int bufLen, out int nVMs)
117108
{
118-
if (JvmDllLoaded)
119-
return NativeMethods.java_interop_jvm_list (handles, bufLen, out nVMs);
120-
121-
return NativeMethods.JNI_GetCreatedJavaVMs (handles, bufLen, out nVMs);
109+
return NativeMethods.java_interop_jvm_list (handles, bufLen, out nVMs);
122110
}
123111

124112
public static IEnumerable<IntPtr> GetAvailableInvocationPointers ()
@@ -134,16 +122,6 @@ public static IEnumerable<IntPtr> GetAvailableInvocationPointers ()
134122
return handles;
135123
}
136124

137-
protected static bool LoadJvmDll (string path)
138-
{
139-
if (JvmDllLoaded)
140-
return true;
141-
142-
JvmDllLoaded = NativeMethods.java_interop_jvm_load (path) != 0;
143-
144-
return JvmDllLoaded;
145-
}
146-
147125
static JniRuntime current;
148126
public static JniRuntime CurrentRuntime {
149127
get {

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class JreRuntimeOptions : JniRuntime.CreationOptions {
2626

2727
internal List<string> Options = new List<string> ();
2828

29+
public string JvmLibraryPath {get; set;}
30+
2931
public JniVersion JniVersion {get; set;}
3032
public bool IgnoreUnrecognizedOptions {get; set;}
3133

@@ -73,21 +75,9 @@ public JreRuntime CreateJreVM ()
7375

7476
public class JreRuntime : JniRuntime
7577
{
76-
const string JvmLibrary = "jvm.dll";
77-
const string JavaInteropLibrary = "java-interop";
78-
79-
[DllImport (JvmLibrary)]
80-
static extern int JNI_CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
81-
82-
[DllImport (JavaInteropLibrary)]
83-
static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
84-
8578
static int CreateJavaVM (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args)
8679
{
87-
if (JvmDllLoaded)
88-
return java_interop_jvm_create (out javavm, out jnienv, ref args);
89-
90-
return JNI_CreateJavaVM (out javavm, out jnienv, ref args);
80+
return NativeMethods.java_interop_jvm_create (out javavm, out jnienv, ref args);
9181
}
9282

9383
static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
@@ -98,8 +88,12 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder)
9888
if (builder.InvocationPointer != IntPtr.Zero)
9989
return builder;
10090

101-
if (builder.JvmDllPath != null && !LoadJvmDll (builder.JvmDllPath))
102-
throw new Exception ($"Unable to load JVM library: {builder.JvmDllPath}");
91+
if (!string.IsNullOrEmpty (builder.JvmLibraryPath)) {
92+
int r = NativeMethods.java_interop_jvm_load (builder.JvmLibraryPath);
93+
if (r != 0) {
94+
throw new Exception ($"Could not load JVM path `{builder.JvmLibraryPath}` ({r})!");
95+
}
96+
}
10397

10498
var args = new JavaVMInitArgs () {
10599
version = builder.JniVersion,
@@ -160,5 +154,13 @@ protected override void Dispose (bool disposing)
160154
base.Dispose (disposing);
161155
}
162156
}
157+
158+
partial class NativeMethods {
159+
[DllImport (JavaInteropLib, CharSet=CharSet.Ansi)]
160+
internal static extern int java_interop_jvm_load (string path);
161+
162+
[DllImport (JavaInteropLib, CharSet=CharSet.Ansi)]
163+
internal static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
164+
}
163165
}
164166

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
<configuration>
2-
<dllmap dll="jvm.dll" os="@OS_NAME@" target="@JI_JVM_PATH@"/>
32
@JAVA_RUNTIME_ENVIRONMENT_DLLMAP@
43
</configuration>

src/java-interop/java-interop-jvm.c

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,56 +5,73 @@
55
#include "java-interop-logger.h"
66
#include "java-interop-util.h"
77

8-
static struct DylibJVM *jvm = NULL;
98

10-
int java_interop_jvm_load (const char *path)
9+
typedef int (*java_interop_JNI_CreateJavaVM_fptr) (JavaVM **p_vm, void **p_env, void *vm_args);
10+
typedef int (*java_interop_JNI_GetCreatedJavaVMs_fptr) (JavaVM **vmBuf, int bufLen, int *nVMs);
11+
12+
struct DylibJVM {
13+
void *dl_handle;
14+
java_interop_JNI_CreateJavaVM_fptr JNI_CreateJavaVM;
15+
java_interop_JNI_GetCreatedJavaVMs_fptr JNI_GetCreatedJavaVMs;
16+
};
17+
18+
static struct DylibJVM *jvm;
19+
20+
int
21+
java_interop_jvm_load (const char *path)
1122
{
23+
if (jvm != NULL) {
24+
return JAVA_INTEROP_JVM_FAILED_ALREADY_LOADED;
25+
}
26+
1227
jvm = calloc (1, sizeof (struct DylibJVM));
28+
if (!jvm) {
29+
return JAVA_INTEROP_JVM_FAILED_OOM;
30+
}
1331

1432
jvm->dl_handle = dlopen (path, RTLD_LAZY);
15-
16-
if (!jvm->dl_handle)
17-
return 0;
33+
if (!jvm->dl_handle) {
34+
free (jvm);
35+
jvm = NULL;
36+
return JAVA_INTEROP_JVM_FAILED_NOT_LOADED;
37+
}
1838

1939
int symbols_missing = 0;
2040

21-
#define LOAD_SYMBOL(symbol) \
22-
jvm->symbol = dlsym (jvm->dl_handle, #symbol); \
23-
if (!jvm->symbol) { \
24-
log_error (LOG_DEFAULT, "Failed to load JVM symbol: %s", #symbol); \
25-
symbols_missing = 1; \
26-
}
41+
#define LOAD_SYMBOL(symbol) do { \
42+
jvm->symbol = dlsym (jvm->dl_handle, #symbol); \
43+
if (!jvm->symbol) { \
44+
log_error (LOG_DEFAULT, "Failed to load JVM symbol: %s", #symbol); \
45+
symbols_missing = 1; \
46+
} \
47+
} while (0)
48+
49+
LOAD_SYMBOL(JNI_CreateJavaVM);
50+
LOAD_SYMBOL(JNI_GetCreatedJavaVMs);
2751

28-
LOAD_SYMBOL(JNI_CreateJavaVM)
29-
LOAD_SYMBOL(JNI_GetCreatedJavaVMs)
52+
#undef LOAD_SYMBOL
3053

3154
if (symbols_missing) {
32-
log_fatal (LOG_DEFAULT, "Failed to load some Mono symbols, aborting...");
33-
exit (FATAL_EXIT_JVM_MISSING_SYMBOLS);
55+
free (jvm);
56+
jvm = NULL;
57+
return JAVA_INTEROP_JVM_FAILED_SYMBOL_MISSING;
3458
}
3559

36-
return 1;
60+
return 0;
3761
}
3862

39-
static inline void
40-
_assert_dl_handle ()
41-
{
42-
if (!jvm || !jvm->dl_handle) {
43-
log_fatal (LOG_DEFAULT, "Missing JVM symbols!");
44-
exit (FATAL_EXIT_JVM_MISSING_SYMBOLS);
45-
}
46-
}
63+
#define ji_return_val_if_fail(expr, val) do { if (!(expr)) return (val); } while (0)
4764

4865
int java_interop_jvm_create (JavaVM **p_vm, void **p_env, void *vm_args)
4966
{
50-
_assert_dl_handle ();
67+
ji_return_val_if_fail (jvm != NULL, JAVA_INTEROP_JVM_FAILED_NOT_LOADED);
5168

5269
return (*jvm->JNI_CreateJavaVM) (p_vm, p_env, vm_args);
5370
}
5471

5572
int java_interop_jvm_list (JavaVM **vmBuf, int bufLen, int *nVMs)
5673
{
57-
_assert_dl_handle ();
74+
ji_return_val_if_fail (jvm != NULL, JAVA_INTEROP_JVM_FAILED_NOT_LOADED);
5875

5976
return (*jvm->JNI_GetCreatedJavaVMs) (vmBuf, bufLen, nVMs);
6077
}

src/java-interop/java-interop-jvm.h

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,14 @@
55

66
typedef void JavaVM;
77

8-
typedef int (*java_interop_JNI_CreateJavaVM_fptr) (JavaVM **p_vm, void **p_env, void *vm_args);
9-
typedef int (*java_interop_JNI_GetCreatedJavaVMs_fptr) (JavaVM **vmBuf, int bufLen, int *nVMs);
10-
11-
/* NOTE: structure members MUST NOT CHANGE ORDER. */
12-
struct DylibJVM {
13-
void *dl_handle;
14-
java_interop_JNI_CreateJavaVM_fptr JNI_CreateJavaVM;
15-
java_interop_JNI_GetCreatedJavaVMs_fptr JNI_GetCreatedJavaVMs;
16-
};
17-
188
JAVA_INTEROP_BEGIN_DECLS
199

10+
#define JAVA_INTEROP_JVM_FAILED (-1000)
11+
#define JAVA_INTEROP_JVM_FAILED_ALREADY_LOADED (JAVA_INTEROP_JVM_FAILED-1)
12+
#define JAVA_INTEROP_JVM_FAILED_NOT_LOADED (JAVA_INTEROP_JVM_FAILED-2)
13+
#define JAVA_INTEROP_JVM_FAILED_OOM (JAVA_INTEROP_JVM_FAILED-3)
14+
#define JAVA_INTEROP_JVM_FAILED_SYMBOL_MISSING (JAVA_INTEROP_JVM_FAILED-4)
15+
2016
MONO_API int java_interop_jvm_load (const char *path);
2117
MONO_API int java_interop_jvm_create (JavaVM **p_vm, void **p_env, void *vm_args);
2218
MONO_API int java_interop_jvm_list (JavaVM **vmBuf, int bufLen, int *nVMs);

tests/TestJVM/TestJVM.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ public class TestJVM : JreRuntime {
1414
static JreRuntimeOptions CreateBuilder (string[] jars)
1515
{
1616
var dir = Path.GetDirectoryName (typeof (TestJVM).Assembly.Location);
17-
var builder = new JreRuntimeOptions ();
17+
var builder = new JreRuntimeOptions () {
18+
JvmLibraryPath = Environment.GetEnvironmentVariable ("JI_JVM_PATH"),
19+
};
1820
if (jars != null) {
1921
foreach (var jar in jars)
2022
builder.ClassPath.Add (Path.Combine (dir, jar));

tools/jnimarshalmethod-gen/App.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@ void ProcessAssemblies (List<string> assemblies)
160160

161161
void CreateJavaVM (string jvmDllPath)
162162
{
163-
var builder = new JreRuntimeOptions { JvmDllPath = jvmDllPath };
163+
var builder = new JreRuntimeOptions {
164+
JvmLibraryPath = jvmDllPath,
165+
};
164166

165167
try {
166168
builder.CreateJreVM ();

0 commit comments

Comments
 (0)