When I create a Java 8 JVM in C++ I usually use something like the following code to tell JVM the class path:
JavaVMOption* options = new JavaVMOption[1]; // JVM invocation options
options[0].optionString = (char *)"-Djava.class.path=.;./lib2"; // where to find java .class
vm_args.version = JNI_VERSION_1.8; // minimum Java version
vm_args.nOptions = 1; // number of options
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
But how to tell the Java 9 JVM about the module path? There is no java.module.path system property. The best I can find is something like:
JavaVMOption* options = new JavaVMOption[2]; // JVM invocation options
options[0].optionString = (char *)"-Djdk.module.path=.;./lib2"; // where to find java .class
options[1].optionString = (char *)"-Djdk.module.main=RemkaAgentService"; // where to find java .class
vm_args.version = JNI_VERSION_9; // minimum Java version
vm_args.nOptions = 2; // number of options
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
But this code does not work, it fails when I try to create JVM. I suppose it is because it does not support the options I try.
This is a late answer but persons that make searches about this problem may find it useful. There is indeed very few documentation to address this case, which is unfortunate.
I spent some time to investigate this problem and I found a way to make it work. Here are the steps to take.
Bundle your application packages within a custom JRE by using the 'jlink' tool provided by the JDK: https://docs.oracle.com/en/java/javase/11/tools/jlink.html
Use the class loader to find your main class. To do this in JNI, you can use the "FindClass()" function : https://docs.oracle.com/en/java/javase/11/docs/specs/jni/functions.html#findclass
The first step removes the requirement to define the '--module-path' flag that is not supported by JNI. Note that to properly bundle your application, all you jars must be converted to modular jars. Third-party jars may not be true modular jars but rather automatic modules, but automatic modules are not supported by jlink (Using jlink with automatic modules). Fortunately, there is a method to perform this automatically documented here : creating module-info for automatic modules with jdeps in java 9
The second step works even if your class lies within a module. Notes that the class name parameter for a class 'p1.p2.p3.MyClass' must be 'p1/p2/p3/MyClass'. I mentioned this as I wasted some time due to this difference in the convention.
I may publish an example if I can find some time for this but it may not happens in a short time-frame.
So the right options are the following
JavaVMOption* options = new JavaVMOption[2]; // JVM invocation options
options[0].optionString = (char *)"--module-path=.;./lib2"; // where to find java .class
options[1].optionString = (char *)"--add-modules=RemkaAgentService,spring.context";
vm_args.version = JNI_VERSION_9; // minimum Java version
vm_args.nOptions = 2; // number of options
vm_args.options = options;
vm_args.ignoreUnrecognized = false; // invalid options make the JVM init fail
Related
JNI_CreateJavaVM function method does not work and cannot be debugged.
The development environment is win10 x64, jdk version is 1.8
Visual studio 2017 Community Edition Writing a C++ project
I am learning about JNI. I am trying to run The Invocation API. The following URL is an example of the official website documentation.
Click here!
I built the project and added a project dependency that contains jvm.lib. And I put jvm.dll in the project directory. I successfully run this program.
Main.test() is a method of print hello world. But the program exits when executing JNI_CreateJavaVM, the console shows that the return value is 1.
I can't get into debugging, I don't know what happened.
#include <jni.h>
int main() {
printf("begin..........\n");
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
char optionString[] = "-Djava.class.path =D:/Program Files/Java/jdk1.8.0_191/lib/";
options[0].optionString = optionString;
vm_args.version = JNI_VERSION_1_8;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
int res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
printf("result=%d", res);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();
return 0;
}
I expect this jvm can be called, but it is forced to exit when the program is executed to `int res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
Where am I wrong? Why is it not working?
Exit screenshot
enter image description here
jvm.dll location
enter image description here
jvm.lib linker additional library directories
enter image description here
jvm.lib linker additional Dependencies
enter image description here
Classpath
There is a space between key and equal sign. The space must be removed.
char optionString[] = "-Djava.class.path =D:/Program Files/Java/jdk1.8.0_191/lib/";
Classpath value
The java.class.path value must point to the base directory where your compiled .class files are located.
It looks like you're not using a package name, then it's the directory where Main.class is located, so probably it should look something like this:
char optionString[] = "-Djava.class.path=c:/Users/name/MyJavaPrograms/classes";
Access violation
SEGV (or exception 0xC0000005) is also generated intentionally on JVM startup to verify certain CPU/OS features.
see this fine answer: https://stackoverflow.com/a/36258856
In Visual Studio, when the exception dialog is shown, simply turn off that it breaks there. This will prevent you from seeing it again the next time you start the program again.
Java
Just for the sake of completeness: the Java method should look like this:
public class Main {
public static void test(int num) {
System.out.println("Java: test called with '" + num + "'");
}
...
Visual Studio Configuration
The jvm.dll needs to be found. In Visual Studio under Configuration Properties/Debugging/Environment add PATH=%PATH%;<path-to-jdk>\bin\server
For later deployment you could think about putting the whole JRE into a subfolder of your application and reference it with a relative path.
Demo
Finally a brief demo (added a \n here printf("result=%d\n", res); to have a separate line):
I'm creating a JVM within my C++ application for windows, and I'm unable to convince it to load multiple jars.
My C++ code:
MyClass::MyClass(std::string & classPath) {
classPath = "-cp "+classPath; // <-- Won't work with any path or single jar
//classPath = "-Djava.class.path="+classPath; <-- Only works with single jar
jvmOptions[0].optionString = (char *)classPath.c_str();
jvmOptions[1].optionString = "-Xms8m";
jvmOptions[2].optionString = "-Xmx24m";
jvmArgs.version = JNI_VERSION_1_6;
jvmArgs.options = jvmOptions;
jvmArgs.nOptions = 3;
jvmArgs.ignoreUnrecognized = JNI_TRUE;
int jvmInitResult = CreateJavaVM( &jvm, (void**)&environment, &jvmArgs);
if( jvmInitResult >= 0 ) {
jclass loadedClass = environment->FindClass( MyClassName.c_str() );
.....
If I pass a path via my classPath variable to a single JAR, such as "C:\path\myjar.jar", the jclass variable is located fine. However, my Java class requires additional JARs to function, so I need to pass more than one JAR to the jvmOptions. When I try to pass the second, or third JAR, in any of the following ways, the FindClass call now fails.
C:\path\myjar.jar <--------- FindClass SUCCESS; can't use due to missing jars
C:\path\myjar.jar;C:\path\secondjar.jar <-----FindClass FAIL
C:\path\myjar.jar:C:\path\secondjar.jar <-----FindClass FAIL
C:\path\* <-----FindClass FAIL
C:\path\*.jar <-----FindClass FAIL
"C:\path\myjar.jar;C:\path\secondjar.jar" <-----FindClass FAIL
"C:\path\myjar.jar:C:\path\secondjar.jar" <-----FindClass FAIL
I assume there is another option I'm not thinking of, but this is driving me nuts.
You should use -cp to set the class path. I suspect -Djava.class.path= won't do what you think it should.
The solution is to not use windows file separators when passing the argument to the program. The \ ends up getting escape sequenced with one or more \ . Changing the argument to unix style file separators correctly loads all of the jars within a directory.
eg:
MyApp "classpath"
MyApp C:\pathtojars\ <-- fails
MyApp C:/pathtojars/ <-- works
Fixed code:
MyClass::MyClass(std::string & classPath )
{
classPath = "-Djava.class.path="+classPath;
jvmOptions[0].optionString = (char *)classPath.c_str();
jvmOptions[1].optionString = "-Xms8m";
jvmOptions[2].optionString = "-Xmx24m";
jvmArgs.version = JNI_VERSION_1_6;
jvmArgs.options = jvmOptions;
jvmArgs.nOptions = 3;
jvmArgs.ignoreUnrecognized = JNI_TRUE;
int jvmInitResult = CreateJavaVM( &jvm, (void**)&environment, &jvmArgs);
if( jvmInitResult >= 0 )
{
jclass loadedClass = environment->FindClass( MyClassName.c_str() );
.....
I'm trying to create JVM usin JNI. I'm using win 7 64 bits OS. On line JNI_CreateJavaVM my program crashes. I decided to compile my program using 64 bit compiler and got following error:
Error 1 error LNK2001: unresolved external symbol __imp_JNI_CreateJavaVM
Where is the point I should start to look for linking problem and why my program crashes in 32 bit mode?
void createJVM()
{
JavaVMInitArgs vm_args;
JavaVMOption options[4];
int n = 0;
char * str;
str= new char[1000];
sprintf(str, "-Djava.class.path=%S\\tst.jar", myPath);
options[n++].optionString = str;
str= new char[1000];
sprintf(str, "-Djava.library.path=%S\\lib;%S", myPath, myPath);
options[n++].optionString = str;
str= new char[1000];
sprintf(str, "-Duser.dir=%S", myPath);
options[n++].optionString = str;
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = n;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
}
have you added 'jvm.lib' as Additional Dependency in your project?
furthermore, you need to specify the location of jvm.lib in Additional Library Directories...
also please note that for 64-bit application, you would need to point to the 64-bit library, otherwise linker won't link
You can find those settings in the Configuration Properties->Linker area.
hope this information helps you out.
Cheers,
Since I can't wote up (still less than 15 reputation) I just want to confirm that Naytzyrhc solution worked for me.
Just to clarify it a bit more, in Visual Studio Express 2013 (v12) you should go to:
Project -> [YourProjectName] Properties... -> Linker -> General -> Additional Library Directories
for adding lib folder to additional library directories, and:
Project -> [YourProjectName] Properties... -> Linker -> Input -> Additional Dependencies
for adding jvm.lib to additional dependencies.
There are lots of examples that Android C/C++ native calls Java APIs.
However, all of these examples I have read are the Android Java APIs call native
first and then the native calls other Java APIs by using the passed JNI-ENV.
Without the passed JNI-ENV, how can the C/C++ get it?
Is it possible that C/C++ native calls Java APIs without JNI-ENV?
Can you give an example or a link for it if it is possible?
Thanks!
You need to include jni.h first. This brings a ton of useful calls; with newer android releases you'll also need JniInvocation.h. To enable this:
LOCAL_C_INCLUDES += ${JNI_H_INCLUDE}
That certainly works with source tree, not sure about NDK, but should be fine, too.
Second, pretty important thing is to have proper signal chain lib selected. Art or Dalvik will load libsigchain.so, which is a stub and abort()s every time any of its methods are being called. On Android it's done with a little hack: local symbols are being exported to global symbol table, so that Art picks exec's symbols instead of loading shared lib. Here's how it's done:
# Enable native helper calls
LOCAL_SHARED_LIBRARIES += libnativehelper
# Include all of the Android's libsigchain symbols
LOCAL_WHOLE_STATIC_LIBRARIES += libsigchain
# Export only libsigchain symbols to global symbol table.
LOCAL_LDFLAGS += \
-Wl,--export-dynamic \
-Wl,--version-script,art/sigchainlib/version-script.txt
Link your executable now. Double check that the required symbols are indeed exported:
% readelf -a <output_binary> | grep InitializeSignalChain
654: 0002cab1 211 FUNC GLOBAL PROTECTED 12 InitializeSignalChain
Done? things get simpler now:
Initialize the JNI (so that your code uses proper VM)
JniInvocation invocation;
if (invocation.Init(nullptr)) return;
Create JavaVM:
JavaVM* vm;
JNIEnv* env;
JavaVMInitArgs* args;
args.version = JNI_VERSION_1_4; // _5, _6
args.options = nullptr;
args.nOptions = 0;
args.ignoreUnrecognized = JNI_FALSE;
if (JNI_CreateJavaVM(&vm, &env, &args) < 0) return;
At this point your vm and env are ready to use. have fun.
Check for exceptions, if any
if (env->ExceptionCheck()) {
// ...
}
When you're done, clean up
vm->DetachCurrentThread();
vm->DestroyJavaVM();
More interesting stuff can be found here and DalvikVM Main is probably the best source of knowledge. Good luck!
Is it possible to create a JVM from within a JNI method using the JNI API?
I've tried to do this using the JNI function "JNI_CreateJavaVM()", but it's not working (the function keeps returning a value less than zero).
Here is the basic code I'm using (C++):
JNIEnv *env;
JavaVM *jvm;
jint res;
#ifdef JNI_VERSION_1_2
JavaVMInitArgs vm_args;
JavaVMOption options[2];
options[0].optionString =
"-Djava.class.path=" USER_CLASSPATH;
options[1].optionString = "-verbose:jni";
vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 2;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */
Where USER_CLASSPATH contains the path to the classes I want to load. After the above code executes, res < 0, indicating that JNI_CreateJavaVM() failed. The code above is part of a native method written in C++ called from Java. Any ideas on how to get this to work?
Thanks.
No, you can't. It's a documented restriction that you can only have one JVM at a time. The API is designed for the possibility of extension, but the extension has never happened.
If you are in a JNI method, then there is already one JVM, and one JVM per process is all you get.
I see what you mean:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4479303
The bug report says it's not possible to run multiple JVMs in the same address space. I have to say I'm a little surprised that JNI_CreateJavaVM() doesn't fork off a new JVM in a different address space.
Since JNI_CreateJavaVM() doesn't fork a new process itself, is it possible to manually fork off another JVM process from within a JNI method and subsequently use IPC to manage it? If so, what's the best way to do this? A literal fork()/exec() doesn't seem like a good idea because it would copy the entire (probably very large) address space of the JVM only to throw it away immediately afterward.