diff --git a/NEWS b/NEWS index 25d3751e..6d6bc031 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,58 @@ NEWS/ChangeLog for rJava -------------------------- +1.0-19 (not yet released) + o The JVM run-time is no longer linked into rJava or loaded when + the namespace loads. The first .jinit() selects one: a JVM + already mapped in the process, then java.home (Windows) or + JAVA_HOME, the JavaSoft registry (Windows), + /usr/libexec/java_home (macOS), R CMD config JAVA_HOME (Linux), + and java on PATH. Linux thus retains the javareconf JDK as its + default when JAVA_HOME is unset. Until a + library is loaded, a failed .jinit() can be retried after + correcting the setting. .jloadJVM() selects a library by path; + .jvmState() reports "not-loaded" before any selection. + + o A selected JVM library stays selected for the life of the R + process: .junloadJVM() fails once one is loaded, and another + run-time requires a new R process. A JVM library already mapped + into the process can be selected explicitly only by a path naming + the same file; a different or unverifiable file is refused before it is + loaded. Device mismatches on btrfs and overlayfs are not evidence + of a different file: matching inodes also require a loader + reference to the same image, without loading another library. + + o If native code in the process has already mapped one JVM library + (e.g. JavaGD), .jinit() adopts it even before a VM exists and warns + if Java discovery selects a different path. If a Java application + embedding R through JRI has created a VM, .jinit() attaches to it + whatever JAVA_HOME names. + + o Changed behavior: the Java home is now chosen when Java is + first used, not when rJava loads, and .onLoad() no longer changes + PATH (Windows) or DYLD_LIBRARY_PATH (macOS). On Linux with musl + (e.g. Alpine), only the run-time whose server directory was on + LD_LIBRARY_PATH when R started can be used; selecting another + is refused, and the refused library stays mapped, so a new R + process is needed (see ?.jinit). + + o JVM loading is now always dynamic. --enable-dynload defaults to + yes; --disable-dynload is rejected instead of silently producing + a dynamic build under --as-needed. RJAVA_FORCE_LOAD is no longer + used, since namespace loading never requires a JVM. + + + o On Windows, retain MSVC runtime DLLs for native libraries Java + loads after startup or host attachment. Already loaded runtimes + keep precedence. For each runtime the JDK bundles, select the + newer of System32 and the bundled copy before loading jvm.dll, + preferring System32 on ties or unknown versions. This preserves + newer system runtimes for other native packages while supporting + missing or incomplete system VC++ installations. Retention lasts + for the process lifetime without changing PATH or retaining a + search directory. Windows JRI build policy is unchanged. + + 1.0-18 2026-04-08 o JRI: fix regression in 1.0-16 breaking older versions of R. diff --git a/R/java-runtime.R b/R/java-runtime.R new file mode 100644 index 00000000..0f7f5fc7 --- /dev/null +++ b/R/java-runtime.R @@ -0,0 +1,79 @@ +## Deferred JVM discovery. This runs at the first .jinit(), +## never at namespace load. The Java home is the first of: +## 1. getOption("java.home") (Windows only, as before) or JAVA_HOME; a value +## that is not an existing directory is ignored with a warning, as rJava +## did on Windows and macOS before deferred loading +## 2. on Windows, the CurrentVersion of the JavaSoft registry keys +## 3. on macOS, /usr/libexec/java_home +## 4. on Linux, R CMD config JAVA_HOME (the javareconf setting) +## 5. the java.home property reported by `java` on PATH +## An existing Java home without a JVM library is an error, not a fallback. +.find.java.runtime <- function() { + windows <- .Platform$OS.type == "windows" + javahome <- if (windows && !is.null(getOption("java.home"))) getOption("java.home") else Sys.getenv("JAVA_HOME") + if (!is.character(javahome) || length(javahome) != 1L || is.na(javahome)) javahome <- "" + if (nzchar(javahome) && !dir.exists(javahome)) { + if (windows) message("java.home option: ", getOption("java.home")) + message("JAVA_HOME environment variable: ", Sys.getenv("JAVA_HOME")) + warning("Java home setting is INVALID, it will be ignored.\nPlease do NOT set it unless you want to override system settings.") + javahome <- "" + } + if (!nzchar(javahome) && windows) javahome <- .find.java.registry() + if (!nzchar(javahome) && file.exists("/usr/libexec/java_home")) + javahome <- .java.output("/usr/libexec/java_home") + if (!nzchar(javahome) && identical(Sys.info()[["sysname"]], "Linux")) { + configured <- .java.output(file.path(R.home("bin"), "R"), c("CMD", "config", "JAVA_HOME")) + if (nzchar(configured) && dir.exists(configured)) javahome <- configured + } + if (!nzchar(javahome)) + javahome <- .java.output("java", c("-cp", shQuote(system.file("java", package="rJava")), "getsp", "java.home")) + if (nzchar(javahome) && dir.exists(javahome)) javahome else "" +} + +## first line of a command's standard output, or "" if it fails +.java.output <- function(command, args=character()) { + out <- tryCatch(suppressWarnings(system2(command, args, stdout=TRUE, stderr=FALSE)), + error=function(...) character()) + if (!length(out) || !is.null(attr(out, "status")) || is.na(out[1L])) "" else out[1L] +} + +.find.java.registry <- function() { + for (root in c("HLM", "HCU")) { + for (key in c( + "Software\\JavaSoft\\JRE", + "Software\\JavaSoft\\JDK", + "Software\\JavaSoft\\Java Runtime Environment", + "Software\\JavaSoft\\Java Development Kit" + )) { + hive <- try(utils::readRegistry(key, root, 2), silent = TRUE) + if (!inherits(hive, "try-error") && length(hive$CurrentVersion)) { + home <- hive[[hive$CurrentVersion]]$JavaHome + if (is.character(home) && length(home) == 1L && nzchar(home)) return(home) + } + } + } + "" +} + +## JVM libraries in a Java home, server VM first. Known layouts are checked +## before a recursive search: lib/server (JDK 9+ on Linux and macOS), +## jre/lib//server (JDK 8 on Linux), jre/lib/server (JDK 8 on macOS), +## bin/server and jre/bin/server (Windows). +.find.jvm.library <- function(home) { + if (.Platform$OS.type == "windows") { + lib <- "jvm.dll" + base <- file.path(home, c("bin", "jre/bin")) + } else { + lib <- if (length(grep("^darwin", R.version$os))) "libjvm.dylib" else "libjvm.so" + base <- file.path(home, c("lib", "jre/lib")) + base <- c(base, list.dirs(base, recursive=FALSE)) + } + found <- c(file.path(base, "server", lib), file.path(base, "client", lib)) + found <- found[file.exists(found)] + if (!length(found)) { + found <- list.files(home, paste0("^", gsub(".", "[.]", lib, fixed=TRUE), "$"), + recursive=TRUE, full.names=TRUE) + found <- c(found[grepl("/server/", found, fixed=TRUE)], found[!grepl("/server/", found, fixed=TRUE)]) + } + found +} diff --git a/R/jfirst.R b/R/jfirst.R index 8f401425..e757dae0 100644 --- a/R/jfirst.R +++ b/R/jfirst.R @@ -22,7 +22,7 @@ "RgetShortArrayCont", "RgetStringArrayCont", "RidenticalRef", "RgetSimpleClassNames", "RisAssignableFrom", "RpollException", "RsetField", "RthrowException", "javaObjectCache", "initRJavaTools", "newRJavaLookupTable", "useDynamicSymbols", - "RgetJVMstate", + "RgetJVMstate", "RloadJVM", "RunloadJVM", "RgetMappedJVM", "RattachJVM", # .External "RcreateObject", "RgetStringValue", "RinitJVM", "RtoString", "RcallMethod", # .C diff --git a/R/jinit.R b/R/jinit.R index 736fc8e3..274c1b22 100644 --- a/R/jinit.R +++ b/R/jinit.R @@ -9,8 +9,44 @@ .need.init <- function() .Call(RJava_needs_init) +.jloadJVM <- function(path, silent=FALSE) + .Call(RloadJVM, path.expand(path), silent) + +.junloadJVM <- function() + .Call(RunloadJVM) + +.jfindAndLoadJVM <- function() { + ## A mapped JVM is the only runtime this process can use, even before + ## a VM exists (e.g. JavaGD links libjvm). Select the held image itself; + ## explicit .jloadJVM(path) still requires a path naming that same file. + mapped <- .Call(RgetMappedJVM) + if (!is.null(mapped) && isTRUE(mapped$vms > 0L)) + return(.Call(RattachJVM)) + home <- .find.java.runtime() + if (!is.null(mapped)) { + selected <- .Call(RattachJVM) + discovered <- if (nzchar(home)) .find.jvm.library(home) else character() + if (length(discovered) && + !identical(normalizePath(discovered[1], winslash="/"), + normalizePath(mapped$path, winslash="/", mustWork=FALSE))) + warning("Using the JVM library already mapped into this process: ", mapped$path, + "; Java discovery selected ", discovered[1], + ". To select that runtime, start a new R process before loading packages that use Java.", + call.=FALSE) + return(selected) + } + if (!nzchar(home)) + stop("Cannot find Java. Set JAVA_HOME to an existing Java home before initializing the JVM.") + libjvm <- .find.jvm.library(home) + if (!length(libjvm)) stop("Cannot find JVM library in ", home) + .jloadJVM(normalizePath(libjvm[1], winslash="/", mustWork=TRUE)) +} + ## initialization .jinit <- function(classpath=NULL, parameters=getOption("java.parameters", NA), ..., silent=FALSE, force.init=FALSE) { + st <- .jvmState() + if (st$state == "not-loaded") + .jfindAndLoadJVM() running.classpath <- character() if (!.need.init()) { running.classpath <- .jclassPath() diff --git a/R/windows/FirstLib.R b/R/windows/FirstLib.R index 2c0da98b..9a088664 100755 --- a/R/windows/FirstLib.R +++ b/R/windows/FirstLib.R @@ -1,65 +1,4 @@ -.msg <- message - -.onLoad <- -function(libname, pkgname) { - OPATH <- Sys.getenv("PATH") - javahome <- if (!is.null(getOption("java.home"))) getOption("java.home") else Sys.getenv("JAVA_HOME") - if (nzchar(javahome) && !dir.exists(javahome)) { - .msg("java.home option: ", getOption("java.home")) - .msg("JAVA_HOME environment variable: ", Sys.getenv("JAVA_HOME")) - warning("Java home setting is INVALID, it will be ignored.\nPlease do NOT set it unless you want to override system settings.") - javahome <- "" - } - if(!nzchar(javahome)) { ## JAVA_HOME was not set explicitly - find.java <- function() { - for (root in c("HLM", "HCU")) - for(key in c( - "Software\\JavaSoft\\JRE", - "Software\\JavaSoft\\JDK", - "Software\\JavaSoft\\Java Runtime Environment", - "Software\\JavaSoft\\Java Development Kit" - )) { - hive <- try(utils::readRegistry(key, root, 2), silent=TRUE) - if (!inherits(hive, "try-error")) return(hive) - } - hive - } - hive <- find.java() - if (inherits(hive, "try-error")) - stop("JAVA_HOME cannot be determined from the Registry") - if (!length(hive$CurrentVersion)) - stop("No CurrentVersion entry in Software/JavaSoft registry! Try re-installing Java and make sure R and Java have matching architectures.") - this <- hive[[hive$CurrentVersion]] - javahome <- this$JavaHome - paths <- if (is.character(this$RuntimeLib)) dirname(this$RuntimeLib) else character() # wrong on 64-bit - } else paths <- character() - if(is.null(javahome) || !length(javahome) || !nchar(javahome)) - stop("JAVA_HOME is not set and could not be determined from the registry") - #else cat("using JAVA_HOME =", javahome, "\n") - - ## we need to add Java-related library paths to PATH - curPath <- OPATH - paths <- c(paths, - file.path(javahome, "bin", "client"), # 32-bit - file.path(javahome, "bin", "server"), # 64-bit - file.path(javahome, "bin"), # base (now needed for MSVCRT in recent Sun Java) - file.path(javahome, "jre", "bin", "server"), # old 64-bit (or manual JAVA_HOME setting to JDK) - file.path(javahome, "jre", "bin", "client")) # old 32-bit (or manual JAVA_HOME setting to JDK) - cpc <- strsplit(curPath, ";", fixed=TRUE)[[1]] ## split it up so we can check presence/absence of a path - - ## add paths only if they are not in already and they exist - for (path in unique(paths)) - if (!path %in% cpc && file.exists(path)) curPath <- paste(path, curPath, sep=";") - - ## set PATH only if it's not correct already (cannot use identical/isTRUE because of PATH name attribute) - if (curPath != OPATH) { - Sys.setenv(PATH = curPath) - # check the resulting PATH - if they don't match then Windows has truncated it - if (curPath != Sys.getenv("PATH")) - warning("*** WARNING: your Windows system seems to suffer from truncated PATH bug which will likely prevent rJava from loading.\n Either reduce your PATH or read http://support.microsoft.com/kb/906469 on how to fix your system.") - } - +.onLoad <- function(libname, pkgname) { library.dynam("rJava", pkgname, libname) - Sys.setenv(PATH = OPATH) .jfirst(libname, pkgname) } diff --git a/R/zzz.R.in b/R/zzz.R.in index 93d06438..284afde5 100644 --- a/R/zzz.R.in +++ b/R/zzz.R.in @@ -1,52 +1,5 @@ +# this should no longer be needed as an autoconf-edited file .onLoad <- function(libname, pkgname) { - ## we can skip all the detection logic if forced - assuming the user has loaded JVM already - if (!nzchar(Sys.getenv("RJAVA_FORCE_LOAD"))) { - ## On OS X with Oracle Java we may need to work around Oracle bug: - ## https://bugs.openjdk.java.net/browse/JDK-7131356 - use.dynload <- FALSE - @USE_DYNLOAD_TRUE@use.dynload <- TRUE - if (length(grep("^darwin", R.version$os))) { - jh <- Sys.getenv("JAVA_HOME") - if (nzchar(jh) && !dir.exists(jh)) { - warning("JAVA_HOME is set incorrectly! Ingoring.") - jh <- "" - } - if (!nzchar(jh) && file.exists("/usr/libexec/java_home")) - jh <- system("/usr/libexec/java_home", intern=TRUE)[1L] - if (!nzchar(jh)) { - if (use.dynload) - stop("Cannot find Java. Install Java run-time and use R CMD javareconf.\nAs a last resort you can set JAVA_HOME appropriately.") - warning("Cannot find Java. Install Java run-time and use R CMD javareconf.\nAs a last resort you can set JAVA_HOME if you know the correct value.") - } - if (file.exists(file.path(jh, "jre/lib"))) jh <- file.path(jh, "jre") - dlp <- Sys.getenv("DYLD_LIBRARY_PATH") - if (nzchar(dlp)) dlp <- paste0(":", dlp) - ## pre-load JLI due to Oracle bug - if (file.exists(jli <- file.path(jh, "lib/jli/libjli.dylib"))) - dyn.load(jli, FALSE) - ## this is mandatory - we need the JVM - if (file.exists(jvm <- file.path(jh, "lib/server/libjvm.dylib"))) { - dyn.load(jvm, FALSE) - Sys.setenv(DYLD_LIBRARY_PATH=paste0(file.path(jh, "lib/server"), dlp)) - } else { - warning("Cannot find JVM library '", jvm, "'\nInstall Java and/or check JAVA_HOME (if in doubt, do NOT set it, it will be detected)") - if (use.dynload) stop("JVM could not be found") - } - } else if (use.dynload) { - jh <- Sys.getenv("JAVA_HOME") - if (nzchar(jh) && !dir.exists(jh)) { - warning("JAVA_HOME is set incorrectly! Ingoring.") - jh <- "" - } - if (!nzchar(jh)) - stop("JAVA_HOME is requied for dynamically loaded JVM, but is not set.\nRe-run R CMD javareconf or set JAVA_HOME correctly.") - if (file.exists(file.path(jh, "jre/lib"))) jh <- file.path(jh, "jre") - if (!file.exists(jvm <- file.path(jh, "lib/server/libjvm.so"))) - stop("Cannot find ", jvm,", re-configure R or use valid JAVA_HOME") - dyn.load(jvm, FALSE) - } - } ## RJAVA_FORCE_LOAD - library.dynam("rJava", pkgname, libname) # pass on to the system-independent part .jfirst(libname, pkgname) diff --git a/configure b/configure index 12aab97f..1405ec3b 100755 --- a/configure +++ b/configure @@ -1359,7 +1359,7 @@ Optional Features: programs to embed R. [auto] --enable-headless enable initialization in headless mode. [auto] --enable-Xrs use -Xrs in Java initialization. [auto] - --enable-dynload load JVM dynamically (without linking). [auto] + --enable-dynload load JVM dynamically (required). [yes] --enable-debug enable debug flags and output. [no] --enable-mem-profile enable memory profiling. [debug] --enable-callbacks enable the support for callbacks from Java into R. @@ -2458,10 +2458,15 @@ if test ${enable_dynload+y} then : enableval=$enable_dynload; want_dynload="${enableval}" else case e in #( - e) want_dynload=auto ;; + e) want_dynload=yes ;; esac fi +if test "x${want_dynload}" = xno +then : + as_fn_error $? "--disable-dynload is no longer supported: JVM loading is deferred to .jinit()." "$LINENO" 5 +fi + ## enable debug flags # Check whether --enable-debug was given. @@ -4009,17 +4014,7 @@ printf "%s\n" "#define HAVE_XRS 1" >>confdefs.h fi -use_dynload="$want_dynload" -if test x"$use_dynload" = xauto; then - if test "x$OSNAME" = xDarwin; then - use_dynload=yes - else - use_dynload=no - fi -fi -if test x"$use_dynload" != xyes; then - use_dynload=no -fi +use_dynload=yes { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether JVM will be loaded dynamically" >&5 printf %s "checking whether JVM will be loaded dynamically... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${use_dynload}" >&5 @@ -4032,100 +4027,6 @@ else USE_DYNLOAD_FALSE= fi - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether JNI programs can be compiled" >&5 -printf %s "checking whether JNI programs can be compiled... " >&6; } -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -#include -int main(void) { - jobject o; - JNI_CreateJavaVM(0, 0, 0); - return 0; -} - -_ACEOF -if ac_fn_c_try_link "$LINENO" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } -else case e in #( - e) as_fn_error $? "Cannot compile a simple JNI program. See config.log for details. - -Make sure you have Java Development Kit installed and correctly registered in R. -If in doubt, re-run \"R CMD javareconf\" as root. -" "$LINENO" 5 ;; -esac -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether JNI programs run" >&5 -printf %s "checking whether JNI programs run... " >&6; } -if test "$cross_compiling" = yes -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: don't know (cross-compiling)" >&5 -printf "%s\n" "don't know (cross-compiling)" >&6; } -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -#include -int main(void) { - jsize n; - JNI_GetCreatedJavaVMs(NULL, 0, &n); - return 0; -} - -_ACEOF -if ac_fn_c_try_run "$LINENO" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -printf "%s\n" "yes" >&6; } -else case e in #( - e) as_fn_error $? "Unable to run a simple JNI program. Make sure you have configured R with Java support (see R documentation) and check config.log for failure reason." "$LINENO" 5 ;; -esac -fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac -fi - - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking JNI data types" >&5 -printf %s "checking JNI data types... " >&6; } -if test "$cross_compiling" = yes -then : - { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 -printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} -as_fn_error $? "cannot run test program while cross compiling -See 'config.log' for more details" "$LINENO" 5; } -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -#include -int main(void) { - return (sizeof(int)==sizeof(jint) && sizeof(long)==sizeof(long) && sizeof(jbyte)==sizeof(char) && sizeof(jshort)==sizeof(short) && sizeof(jfloat)==sizeof(float) && sizeof(jdouble)==sizeof(double))?0:1; -} - -_ACEOF -if ac_fn_c_try_run "$LINENO" -then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ok" >&5 -printf "%s\n" "ok" >&6; } -else case e in #( - e) as_fn_error $? "One or more JNI types differ from the corresponding native type. You may need to use non-standard compiler flags or a different compiler in order to fix this." "$LINENO" 5 ;; -esac -fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext ;; -esac -fi - - - if test "${want_jri}" = auto; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether JRI should be compiled (autodetect)" >&5 printf %s "checking whether JRI should be compiled (autodetect)... " >&6; } diff --git a/configure.ac b/configure.ac index afcb9beb..fef8f3c8 100644 --- a/configure.ac +++ b/configure.ac @@ -64,9 +64,11 @@ AC_ARG_ENABLE([Xrs], ## enable dynloaded JVM AC_ARG_ENABLE([dynload], -[AS_HELP_STRING([--enable-dynload],[load JVM dynamically (without linking). @<:@auto@:>@])], +[AS_HELP_STRING([--enable-dynload],[load JVM dynamically (required). @<:@yes@:>@])], [want_dynload="${enableval}"], -[want_dynload=auto]) +[want_dynload=yes]) +AS_IF([test "x${want_dynload}" = xno], + [AC_MSG_ERROR([--disable-dynload is no longer supported: JVM loading is deferred to .jinit().])]) ## enable debug flags AC_ARG_ENABLE([debug], @@ -238,58 +240,11 @@ if test x"$has_xrs" = xyes; then AC_DEFINE(HAVE_XRS, 1, [Set if the Java parameter -Xrs is supported]) fi -use_dynload="$want_dynload" -if test x"$use_dynload" = xauto; then - if test "x$OSNAME" = xDarwin; then - use_dynload=yes - else - use_dynload=no - fi -fi -if test x"$use_dynload" != xyes; then - use_dynload=no -fi +use_dynload=yes AC_MSG_CHECKING([whether JVM will be loaded dynamically]) AC_MSG_RESULT(${use_dynload}) AM_CONDITIONAL(USE_DYNLOAD, [test "x${use_dynload}" = xyes]) -AC_MSG_CHECKING([whether JNI programs can be compiled]) -AC_LINK_IFELSE([AC_LANG_SOURCE([[ -#include -int main(void) { - jobject o; - JNI_CreateJavaVM(0, 0, 0); - return 0; -} - ]])],[AC_MSG_RESULT(yes)], - [AC_MSG_ERROR([Cannot compile a simple JNI program. See config.log for details. - -Make sure you have Java Development Kit installed and correctly registered in R. -If in doubt, re-run "R CMD javareconf" as root. -])]) - -AC_MSG_CHECKING([whether JNI programs run]) -AC_RUN_IFELSE([AC_LANG_SOURCE([ -#include -int main(void) { - jsize n; - JNI_GetCreatedJavaVMs(NULL, 0, &n); - return 0; -} - ])], - [AC_MSG_RESULT([yes])], - [AC_MSG_ERROR([Unable to run a simple JNI program. Make sure you have configured R with Java support (see R documentation) and check config.log for failure reason.])], - [AC_MSG_RESULT([don't know (cross-compiling)])]) - -AC_MSG_CHECKING([JNI data types]) -AC_RUN_IFELSE([AC_LANG_SOURCE([[ -#include -int main(void) { - return (sizeof(int)==sizeof(jint) && sizeof(long)==sizeof(long) && sizeof(jbyte)==sizeof(char) && sizeof(jshort)==sizeof(short) && sizeof(jfloat)==sizeof(float) && sizeof(jdouble)==sizeof(double))?0:1; -} - ]])],[AC_MSG_RESULT([ok])],[AC_MSG_ERROR([One or more JNI types differ from the corresponding native type. You may need to use non-standard compiler flags or a different compiler in order to fix this.])],[]) - - if test "${want_jri}" = auto; then AC_MSG_CHECKING([whether JRI should be compiled (autodetect)]) AC_MSG_RESULT([${has_R_shlib}]) diff --git a/man/jinit.Rd b/man/jinit.Rd index 4f9d88d5..990927c3 100644 --- a/man/jinit.Rd +++ b/man/jinit.Rd @@ -1,6 +1,9 @@ \name{jinit} \alias{.jinit} \alias{.jvmState} +\alias{.jloadJVM} +\alias{.junloadJVM} +\alias{.jfindAndLoadJVM} \title{ Initialize Java VM } @@ -14,6 +17,9 @@ .jinit(classpath = NULL, parameters = getOption("java.parameters", NA), ..., silent = FALSE, force.init = FALSE) .jvmState() +.jloadJVM(path, silent = FALSE) +.junloadJVM() +.jfindAndLoadJVM() } \arguments{ \item{classpath}{Any additional classes to include in the Java class @@ -30,7 +36,10 @@ currently. \code{NULL} is equivalent to \code{character()}. } \item{...}{Other optional Java initialization parameters (implementation-dependent).} - \item{silent}{If set to \code{TRUE} no warnings are issued.} + \item{path}{Path to a JVM run-time library for explicit selection.} + \item{silent}{If set to \code{TRUE} no initialization warnings are issued. + For \code{.jloadJVM}, return the native status code instead of signaling + a load error: zero means success and negative values mean failure.} \item{force.init}{If set to \code{TRUE} JVM is re-initialized even if it is already running.} } @@ -47,7 +56,8 @@ runing JVM, \code{FALSE} otherwise.} \item{state}{string representing the current state of the JVM. One of the following values: - \code{"none"} if there is no JVM, \code{"created"} if the current + \code{"not-loaded"} if no JVM run-time library has been selected yet, + \code{"none"} if a run-time library is loaded, but there is no JVM, \code{"created"} if the current JVM has been created by rJava, \code{"attached"} if rJava attached into an existing JVM (typically when R is embedded into a running JVM via JRI), \code{"detached"} if there is a JVM (such as embedded @@ -93,6 +103,66 @@ than \code{.jinit} and \code{.jvmState} is undefined unless \code{.jvmState()$initialized} is \code{TRUE}. + Loading the rJava namespace does not select or load a JVM run-time. + The first call to \code{.jinit} selects one, in this order: + the only JVM library already mapped in the process (e.g. by JavaGD), + even if no VM exists yet; if a VM exists (e.g. when R is embedded in + Java via JRI), rJava attaches regardless of other settings; + otherwise the \code{java.home} option (Windows only) or the + \code{JAVA_HOME} environment variable; then, on Windows, the + \code{CurrentVersion} of the JavaSoft registry keys; on macOS, + \code{/usr/libexec/java_home}; on Linux, the existing Java home reported + by \code{R CMD config JAVA_HOME} (set by \code{R CMD javareconf}); + and finally the \code{java} executable + found on \code{PATH}. A Java home setting that is not an existing + directory is ignored with a warning. If no run-time can be found or + loaded, \code{.jinit} fails and can be called again after correcting + the setting. Once a run-time library is loaded, it remains selected + for the life of the R process: a different JVM requires a new R + process. On Linux with musl (e.g. Alpine), the JDK's own libraries + find the JVM library by name only through \code{LD_LIBRARY_PATH} as + set when R started, so only the run-time whose server directory was + on it can be used: start R with \code{JAVA_HOME}, or + \code{R_JAVA_LD_LIBRARY_PATH}, naming the run-time. Selecting another + is refused before a JVM is created, but the refused library stays + mapped, so retrying with any run-time then requires a new R process. + If a JVM library is already mapped into the process, a JVM library + path, such as one given to \code{.jloadJVM}, selects it only if it + names that same file, also when a JVM was created in it. Automatic + selection by \code{.jinit} adopts the mapped image without loading another + JVM and, after successful selection, warns if Java discovery selects a + different path. Loader lookups may inspect the recorded path when acquiring + a reference to the mapped image. + To select another JVM, start a new R process before loading packages + that map a JVM library. + + \code{.jloadJVM} selects a library without starting a VM and returns + \code{TRUE} on success, or a status code when \code{silent = TRUE}. + \code{.jfindAndLoadJVM} performs the automatic selection used by + \code{.jinit}. \code{.junloadJVM} returns \code{TRUE} if no library has + been selected and otherwise signals an error: unloading does not make + replacing a JVM in the same process safe. + + On Linux, filesystem device numbers from \code{stat} and + \code{/proc/self/maps} can differ (e.g. btrfs subvolumes and overlayfs). + Matching inodes then also require the dynamic loader to confirm the + already mapped image without loading anything. If identity cannot be + verified, explicit selection fails; attaching to an existing VM remains + possible without a pathname comparison. + + On Windows, rJava retains MSVC runtime DLLs for later native Java + loads. Already loaded runtimes keep precedence. Otherwise, for each + runtime the JDK bundles, rJava selects the newer of System32 and the + bundled copy before loading \code{jvm.dll}, preferring System32 on a + tie or unknown version. Unbundled, unloaded runtimes are left to the + Windows loader. Retained copies stay loaded for the process lifetime; + this also determines which copies later native packages use. No + persistent change to \code{PATH} or DLL search directories is made. + A JDK that omits a required runtime may still need a suitable system + redistributable. A runtime already loaded by a host is not replaced, + even if a newer copy exists. + + NOTE: \code{.jinit()} can be called implicitly by other functions such as \code{\link{.jpackage}} or \code{\link{J}}. } diff --git a/mkdist b/mkdist index 2574b312..503a1bee 100644 --- a/mkdist +++ b/mkdist @@ -166,6 +166,7 @@ echo "Copy compiled Java classes ..." # copy all complied Java classes and sources cp src/java/*.class inst/java cp src/java/*.java inst/java +cp tools/getsp* inst/java # move RJavaClassLoader into boot area since it will be loaded by the system class loader mv inst/java/RJavaClassLoader* inst/java/boot # move javadoc directory diff --git a/src/Makevars.in b/src/Makevars.in index f59c6227..bcc42e4b 100644 --- a/src/Makevars.in +++ b/src/Makevars.in @@ -1,6 +1,5 @@ # we need to add JNI specific stuff here PKG_CPPFLAGS=-I. @JAVA_CPPFLAGS@ -@USE_DYNLOAD_FALSE@PKG_LIBS=@JAVA_LIBS@ JAVA_HOME=@JAVA_HOME@ # make SHLIB believe that we know better what the objects are #OBJECTS=Rglue.o callJNI.o initJNI.o rJava.o jri.o jri_glue.o diff --git a/src/Makevars.win b/src/Makevars.win index 0b28d1bf..4c6cdb4f 100644 --- a/src/Makevars.win +++ b/src/Makevars.win @@ -23,7 +23,7 @@ $(warning JAVA_HOME is $(JAVA_HOME)) # debugging flags like -DRJ_DEBUG or -DRJ_PROFILE to PKG_CFLAGS JAVA_INCLUDES=$(JAVA_HOME)/include $(JAVA_HOME)/include/win32 PKG_CPPFLAGS = -D_R_ -DWin32 -Ijvm-w32 -I$(RHOME)/src/include $(JAVA_INCLUDES:%=-I%) -PKG_LIBS = -Ljvm-w32 -ljvm.dll +PKG_LIBS = -Ljvm-w32 -lversion $(warning PKG_CPPFLAGS are $(PKG_CPPFLAGS)) diff --git a/src/Rglue.c b/src/Rglue.c index c76da2fa..df73eb61 100644 --- a/src/Rglue.c +++ b/src/Rglue.c @@ -1121,8 +1121,11 @@ REPC SEXP RgetJVMstate(void) { const char *st = "unknown"; switch (rJava_JVM_state) { case JVM_STATE_NONE: /* could be detached */ - st = (existingJVMs() > 0) ? "detached" : "none"; - break; + { + int evm = existingJVMs(); + st = (evm == -1) ? "not-loaded" : ((evm > 0) ? "detached" : "none"); + break; + } case JVM_STATE_CREATED: st = "created"; break; case JVM_STATE_ATTACHED: st = "attached"; break; case JVM_STATE_DEAD: st = "dead"; break; @@ -1133,3 +1136,74 @@ REPC SEXP RgetJVMstate(void) { UNPROTECT(1); return res; } + +static void load_error(int res) { + switch(res) { + case -1: Rf_error("JVM run-time is already loaded"); + case -2: Rf_error("Cannot load JVM run-time (%s)", djni_last_error() ? djni_last_error() : "unsuccessful"); + case -3: Rf_error("JVM run-time does not have required entry points"); + case -4: Rf_error("%s", djni_last_error()); + } +} + +REPC SEXP RloadJVM(SEXP sPath, SEXP sSilent) { + const char *path; + int res, silent = Rf_asInteger(sSilent); + if (TYPEOF(sPath) != STRSXP || LENGTH(sPath) != 1) + Rf_error("Invalid path to JVM library"); +#ifdef _WIN32 + path = Rf_translateCharUTF8(STRING_ELT(sPath, 0)); /* djni converts to UTF-16 */ +#else + path = Rf_translateChar(STRING_ELT(sPath, 0)); /* dlopen uses native bytes */ +#endif + if (!*path) + Rf_error("Invalid path to JVM library"); + res = djni_load(path); + if (silent) + return Rf_ScalarInteger(res); + load_error(res); + return Rf_ScalarLogical(1); +} + +/* Selects the only mapped JVM library, e.g. from JavaGD or a Java host, + without loading another JVM. Loader lookups may inspect its recorded path + to acquire a reference. The VM may not have been started yet. */ +REPC SEXP RattachJVM(void) { + load_error(djni_attach()); + return Rf_ScalarLogical(1); +} + +REPC SEXP RunloadJVM(void) { + /* dlclose/FreeLibrary success does not prove that the JVM and all its + dependencies were unmapped. In particular dyld retains TLS images. */ + if (djni_loaded()) + Rf_error("Cannot safely unload a selected JVM library; use a new R process."); + return Rf_ScalarLogical(1); +} + +/* list(path, vms) for a JVM library already mapped into the process, e.g. by + a Java application embedding R, or NULL. Nothing is loaded or initialized. */ +REPC SEXP RgetMappedJVM(void) { + int vms; + const char *path = djni_mapped(&vms); + SEXP res, names; + if (!path) { + if (vms == -2) + Rf_error("Cannot determine whether a JVM library is mapped: %s", + djni_last_error() ? djni_last_error() : "unknown error"); + return R_NilValue; + } + res = PROTECT(Rf_allocVector(VECSXP, 2)); + names = PROTECT(Rf_allocVector(STRSXP, 2)); +#ifdef _WIN32 + SET_VECTOR_ELT(res, 0, Rf_ScalarString(Rf_mkCharCE(path, CE_UTF8))); +#else + SET_VECTOR_ELT(res, 0, Rf_mkString(path)); +#endif + SET_VECTOR_ELT(res, 1, Rf_ScalarInteger(vms)); + SET_STRING_ELT(names, 0, Rf_mkChar("path")); + SET_STRING_ELT(names, 1, Rf_mkChar("vms")); + Rf_setAttrib(res, R_NamesSymbol, names); + UNPROTECT(2); + return res; +} diff --git a/src/djni.c b/src/djni.c new file mode 100644 index 00000000..52ded3ce --- /dev/null +++ b/src/djni.c @@ -0,0 +1,1222 @@ +/* Dynamic JNI loader: no JVM dependency is resolved at namespace load. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#ifdef _WIN32 +#define _WIN32_WINNT 0x0602 +/* EnumProcessModules is K32EnumProcessModules in kernel32, without psapi.dll */ +#define PSAPI_VERSION 2 +#include +#include +#else +#include +#include +#include +#ifdef __APPLE__ +#include +#include +#elif defined(__linux__) +#include +#include +#endif +#endif +#include +#include +#include +#include +#include "djni.h" + +/* jni.h replaces the JDK headers, so configure no longer compares JNI types + with native ones. Check the fixed sizes all supported JDKs use instead. */ +typedef char djni_jbyte_size[sizeof(jbyte) == 1 ? 1 : -1]; +typedef char djni_jboolean_size[sizeof(jboolean) == 1 ? 1 : -1]; +typedef char djni_jchar_size[sizeof(jchar) == 2 ? 1 : -1]; +typedef char djni_jshort_size[sizeof(jshort) == 2 ? 1 : -1]; +typedef char djni_jint_size[sizeof(jint) == 4 ? 1 : -1]; +typedef char djni_jlong_size[sizeof(jlong) == 8 ? 1 : -1]; +typedef char djni_jfloat_size[sizeof(jfloat) == 4 ? 1 : -1]; +typedef char djni_jdouble_size[sizeof(jdouble) == 8 ? 1 : -1]; +typedef char djni_jvalue_size[sizeof(jvalue) == 8 ? 1 : -1]; + +static JNI_GetDefaultJavaVMInitArgs_fnptr JNI_GetDefaultJavaVMInitArgs_fn; +static JNI_CreateJavaVM_fnptr JNI_CreateJavaVM_fn; +static JNI_GetCreatedJavaVMs_fnptr JNI_GetCreatedJavaVMs_fn; +#ifdef _WIN32 +static HMODULE jni_dl; +#else +static void *jni_dl; +#endif +static char last_error[1024]; +static char *mapped_name; + +static void clear_symbols(void) { + JNI_GetDefaultJavaVMInitArgs_fn = 0; + JNI_CreateJavaVM_fn = 0; + JNI_GetCreatedJavaVMs_fn = 0; +} + +#ifdef _WIN32 +static wchar_t *to_wide(const char *s) { + int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, -1, NULL, 0); + wchar_t *w = (n > 0) ? malloc(sizeof(wchar_t) * n) : NULL; + if (n > 0 && !w) SetLastError(ERROR_NOT_ENOUGH_MEMORY); + if (w && MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, -1, w, n) != n) { + free(w); + w = NULL; + } + return w; +} +static char *to_utf8(const wchar_t *w) { + int n = WideCharToMultiByte(CP_UTF8, 0, w, -1, NULL, 0, NULL, NULL); + char *s = (n > 0) ? malloc(n) : NULL; + if (s && WideCharToMultiByte(CP_UTF8, 0, w, -1, s, n, NULL, NULL) != n) { + free(s); + s = NULL; + } + return s; +} +static void save_win_error(const char *what, const char *path, DWORD code) { + wchar_t *msg = NULL; + char *text = NULL; + size_t n; + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, NULL, code, 0, (LPWSTR) &msg, 0, NULL) && msg) + text = to_utf8(msg); + if (msg) LocalFree(msg); + n = text ? strlen(text) : 0; + while (n && (text[n - 1] == '\n' || text[n - 1] == '\r' || text[n - 1] == ' ')) text[--n] = 0; + snprintf(last_error, sizeof(last_error), "%s %s: %s (Windows error %lu)", what, path, + n ? text : "unknown error", (unsigned long) code); + free(text); +} +/* A return value of at least the buffer size is the size required, not the + length of a complete path, so retry instead of using a truncated path. */ +static wchar_t *full_path(const wchar_t *path) { + DWORD size = GetFullPathNameW(path, 0, NULL, NULL), n; + int tries; + for (tries = 0; size && tries < 3; tries++) { + wchar_t *buf = malloc(sizeof(wchar_t) * size); + if (!buf) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } + n = GetFullPathNameW(path, size, buf, NULL); + if (n && n < size) return buf; + free(buf); + size = n; + } + if (size) SetLastError(ERROR_INSUFFICIENT_BUFFER); + return NULL; +} +/* GetModuleFileNameW returns the buffer size when it truncates. */ +static wchar_t *module_path(HMODULE module) { + DWORD size = MAX_PATH, n; + for (;;) { + wchar_t *buf = malloc(sizeof(wchar_t) * size); + if (!buf) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } + n = GetModuleFileNameW(module, buf, size); + if (n && n < size) return buf; + free(buf); + if (!n) return NULL; + if (size >= 65536) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return NULL; } + size *= 2; + } +} +static HANDLE open_file(const wchar_t *path) { + return CreateFileW(path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); +} +/* Compare file identities rather than names: 8.3 aliases, junctions, + symbolic links and spelling variants can all name the same file. + Returns 1 for the same file, 0 for different files, -1 if unknown. */ +static int same_file(const wchar_t *a, const wchar_t *b) { + HANDLE ha = open_file(a), hb = open_file(b); + FILE_ID_INFO ia, ib; + BY_HANDLE_FILE_INFORMATION fa, fb; + int res = -1; + if (ha != INVALID_HANDLE_VALUE && hb != INVALID_HANDLE_VALUE) { + if (GetFileInformationByHandleEx(ha, FileIdInfo, &ia, sizeof(ia)) && + GetFileInformationByHandleEx(hb, FileIdInfo, &ib, sizeof(ib))) + res = ia.VolumeSerialNumber == ib.VolumeSerialNumber && + !memcmp(&ia.FileId, &ib.FileId, sizeof(ia.FileId)); + else if (GetFileInformationByHandle(ha, &fa) && GetFileInformationByHandle(hb, &fb)) + res = fa.dwVolumeSerialNumber == fb.dwVolumeSerialNumber && + fa.nFileIndexHigh == fb.nFileIndexHigh && fa.nFileIndexLow == fb.nFileIndexLow; + } else if (hb != INVALID_HANDLE_VALUE) + res = 0; /* the requested file does not exist, but a JVM is mapped */ + if (ha != INVALID_HANDLE_VALUE) CloseHandle(ha); + if (hb != INVALID_HANDLE_VALUE) CloseHandle(hb); + return res; +} +/* The file mapped for a module, as a path that opens it: the module's + recorded name may now be another file, if the mapped one was renamed and + replaced. GetMappedFileNameW gives an NT device path. */ +static wchar_t *mapped_path(HMODULE module) { + static const wchar_t root[] = L"\\\\?\\GLOBALROOT"; + DWORD size = 32768, skip = (DWORD) wcslen(root), n; + wchar_t *buf = malloc(sizeof(wchar_t) * (skip + size)); + if (!buf) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } + wcscpy(buf, root); + n = K32GetMappedFileNameW(GetCurrentProcess(), (LPVOID) module, buf + skip, size); + if (n && n < size - 1) return buf; + if (n) SetLastError(ERROR_INSUFFICIENT_BUFFER); + free(buf); + return NULL; +} +static int in_image(DWORD rva, DWORD count, DWORD width, DWORD size) { + return rva < size && count <= (size - rva) / width; +} +/* Whether a mapped module defines, rather than forwards, an export. The + export table is read in memory, so nothing is loaded; GetProcAddress + would load the target of a forwarded export. */ +static int owns_export(HMODULE module, const char *sym) { + const BYTE *base = (const BYTE *) module; + const IMAGE_DOS_HEADER *dos = (const IMAGE_DOS_HEADER *) base; + const IMAGE_NT_HEADERS *nt; + const IMAGE_DATA_DIRECTORY *dir; + const IMAGE_EXPORT_DIRECTORY *exp; + const DWORD *names, *functions; + const WORD *ordinals; + DWORD size, i, len = (DWORD) strlen(sym) + 1; + if (dos->e_magic != IMAGE_DOS_SIGNATURE || dos->e_lfanew < (LONG) sizeof(*dos) || + dos->e_lfanew > 4096 - (LONG) sizeof(*nt)) + return 0; + nt = (const IMAGE_NT_HEADERS *) (base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE || nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC || + nt->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) + return 0; + size = nt->OptionalHeader.SizeOfImage; + dir = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!dir->VirtualAddress || dir->VirtualAddress >= size || dir->Size < sizeof(*exp) || + dir->Size > size - dir->VirtualAddress) + return 0; + exp = (const IMAGE_EXPORT_DIRECTORY *) (base + dir->VirtualAddress); + if (!in_image(exp->AddressOfNames, exp->NumberOfNames, 4, size) || + !in_image(exp->AddressOfNameOrdinals, exp->NumberOfNames, 2, size) || + !in_image(exp->AddressOfFunctions, exp->NumberOfFunctions, 4, size)) + return 0; + names = (const DWORD *) (base + exp->AddressOfNames); + ordinals = (const WORD *) (base + exp->AddressOfNameOrdinals); + functions = (const DWORD *) (base + exp->AddressOfFunctions); + /* Names are usually sorted, but a linear scan does not depend on it. */ + for (i = 0; i < exp->NumberOfNames; i++) { + DWORD f; + if (names[i] >= size || len > size - names[i] || memcmp(base + names[i], sym, len)) continue; + if (ordinals[i] >= exp->NumberOfFunctions) return 0; + f = functions[ordinals[i]]; + /* An address inside the export directory names a forwarded export. */ + return f && f < size && (f < dir->VirtualAddress || f - dir->VirtualAddress >= dir->Size); + } + return 0; +} +static int is_jvm_name(const wchar_t *path) { + const wchar_t *base = wcsrchr(path, L'\\'); + return !_wcsicmp(base ? base + 1 : path, L"jvm.dll"); +} +/* A module is a JVM if it is named jvm.dll, even if malformed, or if it + defines the JNI invocation exports and a JVM_ export, whatever its name. + JNI exports alone do not suffice: rJava's wrappers have them too. */ +static int is_jvm_module(HMODULE module, const wchar_t *path) { + return (path && is_jvm_name(path)) || + (owns_export(module, "JVM_CurrentTimeMillis") && owns_export(module, "JNI_CreateJavaVM") && + owns_export(module, "JNI_GetCreatedJavaVMs")); +} +struct jvm_image { HMODULE handle; wchar_t *path; }; +/* Lists the JVM modules mapped into the process, each with a reference + (and path if known). Returns their number, -1 if modules cannot be listed. */ +static int find_jvms(struct jvm_image **out) { + HMODULE *mods = NULL; + struct jvm_image *v = NULL; + DWORD need = 0, size = 0, count, i; + int n = 0, tries; + *out = NULL; + for (tries = 0; tries < 4 && need >= size; tries++) { + HMODULE *m = realloc(mods, (size = need + 64 * sizeof(HMODULE))); + if (!m) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); goto fail; } + mods = m; + if (!K32EnumProcessModules(GetCurrentProcess(), mods, size, &need)) goto fail; + } + if (need >= size) { SetLastError(ERROR_INSUFFICIENT_BUFFER); goto fail; } + count = need / sizeof(HMODULE); + if (count && !(v = calloc(count, sizeof(*v)))) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); goto fail; } + for (i = 0; i < count; i++) { + HMODULE module = NULL; + wchar_t *path; + /* Hold a reference while reading the module: a module unloaded since + the enumeration is skipped, and cannot be replaced while read. */ + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR) mods[i], &module)) continue; + if (module != mods[i]) { FreeLibrary(module); continue; } + path = module_path(module); + if (is_jvm_module(module, path)) { + v[n].handle = module; + v[n++].path = path; + } else { + free(path); + FreeLibrary(module); + } + } + free(mods); + *out = v; + return n; +fail: + free(mods); + free(v); + return -1; +} +static void free_jvms(struct jvm_image *v, int n) { + int i; + for (i = 0; i < n; i++) { + if (v[i].handle) FreeLibrary(v[i].handle); + free(v[i].path); + } + free(v); +} +/* The number of VMs created by a JVM module, or -1 if unknown. */ +static int created_vms(HMODULE module) { + JNI_GetCreatedJavaVMs_fnptr created; + JavaVM *vm; + jsize count = 0; + if (!owns_export(module, "JNI_GetCreatedJavaVMs")) return -1; + created = (JNI_GetCreatedJavaVMs_fnptr) GetProcAddress(module, "JNI_GetCreatedJavaVMs"); + return created && created(&vm, 1, &count) == JNI_OK ? (int) count : -1; +} +/* File versions let an up-to-date system runtime take precedence over an + older JDK copy. Unknown versions are not evidence that the JDK is newer. */ +static ULONGLONG dll_version(const wchar_t *path) { + DWORD ignored, size = GetFileVersionInfoSizeW(path, &ignored); + VS_FIXEDFILEINFO *info; + UINT len; + ULONGLONG version = 0; + void *data = size ? malloc(size) : NULL; + if (data && GetFileVersionInfoW(path, 0, size, data) && + VerQueryValueW(data, L"\\", (void **) &info, &len) && + len >= sizeof(*info) && info->dwSignature == 0xfeef04bd) + version = ((ULONGLONG) info->dwFileVersionMS << 32) | info->dwFileVersionLS; + free(data); + return version; +} +/* Retain loaded runtimes without replacing a host's choice. For each DLL + the JDK bundles, otherwise select the newer of it and System32, preferring + System32 on a tie or unknown version. Do this before jvm.dll binds imports. + Unbundled, unloaded runtimes remain the loader's responsibility. References + last until process exit, without changing PATH or DLL search directories. */ +static void retain_msvc(const wchar_t *bin) { + static const wchar_t *names[] = { + L"vcruntime140.dll", L"vcruntime140_1.dll", L"msvcp140.dll", + L"msvcp140_1.dll", L"msvcp140_2.dll", L"msvcp140_atomic_wait.dll" + }; + static HMODULE retained[sizeof(names) / sizeof(names[0])]; + wchar_t system[MAX_PATH + 64], *bundled; + UINT slen = GetSystemDirectoryW(system, MAX_PATH); + size_t i, blen = wcslen(bin); + if (!slen || slen >= MAX_PATH || !(bundled = malloc(sizeof(wchar_t) * (blen + 64)))) { + fprintf(stderr, "rJava: cannot prepare MSVC runtime paths.\n"); + return; + } + wcscpy(bundled, bin); + bundled[blen] = L'\\'; + system[slen] = L'\\'; + for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + const wchar_t *choice; + DWORD attrs, error; + char *name; + if (retained[i]) continue; + /* Own a reference even if the original loader later releases its DLL. */ + if (GetModuleHandleExW(0, names[i], &retained[i])) continue; + wcscpy(bundled + blen + 1, names[i]); + wcscpy(system + slen + 1, names[i]); + attrs = GetFileAttributesW(bundled); + if (attrs == INVALID_FILE_ATTRIBUTES) { + error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) continue; + name = to_utf8(bundled); + fprintf(stderr, "rJava: cannot inspect bundled MSVC runtime %s (Windows error %lu).\n", + name ? name : "(unknown path)", (unsigned long) error); + free(name); + continue; + } + choice = bundled; + if (GetFileAttributesW(system) != INVALID_FILE_ATTRIBUTES) { + ULONGLONG vb = dll_version(bundled), vs = dll_version(system); + if (!vb || !vs || vs >= vb) choice = system; + } + retained[i] = LoadLibraryExW(choice, NULL, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); + if (retained[i]) continue; + error = GetLastError(); + name = to_utf8(choice); + fprintf(stderr, "rJava: cannot retain MSVC runtime %s (Windows error %lu); later Java native loads may fail.\n", + name ? name : "(unknown path)", (unsigned long) error); + free(name); + } + free(bundled); +} +static void load_bundled_msvc(void) { + wchar_t *path = module_path(jni_dl), *slash; + if (!path) { + fprintf(stderr, "rJava: cannot determine the selected JVM's MSVC runtime directory (Windows error %lu).\n", (unsigned long) GetLastError()); + return; + } + slash = wcsrchr(path, L'\\'); + if (slash) { + *slash = 0; /* bin\\server */ + slash = wcsrchr(path, L'\\'); + if (slash) { *slash = 0; retain_msvc(path); } + } + free(path); +} +static int load_library(const char *path) { + wchar_t *wide = NULL, *full = NULL, *bin = NULL, *slash; + struct jvm_image *jvms = NULL; + DLL_DIRECTORY_COOKIE cookie = NULL; + int res = -2, n = 0; + if (!(wide = to_wide(path))) { + save_win_error("Cannot convert JVM library path to UTF-16", path, GetLastError()); + goto done; + } + if (!(full = full_path(wide))) { + save_win_error("Cannot resolve JVM library path", path, GetLastError()); + goto done; + } + for (slash = full; *slash; slash++) if (*slash == L'/') *slash = L'\\'; + if ((n = find_jvms(&jvms)) < 0) { + save_win_error("Cannot list the modules of", "this process", GetLastError()); + res = -4; + goto done; + } + if (n) { + /* Two mapped JVM modules are two runtimes, even if they are one file. + A path selects the mapped module only if it names the mapped file, + also once a VM was created: djni_attach() attaches without a path. */ + wchar_t *mapped = NULL; + char *name = jvms[0].path ? to_utf8(jvms[0].path) : NULL; + int same = -1; + if (n == 1 && jvms[0].path && (mapped = mapped_path(jvms[0].handle))) + same = same_file(full, mapped); + free(mapped); + if (same == 1 && !is_jvm_name(jvms[0].path)) { + /* The JDK's DLLs import jvm.dll by name, and the loader matches + loaded modules by base name: starting this one would load + another jvm.dll or fail. */ + snprintf(last_error, sizeof(last_error), "The JVM library %s is already mapped under another name: %s. Use a new R process.", + path, name ? name : "unknown path"); + res = -4; + } else if (same == 1) { + /* Reference the mapped module itself: loading an alias of its path + must never map a second copy of the JVM. */ + jni_dl = jvms[0].handle; + jvms[0].handle = NULL; + res = 0; + } else { + if (n > 1) + snprintf(last_error, sizeof(last_error), "%d JVM libraries are already mapped, including %s. Use a new R process.", + n, name ? name : "unknown path"); + else if (same) + snprintf(last_error, sizeof(last_error), "Cannot verify that %s is the JVM library already mapped: %s. Use a new R process.", + path, name ? name : "unknown path"); + else + snprintf(last_error, sizeof(last_error), "Another JVM library is already mapped: %s. Use a new R process.", + name ? name : "unknown path"); + res = -4; + } + free(name); + goto done; + } + /* JDK 8 keeps MSVC runtime DLLs in bin, the parent of bin\server. The + directory is searched only during this load and removed again after + success or failure, leaving the process DLL search state unchanged. */ + if ((bin = malloc(sizeof(wchar_t) * (wcslen(full) + 1)))) { + wcscpy(bin, full); + if ((slash = wcsrchr(bin, L'\\'))) { + *slash = 0; + if ((slash = wcsrchr(bin, L'\\'))) { + *slash = 0; + retain_msvc(bin); + cookie = AddDllDirectory(bin); + } + } + } + jni_dl = LoadLibraryExW(full, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + if (jni_dl) res = 0; + else save_win_error("Cannot load JVM library", path, GetLastError()); + if (cookie) RemoveDllDirectory(cookie); +done: + if (jvms) free_jvms(jvms, n); + free(wide); free(full); free(bin); + return res; +} +/* Select the only mapped JVM module. An unstarted module must have its + usual name, since the JDK DLLs import jvm.dll by name when starting a VM. */ +static int attach_library(void) { + struct jvm_image *jvms; + char *name; + int n = find_jvms(&jvms), res = -4, count = -1; + if (n < 0) { + save_win_error("Cannot list the modules of", "this process", GetLastError()); + return -4; + } + name = (n && jvms[0].path) ? to_utf8(jvms[0].path) : NULL; + if (!n) + snprintf(last_error, sizeof(last_error), "No JVM library is mapped into this process."); + else if (n > 1) + snprintf(last_error, sizeof(last_error), "%d JVM libraries are already mapped, including %s. Use a new R process.", + n, name ? name : "unknown path"); + else if ((count = created_vms(jvms[0].handle)) < 0) + snprintf(last_error, sizeof(last_error), "Cannot determine the VM count of the mapped JVM library: %s.", + name ? name : "unknown path"); + else if (!count && (!jvms[0].path || !is_jvm_name(jvms[0].path))) + snprintf(last_error, sizeof(last_error), "The JVM library is already mapped under another name: %s. Use a new R process.", + name ? name : "unknown path"); + else { + jni_dl = jvms[0].handle; + jvms[0].handle = NULL; + res = 0; + } + free(name); + free_jvms(jvms, n); + return res; +} +const char *djni_mapped(int *vms) { + struct jvm_image *jvms; + int n; + *vms = -1; + free(mapped_name); + mapped_name = NULL; + if ((n = find_jvms(&jvms)) < 0) { + save_win_error("Cannot list the modules of", "this process", GetLastError()); + *vms = -2; + return NULL; + } + if (!n) { + free(jvms); + return NULL; + } + /* With several JVM modules, the VM count of one is not the answer. */ + if (n == 1) *vms = created_vms(jvms[0].handle); + mapped_name = jvms[0].path ? to_utf8(jvms[0].path) : NULL; + if (!mapped_name) mapped_name = strdup("(unknown path)"); + if (!mapped_name) { + snprintf(last_error, sizeof(last_error), "Cannot report the mapped JVM library: out of memory"); + *vms = -2; + } + free_jvms(jvms, n); + return mapped_name; +} +#else +/* Whether path names the file mapped at addr, by device and inode, which + hold across symbolic links, hard links and equivalent spellings. The name + the loader recorded is not used: the file there may have been deleted or + replaced since, while the mapping keeps the old file. Returns 1 for the + same file, 0 for another or no file, -1 if the mapped file is unknown, and + -2 for mismatched devices, where an inode mismatch proves nothing, and + 2 for matching inodes on mismatched devices, requiring a loader check. */ +static int same_as_mapped(const void *addr, const char *path) { + struct stat s; + int res = -1; + if (stat(path, &s)) return 0; +#ifdef __APPLE__ + { + struct proc_regionwithpathinfo r; + uint64_t a = (uint64_t) (uintptr_t) addr; + /* The region returned starts after addr if nothing is mapped there. */ + if (addr && proc_pidinfo(getpid(), PROC_PIDREGIONPATHINFO, a, &r, sizeof(r)) == (int) sizeof(r) && + a >= r.prp_prinfo.pri_address && a - r.prp_prinfo.pri_address < r.prp_prinfo.pri_size && + r.prp_vip.vip_vi.vi_stat.vst_ino) + res = r.prp_vip.vip_vi.vi_stat.vst_dev == (uint32_t) s.st_dev && + r.prp_vip.vip_vi.vi_stat.vst_ino == (uint64_t) s.st_ino; + } +#elif defined(__linux__) + { + FILE *f = addr ? fopen("/proc/self/maps", "r") : NULL; + char *line = NULL; + size_t size = 0; + unsigned long long a = (uintptr_t) addr, start, end, ino; + unsigned int maj = 0, min = 0; + int device_mismatch; + if (f) { + while (getline(&line, &size, f) > 0) + if (sscanf(line, "%llx-%llx %*s %*s %x:%x %llu", &start, &end, &maj, &min, &ino) == 5 && + a >= start && a < end) { + if (ino) { + device_mismatch = major(s.st_dev) != maj || minor(s.st_dev) != min; + res = device_mismatch ? ((unsigned long long) s.st_ino == ino ? 2 : -2) : + (unsigned long long) s.st_ino == ino; + } + break; + } + free(line); + fclose(f); + } + /* btrfs reports a subvolume device to stat(), but the superblock + device in maps. Overlayfs may report a layer's inode as well. + Neither device mismatch establishes that this is another file. */ + } +#endif + return res; +} +static int is_jvm_name(const char *name) { + const char *base = strrchr(name, '/'); + base = base ? base + 1 : name; + return !strcmp(base, "libjvm.dylib") || !strcmp(base, "libjvm.so"); +} +#ifdef __linux__ +/* An image as dl_iterate_phdr() reports it. Its memory is read only while + the callback runs: glibc holds its loader lock until dl_iterate_phdr() + returns, and dlclose() unmaps an image only while holding that lock + (elf/dl-iteratephdr.c, elf/dl-close.c), so any image, Java or not, may be + unmapped right after. musl never unmaps an image. */ +struct elf { + uintptr_t base; + const ElfW(Phdr) *phdr; + size_t phnum; + const ElfW(Dyn) *dyn; +}; +/* Whether [a, a + len) lies in one readable segment of the image. */ +static int in_image(const struct elf *im, uintptr_t a, size_t len) { + size_t i; + for (i = 0; i < im->phnum; i++) { + const ElfW(Phdr) *ph = &im->phdr[i]; + uintptr_t start = im->base + (uintptr_t) ph->p_vaddr, end = start + (uintptr_t) ph->p_memsz; + if (ph->p_type == PT_LOAD && (ph->p_flags & PF_R) && a >= start && a < end && len <= end - a) return 1; + } + return 0; +} +/* glibc relocates the addresses of a dynamic section in place on most + targets, musl never does: an address is used only if it is in the image. */ +static uintptr_t dyn_addr(const struct elf *im, uintptr_t v) { + if (in_image(im, v, 1)) return v; + if (in_image(im, im->base + v, 1)) return im->base + v; + return 0; +} +/* The number of entries in the dynamic symbol table, from its hash table as + musl counts them (ldso/dynlink.c), or -1 if the table cannot be read. */ +static long count_syms(const struct elf *im, const uint32_t *hash, const uint32_t *gnu) { + const uint32_t *buckets, *chain; + uint32_t i, max = 0; + if (!gnu) return in_image(im, (uintptr_t) hash, 2 * sizeof(*hash)) ? (long) hash[1] : -1; + if (!in_image(im, (uintptr_t) gnu, 4 * sizeof(*gnu))) return -1; + /* The bloom filter words have the size of an address. */ + buckets = (const uint32_t *) ((const ElfW(Addr) *) (gnu + 4) + gnu[2]); + if (!in_image(im, (uintptr_t) buckets, (size_t) gnu[0] * sizeof(*buckets))) return -1; + for (i = 0; i < gnu[0]; i++) if (buckets[i] > max) max = buckets[i]; + if (!max) return 0; + if (max < gnu[1]) return -1; + /* The chain of the last bucket ends with an odd hash value. */ + for (chain = buckets + gnu[0] + (max - gnu[1]); ; chain++, max++) { + if (!in_image(im, (uintptr_t) chain, sizeof(*chain))) return -1; + if (*chain & 1) return (long) max + 1; + } +} +/* Whether the image defines sym, read from its dynamic symbol table in + memory as the dynamic loader reads it. Unlike dlsym(), this needs no + handle: musl takes one by opening the recorded name again, which may since + name another file or none. Returns 1 if defined, setting *fn to its + address if it is a function, 0 if not, -1 if the table cannot be read. */ +static int image_symbol(const struct elf *im, const char *sym, uintptr_t *fn) { + const ElfW(Dyn) *d = im->dyn; + const ElfW(Sym) *syms; + const char *strs; + const uint32_t *hash = NULL, *gnu = NULL; + uintptr_t symtab = 0, strtab = 0, strsz = 0; + size_t len = strlen(sym) + 1; + long i, n; + *fn = 0; + if (!d) return 0; /* no dynamic section, so no exports */ + for (;; d++) { + if (!in_image(im, (uintptr_t) d, sizeof(*d))) return -1; + if (d->d_tag == DT_NULL) break; + switch (d->d_tag) { + case DT_SYMTAB: symtab = dyn_addr(im, d->d_un.d_ptr); if (!symtab) return -1; break; + case DT_STRTAB: strtab = dyn_addr(im, d->d_un.d_ptr); if (!strtab) return -1; break; + case DT_STRSZ: strsz = d->d_un.d_val; break; + case DT_HASH: if (!(hash = (const uint32_t *) dyn_addr(im, d->d_un.d_ptr))) return -1; break; + case DT_GNU_HASH: if (!(gnu = (const uint32_t *) dyn_addr(im, d->d_un.d_ptr))) return -1; break; + } + } + /* Without both tables and a hash table, the loader finds no symbol. */ + if (!symtab || !strtab || (!hash && !gnu)) return 0; + if (!in_image(im, strtab, strsz) || (n = count_syms(im, hash, gnu)) < 0 || + !in_image(im, symtab, (size_t) n * sizeof(*syms))) + return -1; + syms = (const ElfW(Sym) *) symtab; + strs = (const char *) strtab; + for (i = 0; i < n; i++) { + const ElfW(Sym) *s = &syms[i]; + if (s->st_shndx == SHN_UNDEF || s->st_name >= strsz || len > strsz - s->st_name || + memcmp(strs + s->st_name, sym, len)) + continue; + /* The low four bits of st_info are the type, in ELF32 and ELF64. */ + if ((s->st_info & 0xf) == STT_FUNC) *fn = im->base + (uintptr_t) s->st_value; + return 1; + } + return 0; +} +#endif +/* An image mapped into the process: its name as the dynamic loader records + it, and where it is mapped, to tell its own definitions from those of its + dependencies (macOS). On Linux, only JVM images are listed, with what + phdr_image() read of them: their dynamic section, which identifies them + but is not read again, their first readable segment and their + JNI_GetCreatedJavaVMs. */ +struct image { + char *name; +#ifdef __APPLE__ + const void *header; +#elif defined(__linux__) + const ElfW(Dyn) *dyn; + uintptr_t addr, created; +#endif +}; +struct images { struct image *v; size_t n, size; int failed; }; +static struct image *add_image(struct images *l, const char *name) { + struct image *im; + if (l->n == l->size) { + size_t size = l->size ? 2 * l->size : 64; + struct image *v = realloc(l->v, size * sizeof(*v)); + if (!v) { l->failed = 1; return NULL; } + l->v = v; + l->size = size; + } + im = &l->v[l->n]; + memset(im, 0, sizeof(*im)); + if (!(im->name = strdup(name))) { l->failed = 1; return NULL; } + l->n++; + return im; +} +#ifdef __linux__ +/* Records the JVM images, read only while dl_iterate_phdr() runs (struct + elf). An image is identified by its exports, not by a handle, which musl + may be unable to take; an unreadable table is not taken as no JVM. Only + reads memory: dlopen() must not be called from this callback. */ +static int phdr_image(struct dl_phdr_info *info, size_t size, void *data) { + struct images *l = (struct images *) data; + struct elf e = { (uintptr_t) info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, NULL }; + struct image *im; + uintptr_t fn, addr = 0, created = 0; + size_t i; + int jvm, owned; + (void) size; + if (!info->dlpi_name || !*info->dlpi_name) return 0; /* the executable */ + for (i = 0; i < e.phnum; i++) { + const ElfW(Phdr) *ph = &e.phdr[i]; + if (ph->p_type == PT_LOAD && (ph->p_flags & PF_R) && !addr) addr = e.base + (uintptr_t) ph->p_vaddr; + if (ph->p_type == PT_DYNAMIC) e.dyn = (const ElfW(Dyn) *) (e.base + (uintptr_t) ph->p_vaddr); + } + jvm = is_jvm_name(info->dlpi_name); + owned = image_symbol(&e, "JVM_CurrentTimeMillis", &fn); + if (owned > 0) owned = image_symbol(&e, "JNI_CreateJavaVM", &fn); + if (owned > 0 || jvm) { + int found = image_symbol(&e, "JNI_GetCreatedJavaVMs", &fn); + if (owned > 0) owned = found; + if (found > 0) created = fn; + } + if (owned < 0 && !jvm) { + snprintf(last_error, sizeof(last_error), "Cannot read the exports of %s, mapped into this process", info->dlpi_name); + l->failed = 2; + return 1; + } + if (!jvm && owned <= 0) return 0; + if (!(im = add_image(l, info->dlpi_name))) return 1; + im->dyn = e.dyn; + im->addr = addr; + im->created = created; + return 0; +} +#endif +static void free_images(struct images *l) { + size_t i; + for (i = 0; i < l->n; i++) free(l->v[i].name); + free(l->v); +} +/* Returns 0, or -1 if the images cannot be listed. Other systems list none. */ +static int list_images(struct images *l) { +#ifdef __APPLE__ + uint32_t i, n = _dyld_image_count(); + for (i = 0; i < n; i++) { + const char *name = _dyld_get_image_name(i); + const struct mach_header *header = _dyld_get_image_header(i); + struct image *im; + if (!name || !header) continue; + if (!(im = add_image(l, name))) break; + im->header = header; + } +#elif defined(__linux__) + dl_iterate_phdr(phdr_image, l); +#endif + return l->failed ? -1 : 0; +} +#ifdef __APPLE__ +/* Whether the image defines sym itself: dlsym() on its handle also finds + definitions it inherits from the libraries it depends on. */ +static int owns(void *handle, const struct image *im, const char *sym) { + void *p = dlsym(handle, sym); + Dl_info info; + return p && dladdr(p, &info) && info.dli_fbase == im->header; +} +#elif defined(__linux__) +/* A reference to the image by name, if the name opens it without loading + anything. musl matches a name with a slash by the file now there, which + may be another mapped library, so the handle must be the image's own. */ +static void *reference(const ElfW(Dyn) *dyn, const char *name) { + struct link_map *map; + void *handle = dlopen(name, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (handle && (dlinfo(handle, RTLD_DI_LINKMAP, &map) || (const void *) map->l_ld != (const void *) dyn)) { + dlclose(handle); + handle = NULL; + } + dlerror(); /* failed lookups are not errors of the caller */ + return handle; +} +#endif +/* A mapped JVM image: its recorded name, a reference to it if one could be + taken without loading anything, its JNI_GetCreatedJavaVMs, and an address + in it. */ +struct jvm_image { + char *name; + void *handle; + JNI_GetCreatedJavaVMs_fnptr created; + const void *addr; +#ifdef __linux__ + const ElfW(Dyn) *dyn; +#endif +}; +static void free_jvms(struct jvm_image *v, int n) { + int i; + for (i = 0; i < n; i++) { + if (v[i].handle) dlclose(v[i].handle); + free(v[i].name); + } + free(v); +} +/* Lists the JVM images mapped into the process. An image is a JVM if it has + a JVM library name, even if malformed, or if it defines the JNI invocation + exports and a JVM_ export, whatever its name. JNI exports alone do not + suffice: the macOS libjli defines them to forward to libjvm. Returns the + number of JVM images, or -1 with last_error set if the images cannot be + listed, or (Linux) the exports of one cannot be read. On Linux, the JVM + images were identified by phdr_image(), and their functions are called + only through a reference that holds them mapped (created_vms()). */ +static int find_jvms(struct jvm_image **out) { + struct images l = { NULL, 0, 0, 0 }; + struct jvm_image *v = NULL; + size_t i; + int n = 0; + *out = NULL; + if (list_images(&l) || (l.n && !(v = calloc(l.n, sizeof(*v))))) { + if (l.failed != 2) snprintf(last_error, sizeof(last_error), "Cannot list the libraries mapped into this process"); + free_images(&l); + free(v); + return -1; + } + for (i = 0; i < l.n; i++) { + struct image *im = &l.v[i]; + void *handle = NULL; + int jvm = is_jvm_name(im->name); +#ifdef __APPLE__ + /* RTLD_NOLOAD returns a reference only if the image is already mapped, + and RTLD_LAZY leaves the relocation of other images unchanged. */ + handle = dlopen(im->name, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (handle && !jvm) + jvm = owns(handle, im, "JVM_CurrentTimeMillis") && owns(handle, im, "JNI_CreateJavaVM") && + owns(handle, im, "JNI_GetCreatedJavaVMs"); + if (jvm && handle && owns(handle, im, "JNI_GetCreatedJavaVMs")) + *(void **) &v[n].created = dlsym(handle, "JNI_GetCreatedJavaVMs"); +#elif defined(__linux__) + /* Only JVM images are listed. The recorded name, or the name the + JDK's libraries bind to. */ + jvm = 1; + if (!(handle = reference(im->dyn, im->name))) handle = reference(im->dyn, "libjvm.so"); + v[n].dyn = im->dyn; + v[n].created = (JNI_GetCreatedJavaVMs_fnptr) im->created; +#endif + if (jvm) { + v[n].name = im->name; + im->name = NULL; +#ifdef __APPLE__ + v[n].addr = im->header; +#elif defined(__linux__) + v[n].addr = (const void *) im->addr; +#endif + v[n++].handle = handle; + } else if (handle) + dlclose(handle); + } + dlerror(); /* failed lookups are not errors of the caller */ + free_images(&l); + *out = v; + return n; +} +/* The number of VMs created by a JVM image, or -1 if unknown. Its function + is called only while a reference holds the image mapped. musl, the Linux + C library assumed when neither glibc nor bionic is, never unmaps an image + (its dlclose() only checks the handle), so an address read while listing + the images stays valid without one. */ +static int created_vms(const struct jvm_image *im) { + JavaVM *vm; + jsize count = 0; + JNI_GetCreatedJavaVMs_fnptr created = im->created; +#if !defined(__linux__) || defined(__GLIBC__) || defined(__BIONIC__) + if (!im->handle) return -1; +#endif +#ifdef __linux__ + /* Resolve from the held image, rather than calling an address saved + before the reference was acquired. A library may have been unmapped + and another mapped at the same address in that interval. */ + if (im->handle) { + Dl_info info; + void *symbol = dlsym(im->handle, "JNI_GetCreatedJavaVMs"); + created = symbol && dladdr(symbol, &info) && info.dli_fbase == im->addr ? + (JNI_GetCreatedJavaVMs_fnptr) symbol : NULL; + dlerror(); + } +#endif + return created && created(&vm, 1, &count) == JNI_OK ? (int) count : -1; +} +static void use_new_process(void) { + size_t n = strlen(last_error); + snprintf(last_error + n, sizeof(last_error) - n, ". Use a new R process."); +} +static int load_library(const char *path) { + struct jvm_image *jvms; + int n = find_jvms(&jvms), res = -4; + if (n < 0) { + use_new_process(); + return -4; + } + if (n > 1) { + snprintf(last_error, sizeof(last_error), "%d JVM libraries are already mapped, including %s. Use a new R process.", + n, jvms[0].name); + } else if (n == 1) { + /* A path selects the mapped image only if it names the mapped file, + also once a VM was created: djni_attach() attaches without a path. */ + int same = same_as_mapped(jvms[0].addr, path); +#ifdef __linux__ + if (same == 2) { + /* A matching inode alone is insufficient across devices. Ask + the loader whether this path already names this exact image; + RTLD_NOLOAD never maps the requested file or its dependencies. + Retain that reference so no second JVM is loaded. */ + void *handle = reference(jvms[0].dyn, path); + if (handle) { + if (jvms[0].handle) dlclose(jvms[0].handle); + jvms[0].handle = handle; + same = 1; + } else same = -2; + } + /* The path names the mapped file, so it opens the image if the + names find_jvms() tried no longer do. */ + if (same == 1 && !jvms[0].handle) jvms[0].handle = reference(jvms[0].dyn, path); +#endif + if (!same) + snprintf(last_error, sizeof(last_error), "Another JVM library is already mapped: %s. Use a new R process.", jvms[0].name); + else if (same == -2) + snprintf(last_error, sizeof(last_error), "Cannot verify that %s is the JVM library already mapped: %s, because the filesystem mapping identity cannot be verified. Use a new R process.", + path, jvms[0].name); + else if (same < 0) + snprintf(last_error, sizeof(last_error), "Cannot verify that %s is the JVM library already mapped: %s. Use a new R process.", path, jvms[0].name); + else if (!jvms[0].handle) + snprintf(last_error, sizeof(last_error), "Cannot reference the JVM library already mapped: %s. Use a new R process.", jvms[0].name); + else { + /* Reference the mapped image itself, so an alias cannot map another copy. */ + jni_dl = jvms[0].handle; + jvms[0].handle = NULL; + res = 0; + } + } else if (!(jni_dl = dlopen(path, RTLD_LOCAL | RTLD_NOW))) { + const char *error = dlerror(); + snprintf(last_error, sizeof(last_error), "%s", error ? error : "dynamic loader error"); + res = -2; + } else + res = 0; + free_jvms(jvms, n); + return res; +} +#ifdef __linux__ +static int check_jvm_name(const char *path); +#endif +/* Select the only mapped JVM image using a held reference, without loading + another JVM. Acquiring the reference may inspect the recorded path, whose + file may have been deleted or replaced. */ +static int attach_library(void) { + struct jvm_image *jvms; + int n = find_jvms(&jvms), res = -4, count = -1; + if (n < 0) { + use_new_process(); + return -4; + } + if (!n) + snprintf(last_error, sizeof(last_error), "No JVM library is mapped into this process."); + else if (n > 1) + snprintf(last_error, sizeof(last_error), "%d JVM libraries are already mapped, including %s. Use a new R process.", + n, jvms[0].name); + else if (!jvms[0].handle) + snprintf(last_error, sizeof(last_error), "Cannot reference the JVM library mapped into this process: %s. Use a new R process.", jvms[0].name); + else if ((count = created_vms(&jvms[0])) < 0) + snprintf(last_error, sizeof(last_error), "Cannot determine the VM count of the mapped JVM library: %s.", jvms[0].name); + else { + jni_dl = jvms[0].handle; + jvms[0].handle = NULL; + res = 0; +#ifdef __linux__ + /* Starting an unstarted JVM still requires its dependencies to bind + to this very image, including on musl and with renamed libraries. */ + if (!count && (res = check_jvm_name(jvms[0].name))) djni_unload(); +#endif + } + free_jvms(jvms, n); + return res; +} +const char *djni_mapped(int *vms) { + struct jvm_image *jvms; + int n; + *vms = -1; + free(mapped_name); + mapped_name = NULL; + if ((n = find_jvms(&jvms)) < 0) { + *vms = -2; + return NULL; + } + /* With several JVM images, the VM count of one is not the answer. */ + if (n == 1) *vms = created_vms(&jvms[0]); + if (n) { + mapped_name = jvms[0].name; + jvms[0].name = NULL; + } + free_jvms(jvms, n); + return mapped_name; +} +#endif + +static int load_sym(const char *sym, void **ptr) { +#ifdef _WIN32 + *ptr = (void *) GetProcAddress(jni_dl, sym); + if (!*ptr) { save_win_error("Cannot find JNI entry point", sym, GetLastError()); return -1; } +#else + const char *error; + *ptr = dlsym(jni_dl, sym); + if (!*ptr) { + error = dlerror(); + snprintf(last_error, sizeof(last_error), "%s", error ? error : "dynamic loader error"); + return -1; + } +#endif + return 0; +} + +/* Resolves the JNI entry points of jni_dl, releasing it if one is missing. */ +static int load_syms(void) { + if (load_sym("JNI_GetDefaultJavaVMInitArgs", (void **) &JNI_GetDefaultJavaVMInitArgs_fn) || + load_sym("JNI_CreateJavaVM", (void **) &JNI_CreateJavaVM_fn) || + load_sym("JNI_GetCreatedJavaVMs", (void **) &JNI_GetCreatedJavaVMs_fn)) { + djni_unload(); + return -3; + } + return 0; +} + +#ifdef __linux__ +/* The JDK's own libraries (libjava, libjimage, ...) depend on libjvm.so by + name, with only their own directory as run path. glibc matches that name + to the loaded JVM library by its SONAME. musl matches it only to a library + it found by searching: LD_LIBRARY_PATH as set when the process started, + then its system path. A JVM library selected by path is then not found, + or another file is, and starting a VM would map a second JVM library. + Resolving the name without loading anything shows what they will get. */ +static int check_jvm_name(const char *path) { + void *named = dlopen("libjvm.so", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + const char *slash = strrchr(path, '/'); + int dir = slash ? (int) (slash - path) : 1; + if (named) dlclose(named); + dlerror(); + if (named == jni_dl) return 0; + snprintf(last_error, sizeof(last_error), + "The JDK's libraries load libjvm.so by name, which in this process does not resolve to %s. " + "With musl, put %.*s on LD_LIBRARY_PATH before R starts, e.g. with R_JAVA_LD_LIBRARY_PATH. Use a new R process.", + path, dir, slash ? path : "."); + return -4; +} +#endif + +/* returns: 0 = success, -1 = already loaded, -2 = load error, -3 = symbols + not found, -4 = another JVM library is already mapped, or (Linux) the + JDK's libraries would not bind to the selected one */ +int djni_load(const char *path) { + int res; + last_error[0] = 0; + if (jni_dl) return -1; + if ((res = load_library(path))) return res; + res = load_syms(); +#ifdef __linux__ + /* After load_syms(), so that a library that is no JVM is reported as such. */ + if (!res && (res = check_jvm_name(path))) djni_unload(); +#endif +#ifdef _WIN32 + if (!res) load_bundled_msvc(); +#endif + return res; +} +/* returns: 0 = success, -1 = already loaded, -3 = symbols not found, -4 = no + single verifiable mapped JVM library */ +int djni_attach(void) { + int res; + last_error[0] = 0; + if (jni_dl) return -1; + if ((res = attach_library())) return res; + res = load_syms(); +#ifdef _WIN32 + if (!res) load_bundled_msvc(); +#endif + return res; +} +int djni_unload(void) { + if (!jni_dl) return -1; +#ifdef _WIN32 + if (!FreeLibrary(jni_dl)) { save_win_error("Cannot unload JVM library", "jvm.dll", GetLastError()); return -2; } +#else + if (dlclose(jni_dl)) { + const char *error = dlerror(); + snprintf(last_error, sizeof(last_error), "%s", error ? error : "dynamic loader error"); + return -2; + } +#endif + jni_dl = 0; + clear_symbols(); + return 0; +} +int djni_loaded(void) { return jni_dl ? 1 : 0; } +const char *djni_last_error(void) { return last_error[0] ? last_error : 0; } +jint JNI_GetDefaultJavaVMInitArgs(void *args) { + return JNI_GetDefaultJavaVMInitArgs_fn ? JNI_GetDefaultJavaVMInitArgs_fn(args) : JNI_NO_DJNI; +} +jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args) { + return JNI_CreateJavaVM_fn ? JNI_CreateJavaVM_fn(pvm, penv, args) : JNI_NO_DJNI; +} +jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs) { + return JNI_GetCreatedJavaVMs_fn ? JNI_GetCreatedJavaVMs_fn(vmBuf, bufLen, nVMs) : JNI_NO_DJNI; +} + + +/* define DJNI_MAIN_TEST to test the load + create + print version + destroy + unload cycle + it will create an executable - just compile djni.c alone for this (not used in rJava) */ +#ifdef DJNI_MAIN_TEST + +#include +#include +#include + +/* calls java.lang.System.getProperty("java.version") and returns the result or NULL + if something went wrong. Any non-NULL returns must be free()d */ +static char *java_version(JNIEnv *eenv) { + char *jver = 0; + jclass sys = (*eenv)->FindClass(eenv, "java/lang/System"); + if (sys) { + jmethodID mid = (*eenv)->GetStaticMethodID(eenv, sys, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); + if (mid) { + jstring jvs = (*eenv)->NewStringUTF(eenv, "java.version"); + if (jvs) { + jobject jres = (*eenv)->CallStaticObjectMethod(eenv, sys, mid, jvs); + (*eenv)->DeleteLocalRef(eenv, jvs); + if (jres) { + const char *str = (*eenv)->GetStringUTFChars(eenv, (jstring) jres, NULL); + if (str) { + jver = strdup(str); + (*eenv)->ReleaseStringUTFChars(eenv, (jstring) jres, str); + } + (*eenv)->DeleteLocalRef(eenv, jres); + } + } + } + (*eenv)->DeleteLocalRef(eenv, sys); + } + return jver; +} + +/* create a JVM, call java_version(), destroy the JVM */ +static int run_jvm_test(void) { + JavaVM *jvm; + JNIEnv *eenv; + JavaVMOption vm_options[4]; + JavaVMInitArgs vm_args; + vm_args.version = JNI_VERSION_1_2; + if(JNI_GetDefaultJavaVMInitArgs(&vm_args) != JNI_OK) { + fprintf(stderr, "JNI 1.2 or higher is required\n"); + return -1; + } + vm_args.ignoreUnrecognized = JNI_TRUE; + vm_args.options = vm_options; + vm_args.nOptions = 0; + jint res = JNI_CreateJavaVM(&jvm,(void **)&eenv, &vm_args); + if (res != 0) { + fprintf(stderr, "Cannot create Java virtual machine (JNI_CreateJavaVM returned %d)\n", (int) res); + return -1; + } + if (!eenv) { + fprintf(stderr, "Cannot obtain JVM environment"); + return -1; + } + printf("JVM initialized, JNI version: %x\n", (*eenv)->GetVersion(eenv)); + { + char *jver = java_version(eenv); + if (jver) { + printf("Java version: %s\n", jver); + free(jver); + } + } + /* this shouldn't be even needed, but was last-ditch effort - makes no difference */ + if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) + fprintf(stderr, "FWIW: cannot detach thread.\n"); + /* destroy the VM */ + if ((*jvm)->DestroyJavaVM(jvm) != JNI_OK) { + fprintf(stderr, "ERROR: cannot destroy VM\n"); + return -1; + } + printf("JVM destroyed.\n"); + return 0; +} + +int test_jvm(const char *path) { + int e = djni_load(path); + if (e) { + printf("Cannot load: %d (%s)\n", e, djni_last_error() ? djni_last_error() : "not a dl error"); + return 1; + } + printf("JVM Loaded!\n"); + if (!run_jvm_test()) { + if (djni_unload()) + fprintf(stderr, "ERROR: cannot unload JNI (%s)\n", djni_last_error() ? djni_last_error() : "no information"); + else + printf("JNI unloaded.\n"); + } + return 0; +} + +/* for debugging on macOS - prints all dynamic loads */ +#ifdef __APPLE__ +#include + +static void debug_add_image(const struct mach_header* mh, intptr_t vmaddr_slide) { + Dl_info DlInfo; + dladdr(mh, &DlInfo); + printf("LOADED: %s\n", DlInfo.dli_fname); +} + +static void rm_image(const struct mach_header* mh, intptr_t vmaddr_slide) { + Dl_info DlInfo; + dladdr(mh, &DlInfo); + printf("REMOVE: %s\n", DlInfo.dli_fname); +} +#endif + +int main(int ac, char **av) { +#ifdef __APPLE__ + _dyld_register_func_for_add_image(debug_add_image); + _dyld_register_func_for_remove_image(rm_image); +#endif + + test_jvm("/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/lib/server/libjvm.dylib"); +#ifdef SAME_VM /* option 1: try the same VM twice (will load but fail to create VM) */ + test_jvm("/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/lib/server/libjvm.dylib"); +#else /* option 2: will even fail to load */ + test_jvm("/Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home/lib/server/libjvm.dylib"); +#endif + return 0; +} + +#endif diff --git a/src/djni.h b/src/djni.h new file mode 100644 index 00000000..cd29f848 --- /dev/null +++ b/src/djni.h @@ -0,0 +1,47 @@ +/* Dynamic JNI + Allows to load JVM dynamically into a process. + + (C)2024 Simon Urbanek + License: LGPL-2.1 or MIT +*/ +#ifndef DJNI_H__ +#define DJNI_H__ + +#include "jni.h" + +/* returned if no run-time is loaded */ +#define JNI_NO_DJNI -99 + +/* Selects the JVM library at path. If one is already mapped, path must name + the mapped file, whether or not it has created a VM. */ +int djni_load(const char *path); +/* Selects the only JVM library mapped into the process, whether or not it + has created a VM, by its held image: no additional JVM is loaded. + A loader lookup may inspect its recorded path to acquire the reference. + On Windows, selection and attachment retain MSVC runtimes from the JDK + or System32, preserving already loaded copies, for later Java native loads. */ +int djni_attach(void); +int djni_unload(void); +const char* djni_last_error(void); +int djni_loaded(void); +/* path of a JVM library already mapped into the process (or NULL), without + loading one; *vms is the number of created VMs, -1 if unknown (also if + several JVM libraries are mapped). NULL with *vms == -2 means the mapped + libraries could not be listed, see djni_last_error(). */ +const char* djni_mapped(int *vms); + +/* The wrappers below have the names of the JVM exports. Hide them, so that + calls from rJava bind to the wrappers even if a JVM library was loaded + with global symbols, as by the java launcher of an application + embedding R; ELF symbol interposition would otherwise bypass djni. */ +#if defined(__GNUC__) && !defined(_WIN32) +#define DJNI_LOCAL __attribute__((visibility("hidden"))) +#else +#define DJNI_LOCAL +#endif + +DJNI_LOCAL jint JNI_GetDefaultJavaVMInitArgs(void *args); +DJNI_LOCAL jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args); +DJNI_LOCAL jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs); + +#endif diff --git a/src/init.c b/src/init.c index d44d92d7..885a6f39 100644 --- a/src/init.c +++ b/src/init.c @@ -89,10 +89,12 @@ static void JNICALL exit_hook(int status) { exit(status); } +/* -1 if no run-time is loaded, otherwise number of active VMs */ int existingJVMs(void) { jsize vms = 0; JavaVM *jvms[32]; - return (JNI_GetCreatedJavaVMs(jvms, 32, &vms) >= 0) ? vms : 0; + jint res = JNI_GetCreatedJavaVMs(jvms, 32, &vms); + return (res == JNI_NO_DJNI) ? -1 : ((res >= 0) ? vms : 0); } /* 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, if(!user_classpath) user_classpath = ""; vm_args.version = JNI_VERSION_1_2; - if(JNI_GetDefaultJavaVMInitArgs(&vm_args) != JNI_OK) { + if((res = JNI_GetDefaultJavaVMInitArgs(&vm_args)) != JNI_OK) { + if (res == JNI_NO_DJNI) /* DJNI has not loaded a run-time yet */ + error("No Java run-time is loaded."); error("JNI 1.2 or higher is required"); return -1; } @@ -193,8 +197,11 @@ static int initJVM(const char *user_classpath, int opts, char **optv, int hooks, if (disableGuardPages && (res != 0 || !eenv)) return -2; /* perhaps this VM does not allow disabling guard pages */ - if (res != 0) - error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %ld)", (long int) res); + if (res != 0) { + if (rJava_JVM_state != JVM_STATE_NONE) + Rf_warning("Attempt to re-create a second JVM in a process - most known Java implementations do not support it."); + Rf_error("Cannot create Java virtual machine (JNI_CreateJavaVM returned %d)", (int) res); + } if (!eenv) error("Cannot obtain JVM environment"); @@ -760,21 +767,15 @@ static SEXP RinitJVM_jsw(SEXP par) { /** RinitJVM(classpath) initializes JVM with the specified class path */ REP SEXP RinitJVM(SEXP par) { - + if (!djni_loaded()) + Rf_error("No Java run-time is loaded."); #ifndef JVM_STACK_WORKAROUND - return RinitJVM_real(par, 0); + return RinitJVM_real(par, 0); #else - return RinitJVM_jsw(par); + return RinitJVM_jsw(par); #endif } -REP void doneJVM(void) { - (*jvm)->DestroyJavaVM(jvm); - jvm = 0; - eenv = 0; - rJava_JVM_state = JVM_STATE_DESTROYED; -} - /** * Initializes the cached values of classes and methods used internally * These classes and methods are the ones that are in rJava (RJavaTools, ...) diff --git a/src/jni.h b/src/jni.h new file mode 100644 index 00000000..1db2a39f --- /dev/null +++ b/src/jni.h @@ -0,0 +1,434 @@ +/* JNI compatibility header - based on JNI 1.2 specification */ + +#ifndef RJAVA_JNI_H__ +#define RJAVA_JNI_H__ + +/* This header only matches JNI 1.2 - we don't need more + (and we don't support anything lower due to the way the + JVM initialization was changed in 1.2) */ +#define JNI_VERSION_1_2 0x00010002 + +/* call API uses va_list */ +#include + +/* basic native types */ +#ifdef _LP64 +typedef long jlong; +#else +typedef long long jlong; +#endif + +/* Windows support is currently untested */ +#ifdef WIN32 +#define JNICALL __stdcall +#else +#define JNICALL +#endif + +typedef signed char jbyte; +typedef int jint; +typedef unsigned char jboolean; +typedef unsigned short jchar; +typedef short jshort; +typedef float jfloat; +typedef double jdouble; +typedef jint jsize; + +struct jni_jobject; + +typedef struct jni_jobject *jobject; +typedef jobject jclass; +typedef jobject jthrowable; +typedef jobject jstring; +typedef jobject jarray; +typedef jobject jweak; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jarray jobjectArray; + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +/* basic defines */ +#define JNI_FALSE 0 +#define JNI_TRUE 1 +#define JNI_OK 0 +#define JNI_ERR (-1) + +/* additonal types */ +struct jni_jfieldID; +typedef struct jni_jfieldID *jfieldID; + +struct jni_jmethodID; +typedef struct jni_jmethodID *jmethodID; + +typedef struct { + char *name; + char *signature; + void *fnPtr; +} JNINativeMethod; + +struct JNINativeInterface_; +struct JNIEnv_; + +typedef const struct JNINativeInterface_ *JNIEnv; + +struct JavaVM_; + +typedef const struct JNIInvokeInterface_ *JavaVM; + +typedef struct JavaVMOption { + char *optionString; + void *extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; + jint nOptions; + JavaVMOption *options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +typedef struct JavaVMAttachArgs { + jint version; + char *name; + jobject group; +} JavaVMAttachArgs; + +struct JNIInvokeInterface_ { + void *reserved0; + void *reserved1; + void *reserved2; + + jint (JNICALL *DestroyJavaVM)(JavaVM *vm); + jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args); + jint (JNICALL *DetachCurrentThread)(JavaVM *vm); + jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version); + jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args); +}; + +struct JavaVM_ { + const struct JNIInvokeInterface_ *functions; +}; + +/* typedefs for the direct API symbols */ +typedef jint (JNICALL *JNI_GetDefaultJavaVMInitArgs_fnptr)(void *); +typedef jint (JNICALL *JNI_CreateJavaVM_fnptr)(JavaVM **, void **, void *); +typedef jint (JNICALL *JNI_GetCreatedJavaVMs_fnptr)(JavaVM **, jsize, jsize *); + +/* actual JNI interface */ +struct JNINativeInterface_ { + void *reserved0; + void *reserved1; + void *reserved2; + void *reserved3; + jint (JNICALL *GetVersion)(JNIEnv *env); + + jclass (JNICALL *DefineClass)(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len); + jclass (JNICALL *FindClass)(JNIEnv *env, const char *name); + + jmethodID (JNICALL *FromReflectedMethod)(JNIEnv *env, jobject method); + jfieldID (JNICALL *FromReflectedField)(JNIEnv *env, jobject field); + + jobject (JNICALL *ToReflectedMethod)(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic); + + jclass (JNICALL *GetSuperclass)(JNIEnv *env, jclass sub); + jboolean (JNICALL *IsAssignableFrom)(JNIEnv *env, jclass sub, jclass sup); + + jobject (JNICALL *ToReflectedField)(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic); + + jint (JNICALL *Throw)(JNIEnv *env, jthrowable obj); + jint (JNICALL *ThrowNew)(JNIEnv *env, jclass clazz, const char *msg); + jthrowable (JNICALL *ExceptionOccurred)(JNIEnv *env); + void (JNICALL *ExceptionDescribe)(JNIEnv *env); + void (JNICALL *ExceptionClear)(JNIEnv *env); + void (JNICALL *FatalError)(JNIEnv *env, const char *msg); + + jint (JNICALL *PushLocalFrame)(JNIEnv *env, jint capacity); + jobject (JNICALL *PopLocalFrame)(JNIEnv *env, jobject result); + + jobject (JNICALL *NewGlobalRef)(JNIEnv *env, jobject lobj); + void (JNICALL *DeleteGlobalRef)(JNIEnv *env, jobject gref); + void (JNICALL *DeleteLocalRef)(JNIEnv *env, jobject obj); + jboolean (JNICALL *IsSameObject)(JNIEnv *env, jobject obj1, jobject obj2); + jobject (JNICALL *NewLocalRef)(JNIEnv *env, jobject ref); + jint (JNICALL *EnsureLocalCapacity)(JNIEnv *env, jint capacity); + + jobject (JNICALL *AllocObject)(JNIEnv *env, jclass clazz); + jobject (JNICALL *NewObject)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *NewObjectV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jobject (JNICALL *NewObjectA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jclass (JNICALL *GetObjectClass)(JNIEnv *env, jobject obj); + jboolean (JNICALL *IsInstanceOf)(JNIEnv *env, jobject obj, jclass clazz); + + jmethodID (JNICALL *GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *CallObjectMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jobject (JNICALL *CallObjectMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jobject (JNICALL *CallObjectMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jboolean (JNICALL *CallBooleanMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jboolean (JNICALL *CallBooleanMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jboolean (JNICALL *CallBooleanMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jbyte (JNICALL *CallByteMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jbyte (JNICALL *CallByteMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jbyte (JNICALL *CallByteMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jchar (JNICALL *CallCharMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jchar (JNICALL *CallCharMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jchar (JNICALL *CallCharMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jshort (JNICALL *CallShortMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jshort (JNICALL *CallShortMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jshort (JNICALL *CallShortMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jint (JNICALL *CallIntMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jint (JNICALL *CallIntMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jint (JNICALL *CallIntMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jlong (JNICALL *CallLongMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jlong (JNICALL *CallLongMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jlong (JNICALL *CallLongMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jfloat (JNICALL *CallFloatMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jfloat (JNICALL *CallFloatMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jfloat (JNICALL *CallFloatMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + jdouble (JNICALL *CallDoubleMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + jdouble (JNICALL *CallDoubleMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + jdouble (JNICALL *CallDoubleMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); + + void (JNICALL *CallVoidMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...); + void (JNICALL *CallVoidMethodV)(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); + void (JNICALL *CallVoidMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue * args); + + jobject (JNICALL *CallNonvirtualObjectMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *CallNonvirtualObjectMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jobject (JNICALL *CallNonvirtualObjectMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue * args); + + jboolean (JNICALL *CallNonvirtualBooleanMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jboolean (JNICALL *CallNonvirtualBooleanMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jboolean (JNICALL *CallNonvirtualBooleanMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue * args); + + jbyte (JNICALL *CallNonvirtualByteMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jbyte (JNICALL *CallNonvirtualByteMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jbyte (JNICALL *CallNonvirtualByteMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); + + jchar (JNICALL *CallNonvirtualCharMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jchar (JNICALL *CallNonvirtualCharMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jchar (JNICALL *CallNonvirtualCharMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); + + jshort (JNICALL *CallNonvirtualShortMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jshort (JNICALL *CallNonvirtualShortMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jshort (JNICALL *CallNonvirtualShortMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); + + jint (JNICALL *CallNonvirtualIntMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jint (JNICALL *CallNonvirtualIntMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jint (JNICALL *CallNonvirtualIntMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); + + jlong (JNICALL *CallNonvirtualLongMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jlong (JNICALL *CallNonvirtualLongMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jlong (JNICALL *CallNonvirtualLongMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); + + jfloat (JNICALL *CallNonvirtualFloatMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jfloat (JNICALL *CallNonvirtualFloatMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jfloat (JNICALL *CallNonvirtualFloatMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); + + jdouble (JNICALL *CallNonvirtualDoubleMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + jdouble (JNICALL *CallNonvirtualDoubleMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + jdouble (JNICALL *CallNonvirtualDoubleMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, + const jvalue *args); + + void (JNICALL *CallNonvirtualVoidMethod)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); + void (JNICALL *CallNonvirtualVoidMethodV)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); + void (JNICALL *CallNonvirtualVoidMethodA)(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue * args); + + jfieldID (JNICALL *GetFieldID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *GetObjectField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jboolean (JNICALL *GetBooleanField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jbyte (JNICALL *GetByteField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jchar (JNICALL *GetCharField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jshort (JNICALL *GetShortField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jint (JNICALL *GetIntField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jlong (JNICALL *GetLongField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jfloat (JNICALL *GetFloatField)(JNIEnv *env, jobject obj, jfieldID fieldID); + jdouble (JNICALL *GetDoubleField)(JNIEnv *env, jobject obj, jfieldID fieldID); + + void (JNICALL *SetObjectField)(JNIEnv *env, jobject obj, jfieldID fieldID, jobject val); + void (JNICALL *SetBooleanField)(JNIEnv *env, jobject obj, jfieldID fieldID, jboolean val); + void (JNICALL *SetByteField)(JNIEnv *env, jobject obj, jfieldID fieldID, jbyte val); + void (JNICALL *SetCharField)(JNIEnv *env, jobject obj, jfieldID fieldID, jchar val); + void (JNICALL *SetShortField)(JNIEnv *env, jobject obj, jfieldID fieldID, jshort val); + void (JNICALL *SetIntField)(JNIEnv *env, jobject obj, jfieldID fieldID, jint val); + void (JNICALL *SetLongField)(JNIEnv *env, jobject obj, jfieldID fieldID, jlong val); + void (JNICALL *SetFloatField)(JNIEnv *env, jobject obj, jfieldID fieldID, jfloat val); + void (JNICALL *SetDoubleField)(JNIEnv *env, jobject obj, jfieldID fieldID, jdouble val); + + jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); + + jobject (JNICALL *CallStaticObjectMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jobject (JNICALL *CallStaticObjectMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jobject (JNICALL *CallStaticObjectMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jboolean (JNICALL *CallStaticBooleanMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jboolean (JNICALL *CallStaticBooleanMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jboolean (JNICALL *CallStaticBooleanMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jbyte (JNICALL *CallStaticByteMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jbyte (JNICALL *CallStaticByteMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jbyte (JNICALL *CallStaticByteMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jchar (JNICALL *CallStaticCharMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jchar (JNICALL *CallStaticCharMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jchar (JNICALL *CallStaticCharMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jshort (JNICALL *CallStaticShortMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jshort (JNICALL *CallStaticShortMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jshort (JNICALL *CallStaticShortMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jint (JNICALL *CallStaticIntMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jint (JNICALL *CallStaticIntMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jint (JNICALL *CallStaticIntMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jlong (JNICALL *CallStaticLongMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jlong (JNICALL *CallStaticLongMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jlong (JNICALL *CallStaticLongMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jfloat (JNICALL *CallStaticFloatMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jfloat (JNICALL *CallStaticFloatMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jfloat (JNICALL *CallStaticFloatMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + jdouble (JNICALL *CallStaticDoubleMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...); + jdouble (JNICALL *CallStaticDoubleMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); + jdouble (JNICALL *CallStaticDoubleMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args); + + void (JNICALL *CallStaticVoidMethod)(JNIEnv *env, jclass cls, jmethodID methodID, ...); + void (JNICALL *CallStaticVoidMethodV)(JNIEnv *env, jclass cls, jmethodID methodID, va_list args); + void (JNICALL *CallStaticVoidMethodA)(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue * args); + + jfieldID (JNICALL *GetStaticFieldID)(JNIEnv *env, jclass clazz, const char *name, const char *sig); + jobject (JNICALL *GetStaticObjectField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jboolean (JNICALL *GetStaticBooleanField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jbyte (JNICALL *GetStaticByteField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jchar (JNICALL *GetStaticCharField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jshort (JNICALL *GetStaticShortField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jint (JNICALL *GetStaticIntField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jlong (JNICALL *GetStaticLongField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jfloat (JNICALL *GetStaticFloatField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + jdouble (JNICALL *GetStaticDoubleField)(JNIEnv *env, jclass clazz, jfieldID fieldID); + + void (JNICALL *SetStaticObjectField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jobject value); + void (JNICALL *SetStaticBooleanField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jboolean value); + void (JNICALL *SetStaticByteField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jbyte value); + void (JNICALL *SetStaticCharField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jchar value); + void (JNICALL *SetStaticShortField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jshort value); + void (JNICALL *SetStaticIntField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jint value); + void (JNICALL *SetStaticLongField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jlong value); + void (JNICALL *SetStaticFloatField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jfloat value); + void (JNICALL *SetStaticDoubleField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jdouble value); + + jstring (JNICALL *NewString)(JNIEnv *env, const jchar *unicode, jsize len); + jsize (JNICALL *GetStringLength)(JNIEnv *env, jstring str); + const jchar *(JNICALL *GetStringChars)(JNIEnv *env, jstring str, jboolean *isCopy); + void (JNICALL *ReleaseStringChars)(JNIEnv *env, jstring str, const jchar *chars); + + jstring (JNICALL *NewStringUTF)(JNIEnv *env, const char *utf); + jsize (JNICALL *GetStringUTFLength)(JNIEnv *env, jstring str); + const char* (JNICALL *GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy); + void (JNICALL *ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars); + + + jsize (JNICALL *GetArrayLength)(JNIEnv *env, jarray array); + + jobjectArray (JNICALL *NewObjectArray)(JNIEnv *env, jsize len, jclass clazz, jobject init); + jobject (JNICALL *GetObjectArrayElement)(JNIEnv *env, jobjectArray array, jsize index); + void (JNICALL *SetObjectArrayElement)(JNIEnv *env, jobjectArray array, jsize index, jobject val); + + jbooleanArray (JNICALL *NewBooleanArray)(JNIEnv *env, jsize len); + jbyteArray (JNICALL *NewByteArray)(JNIEnv *env, jsize len); + jcharArray (JNICALL *NewCharArray)(JNIEnv *env, jsize len); + jshortArray (JNICALL *NewShortArray)(JNIEnv *env, jsize len); + jintArray (JNICALL *NewIntArray)(JNIEnv *env, jsize len); + jlongArray (JNICALL *NewLongArray)(JNIEnv *env, jsize len); + jfloatArray (JNICALL *NewFloatArray)(JNIEnv *env, jsize len); + jdoubleArray (JNICALL *NewDoubleArray)(JNIEnv *env, jsize len); + + jboolean * (JNICALL *GetBooleanArrayElements)(JNIEnv *env, jbooleanArray array, jboolean *isCopy); + jbyte * (JNICALL *GetByteArrayElements)(JNIEnv *env, jbyteArray array, jboolean *isCopy); + jchar * (JNICALL *GetCharArrayElements)(JNIEnv *env, jcharArray array, jboolean *isCopy); + jshort * (JNICALL *GetShortArrayElements)(JNIEnv *env, jshortArray array, jboolean *isCopy); + jint * (JNICALL *GetIntArrayElements)(JNIEnv *env, jintArray array, jboolean *isCopy); + jlong * (JNICALL *GetLongArrayElements)(JNIEnv *env, jlongArray array, jboolean *isCopy); + jfloat * (JNICALL *GetFloatArrayElements)(JNIEnv *env, jfloatArray array, jboolean *isCopy); + jdouble * (JNICALL *GetDoubleArrayElements)(JNIEnv *env, jdoubleArray array, jboolean *isCopy); + + void (JNICALL *ReleaseBooleanArrayElements)(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode); + void (JNICALL *ReleaseByteArrayElements)(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode); + void (JNICALL *ReleaseCharArrayElements)(JNIEnv *env, jcharArray array, jchar *elems, jint mode); + void (JNICALL *ReleaseShortArrayElements)(JNIEnv *env, jshortArray array, jshort *elems, jint mode); + void (JNICALL *ReleaseIntArrayElements)(JNIEnv *env, jintArray array, jint *elems, jint mode); + void (JNICALL *ReleaseLongArrayElements)(JNIEnv *env, jlongArray array, jlong *elems, jint mode); + void (JNICALL *ReleaseFloatArrayElements)(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode); + void (JNICALL *ReleaseDoubleArrayElements)(JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode); + + void (JNICALL *GetBooleanArrayRegion)(JNIEnv *env, jbooleanArray array, jsize start, jsize l, jboolean *buf); + void (JNICALL *GetByteArrayRegion)(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); + void (JNICALL *GetCharArrayRegion)(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); + void (JNICALL *GetShortArrayRegion)(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); + void (JNICALL *GetIntArrayRegion)(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); + void (JNICALL *GetLongArrayRegion)(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); + void (JNICALL *GetFloatArrayRegion)(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); + void (JNICALL *GetDoubleArrayRegion)(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf); + + void (JNICALL *SetBooleanArrayRegion)(JNIEnv *env, jbooleanArray array, jsize start, jsize l, const jboolean *buf); + void (JNICALL *SetByteArrayRegion)(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte *buf); + void (JNICALL *SetCharArrayRegion)(JNIEnv *env, jcharArray array, jsize start, jsize len, const jchar *buf); + void (JNICALL *SetShortArrayRegion)(JNIEnv *env, jshortArray array, jsize start, jsize len, const jshort *buf); + void (JNICALL *SetIntArrayRegion)(JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf); + void (JNICALL *SetLongArrayRegion)(JNIEnv *env, jlongArray array, jsize start, jsize len, const jlong *buf); + void (JNICALL *SetFloatArrayRegion)(JNIEnv *env, jfloatArray array, jsize start, jsize len, const jfloat *buf); + void (JNICALL *SetDoubleArrayRegion)(JNIEnv *env, jdoubleArray array, jsize start, jsize len, const jdouble *buf); + + jint (JNICALL *RegisterNatives)(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods); + jint (JNICALL *UnregisterNatives)(JNIEnv *env, jclass clazz); + + jint (JNICALL *MonitorEnter)(JNIEnv *env, jobject obj); + jint (JNICALL *MonitorExit)(JNIEnv *env, jobject obj); + + jint (JNICALL *GetJavaVM)(JNIEnv *env, JavaVM **vm); + + /* new since JNI 1.2 (we don't really use any of those) */ + void (JNICALL *GetStringRegion)(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); + void (JNICALL *GetStringUTFRegion)(JNIEnv *env, jstring str, jsize start, jsize len, char *buf); + + void * (JNICALL *GetPrimitiveArrayCritical)(JNIEnv *env, jarray array, jboolean *isCopy); + void (JNICALL *ReleasePrimitiveArrayCritical)(JNIEnv *env, jarray array, void *carray, jint mode); + + const jchar * (JNICALL *GetStringCritical)(JNIEnv *env, jstring string, jboolean *isCopy); + void (JNICALL *ReleaseStringCritical)(JNIEnv *env, jstring string, const jchar *cstring); + + jweak (JNICALL *NewWeakGlobalRef)(JNIEnv *env, jobject obj); + void (JNICALL *DeleteWeakGlobalRef)(JNIEnv *env, jweak ref); + + jboolean (JNICALL *ExceptionCheck)(JNIEnv *env); +}; + +#endif diff --git a/src/rJava.c b/src/rJava.c index 33bd33d3..e9a3affe 100644 --- a/src/rJava.c +++ b/src/rJava.c @@ -11,6 +11,11 @@ int use_eenv = 1; /* cached environment. Do NOT use directly! Always use getJNIEnv()! */ JNIEnv *eenv; +/* quick hack - we have to get rid of R_GetCurrentEnv() soon anyway */ +#if R_VERSION < R_Version(3,6,0) +#define R_GetCurrentEnv() R_GlobalEnv +#endif + static SEXP getCurrentCall(void) { SEXP cexp, sys_calls = PROTECT(install("sys.calls")); cexp = PROTECT(lang1(sys_calls)); diff --git a/src/rJava.h b/src/rJava.h index 15c2337e..1a59a805 100644 --- a/src/rJava.h +++ b/src/rJava.h @@ -19,7 +19,7 @@ 0.2 - uses S4 classes 0.1 - first public release */ -#include +#include "djni.h" #include #include #include diff --git a/src/rjstring.h b/src/rjstring.h index 6fd24a14..b73fa634 100644 --- a/src/rjstring.h +++ b/src/rjstring.h @@ -1,7 +1,7 @@ #ifndef RJ_STRING_H__ #define RJ_STRING_H__ -#include /* for jchar */ +#include "jni.h" /* for jchar */ #include /* for SEXP */ /* --- API --- */