Skip to content

Commit e127640

Browse files
committed
add dynamic JNI load/unload support
1 parent 958a759 commit e127640

File tree

8 files changed

+117
-18
lines changed

8 files changed

+117
-18
lines changed

R/jfirst.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"RgetShortArrayCont", "RgetStringArrayCont", "RidenticalRef", "RgetSimpleClassNames",
2323
"RisAssignableFrom", "RpollException", "RsetField", "RthrowException",
2424
"javaObjectCache", "initRJavaTools", "newRJavaLookupTable", "useDynamicSymbols",
25-
"RgetJVMstate",
25+
"RgetJVMstate", "RloadJVM", "RunloadJVM",
2626
# .External
2727
"RcreateObject", "RgetStringValue", "RinitJVM", "RtoString", "RcallMethod",
2828
# .C

R/jinit.R

+33
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,41 @@
99
.need.init <- function()
1010
.Call(RJava_needs_init)
1111

12+
.jloadJVM <- function(path, silent=FALSE)
13+
.Call(RloadJVM, path.expand(path), silent)
14+
15+
.junloadJVM <- function()
16+
.Call(RunloadJVM)
17+
18+
.jfindAndLoadJVM <- function() {
19+
home <- Sys.getenv("JAVA_HOME")
20+
if (!nzchar(home) || !isTRUE(dir.exists(home))) {
21+
## try java_home first if present
22+
if (file.exists("/usr/libexec/java_home")) {
23+
home <- tryCatch(system("/usr/libexec/java_home", intern=TRUE, ignore.stderr=TRUE),
24+
error=function(...) "")
25+
}
26+
## otherwise rely on java in PATH
27+
if (!nzchar(home) || !isTRUE(dir.exists(home))) {
28+
home <- tryCatch(system(paste("java", "-cp",
29+
shQuote(system.file("java", package="rJava")), "getsp", "java.home"),
30+
intern=TRUE, ignore.stderr=TRUE), error=function(...) "")
31+
}
32+
}
33+
if (!nzchar(home) || !isTRUE(dir.exists(home)))
34+
stop("Cannot find Java. Either make sure java executable is on the PATH or set the JAVA_HOME environment variable.")
35+
libjvm <- list.files(home, "^libjvm[.]", recursive=TRUE, full.names=TRUE)
36+
if (!length(libjvm))
37+
stop("Cannot find libjvm in ", home)
38+
message("Loading JVM from ", home)
39+
.jloadJVM(libjvm)
40+
}
41+
1242
## initialization
1343
.jinit <- function(classpath=NULL, parameters=getOption("java.parameters"), ..., silent=FALSE, force.init=FALSE) {
44+
st <- .jvmState()
45+
if (st$state == "not-loaded")
46+
.jfindAndLoadJVM()
1447
running.classpath <- character()
1548
if (!.need.init()) {
1649
running.classpath <- .jclassPath()

mkdist

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ echo "Copy compiled Java classes ..."
166166
# copy all complied Java classes and sources
167167
cp src/java/*.class inst/java
168168
cp src/java/*.java inst/java
169+
cp tools/getsp* inst/java
169170
# move RJavaClassLoader into boot area since it will be loaded by the system class loader
170171
mv inst/java/RJavaClassLoader* inst/java/boot
171172
# move javadoc directory

src/Rglue.c

+33-2
Original file line numberDiff line numberDiff line change
@@ -1135,8 +1135,11 @@ REPC SEXP RgetJVMstate(void) {
11351135
const char *st = "unknown";
11361136
switch (rJava_JVM_state) {
11371137
case JVM_STATE_NONE: /* could be detached */
1138-
st = (existingJVMs() > 0) ? "detached" : "none";
1139-
break;
1138+
{
1139+
int evm = existingJVMs();
1140+
st = (evm == -1) ? "not-loaded" : ((evm > 0) ? "detached" : "none");
1141+
break;
1142+
}
11401143
case JVM_STATE_CREATED: st = "created"; break;
11411144
case JVM_STATE_ATTACHED: st = "attached"; break;
11421145
case JVM_STATE_DEAD: st = "dead"; break;
@@ -1147,3 +1150,31 @@ REPC SEXP RgetJVMstate(void) {
11471150
UNPROTECT(1);
11481151
return res;
11491152
}
1153+
1154+
REPC SEXP RloadJVM(SEXP sPath, SEXP sSilent) {
1155+
const char *path;
1156+
int res, silent = Rf_asInteger(sSilent);
1157+
if (TYPEOF(sPath) != STRSXP || LENGTH(sPath) != 1)
1158+
Rf_error("Invalid path to JVM library");
1159+
/* FIXME: worry about encoding? */
1160+
path = CHAR(STRING_ELT(sPath, 0));
1161+
res = djni_load(path);
1162+
if (silent)
1163+
return Rf_ScalarInteger(res);
1164+
switch(res) {
1165+
case -1: Rf_error("JVM run-time is already loaded");
1166+
case -2: Rf_error("Cannot load JVM run-time (%s)", djni_last_error() ? djni_last_error() : "unsuccessful");
1167+
case -3: Rf_error("JVM run-time does not have required entry points");
1168+
}
1169+
return Rf_ScalarLogical(1);
1170+
}
1171+
1172+
REPC SEXP RunloadJVM(void) {
1173+
int res;
1174+
if (rJava_JVM_state == JVM_STATE_CREATED ||
1175+
rJava_JVM_state == JVM_STATE_ATTACHED) {
1176+
destroyJVM();
1177+
}
1178+
res = djni_unload();
1179+
return Rf_ScalarLogical((res < -1) ? 0 : 1);
1180+
}

src/djni.c

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ int djni_load(const char *path) {
3838
JNI_GetDefaultJavaVMInitArgs_fn = 0;
3939
JNI_CreateJavaVM_fn = 0;
4040
JNI_GetCreatedJavaVMs_fn = 0;
41+
dlclose(jni_dl);
42+
jni_dl = 0;
4143
return -3;
4244
}
4345
return 0;
@@ -54,6 +56,14 @@ int djni_unload(void) {
5456
return -1;
5557
}
5658

59+
int djni_loaded(void) {
60+
return jni_dl ? 1 : 0;
61+
}
62+
63+
const char* djni_last_error(void) {
64+
return last_error;
65+
}
66+
5767
/* The following are the "normal" JNI API calls which are routed to libjvm JNI API.
5868
If no JNI was loaded, they return -99 to distinguish it from JNI error codes. */
5969

src/djni.h

+5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@
99

1010
#include "jni.h"
1111

12+
/* returned if no run-time is loaded */
13+
#define JNI_NO_DJNI -99
14+
1215
int djni_load(const char *path);
1316
int djni_unload(void);
17+
const char* djni_last_error(void);
18+
int djni_loaded(void);
1419

1520
jint JNI_GetDefaultJavaVMInitArgs(void *args);
1621
jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);

src/init.c

+29-12
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,12 @@ static void JNICALL exit_hook(int status) {
8989
exit(status);
9090
}
9191

92+
/* -1 if no run-time is loaded, otherwise number of active VMs */
9293
int existingJVMs(void) {
9394
jsize vms = 0;
9495
JavaVM *jvms[32];
95-
return (JNI_GetCreatedJavaVMs(jvms, 32, &vms) >= 0) ? vms : 0;
96+
jint res = JNI_GetCreatedJavaVMs(jvms, 32, &vms);
97+
return (res == JNI_NO_DJNI) ? -1 : ((res >= 0) ? vms : 0);
9698
}
9799

98100
/* in reality WIN64 implies WIN32 but to make sure ... */
@@ -118,7 +120,9 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks,
118120
if(!user_classpath) user_classpath = "";
119121

120122
vm_args.version = JNI_VERSION_1_2;
121-
if(JNI_GetDefaultJavaVMInitArgs(&vm_args) != JNI_OK) {
123+
if((res = JNI_GetDefaultJavaVMInitArgs(&vm_args)) != JNI_OK) {
124+
if (res == JNI_NO_DJNI) /* DJNI has not loaded a run-time yet */
125+
error("No Java run-time is not loaded.");
122126
error("JNI 1.2 or higher is required");
123127
return -1;
124128
}
@@ -193,8 +197,11 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks,
193197
if (disableGuardPages && (res != 0 || !eenv))
194198
return -2; /* perhaps this VM does not allow disabling guard pages */
195199

196-
if (res != 0)
197-
error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %ld)", (long int) res);
200+
if (res != 0) {
201+
if (rJava_JVM_state != JVM_STATE_NONE)
202+
Rf_warning("Attempt to re-create a second JVM in a process - most known Java implementations do not support it.");
203+
Rf_error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %d)", (int) res);
204+
}
198205
if (!eenv)
199206
error("Cannot obtain JVM environment");
200207

@@ -760,21 +767,31 @@ static SEXP RinitJVM_jsw(SEXP par) {
760767
/** RinitJVM(classpath)
761768
initializes JVM with the specified class path */
762769
REP SEXP RinitJVM(SEXP par) {
763-
770+
if (!djni_loaded())
771+
Rf_error("No Java run-time is loaded.");
764772
#ifndef JVM_STACK_WORKAROUND
765-
return RinitJVM_real(par, 0);
773+
return RinitJVM_real(par, 0);
766774
#else
767-
return RinitJVM_jsw(par);
775+
return RinitJVM_jsw(par);
768776
#endif
769777
}
770778

771-
REP void doneJVM(void) {
772-
(*jvm)->DestroyJavaVM(jvm);
773-
jvm = 0;
774-
eenv = 0;
775-
rJava_JVM_state = JVM_STATE_DESTROYED;
779+
/* detaches (if only attached) or destroys (if created) JVM */
780+
HIDE void destroyJVM(void) {
781+
if (!jvm)
782+
return;
783+
if (rJava_JVM_state == JVM_STATE_CREATED ||
784+
rJava_JVM_state == JVM_STATE_ATTACHED) {
785+
(*jvm)->DetachCurrentThread(jvm);
786+
if (rJava_JVM_state == JVM_STATE_CREATED)
787+
(*jvm)->DestroyJavaVM(jvm);
788+
jvm = 0;
789+
eenv = 0;
790+
rJava_JVM_state = (rJava_JVM_state == JVM_STATE_CREATED) ? JVM_STATE_DESTROYED : JVM_STATE_DETACHED;
791+
}
776792
}
777793

794+
778795
/**
779796
* Initializes the cached values of classes and methods used internally
780797
* These classes and methods are the ones that are in rJava (RJavaTools, ...)

src/rJava.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#ifndef __RJAVA_H__
22
#define __RJAVA_H__
33

4-
#define RJAVA_VER 0x01000b /* rJava v1.0-11 */
4+
#define RJAVA_VER 0x010010 /* rJava v1.1-0 */
55

66
/* important changes between versions:
77
3.0 - adds compiler
@@ -19,7 +19,7 @@
1919
0.2 - uses S4 classes
2020
0.1 - first public release */
2121

22-
#include <jni.h>
22+
#include "djni.h"
2323
#include <R.h>
2424
#include <Rinternals.h>
2525
#include <Rversion.h>
@@ -140,7 +140,8 @@ extern int rJava_initialized;
140140
#define JVM_STATE_CREATED 1 /* JVM was created by us */
141141
#define JVM_STATE_ATTACHED 2 /* we attached to another JVM */
142142
#define JVM_STATE_DEAD 4 /* set when Java exit handler was called */
143-
#define JVM_STATE_DESTROYED 8 /* JVM was destroyed */
143+
#define JVM_STATE_DETACHED 8 /* detached from existing JVM */
144+
#define JVM_STATE_DESTROYED 9 /* JVM was destroyed */
144145

145146
extern int rJava_JVM_state;
146147

@@ -168,6 +169,7 @@ extern jmethodID mid_RJavaImport_lookup ;
168169
extern jmethodID mid_RJavaImport_exists ;
169170

170171
HIDE void init_rJava(void);
172+
HIDE void destroyJVM(void);
171173

172174
/* in otables.c */
173175
// turn this for debugging in otables.c

0 commit comments

Comments
 (0)