I'm writing an application in C++. I want to start JVM and call Java methods from this application. I'm running on Windows 10 using Cygwin.
According to the Java Invocation API page I believe I'm invoking JVM correctly; however, when I print the System Property "java.class.path" it shows as empty.
If I copy the class file to $PWD/main/MyClass.class
everything works, but if I package up this class file into $PWD/main.jar
and try to reference that the same way, then I get:
java.lang.NoClassDefFoundError: main/MyClass
Caused by: java.lang.ClassNotFoundException: main.MyClass
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
The output of the following program is:
Value of 'java/lang/System.getProperty(java.class.path)' is ''.
Value of 'java/lang/System.getProperty(user.dir)' is 'C:\Users\admin\java_from_cpp_stack\bin'.
Failed to find class 'main/MyClass'.
Here is the C++ code:
#include <jni.h>
#include <iostream>
bool
printSysemProperty(JNIEnv* env, std::string property)
{
std::string className = "java/lang/System";
jclass cls = env->FindClass(className.c_str());
if (cls == 0) {
std::cout << "Failed to find class '" << className.c_str() << "'.\n";
return false;
}
std::string methodName = "getProperty";
jmethodID mid = env->GetStaticMethodID(cls, methodName.c_str(), "(Ljava/lang/String;)Ljava/lang/String;");
if (mid == 0) {
std::cout << "Failed to find method '" << className.c_str() << "." << methodName.c_str() << "'.\n";
return false;
}
jstring cp = env->NewStringUTF(property.c_str());
jstring val = (jstring)env->CallStaticObjectMethod(cls, mid, (jstring)cp);
const char* valCStr = env->GetStringUTFChars(val, JNI_FALSE);
std::cout << "Value of '" << className.c_str() << "." << methodName.c_str() << "(" << property.c_str() << ")' is '" << (char*)valCStr << "'.\n";
return true;
}
int
main(int argc, char **argv)
{
// Create JVM
JavaVM* vm;
JNIEnv* env;
JavaVMInitArgs vm_args;
JavaVMOption* options = new JavaVMOption[1];
//options[0].optionString = (char*)"-Djava.class.path=.";
//options[0].optionString = (char*)"-Djava.class.path=C:\\Users\\admin\\java_from_cpp_stack\\bin\\main.jar";
//options[0].optionString = (char*)"-Djava.class.path=/cygdrive/c/Users/admin/java_from_cpp_stack/bin/main.jar";
options[0].optionString = (char*)"-Djava.class.path=main.jar";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
int ret = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args);
delete options;
if (ret != 0)
std::cerr << "Failed to create JVM.\n";
// Print System Properties
bool rc = true;
rc &= printSysemProperty( env, "java.class.path");
rc &= printSysemProperty( env, "user.dir");
if (!rc) {
std::cout << "Printing system properties failed.\n";
return rc;
}
// Call Static Method from JAR
std::string className = "main/MyClass";
jclass cls = env->FindClass(className.c_str());
if (cls == 0) {
std::cout << "Failed to find class '" << className.c_str() << "'.\n";
return -3;
}
jmethodID mid = env->GetStaticMethodID(cls, "hello", "()V");
if (mid == 0) {
std::cout << "Failed to find method '" << className.c_str() << ".hello'.\n";
return -4;
}
std::cout << "Calling '" << className.c_str() << ".main'.\n";
env->CallStaticVoidMethod(cls, mid);
// Clean up
vm->DestroyJavaVM();
std::cout << "Complete.\n";
return 0;
}
Here is the Java code:
package main;
public class MyClass {
public MyClass() {
}
public static void hello() {
System.out.println("From Java: Hello World!");
}
}
Here is a BASH script I use to compile:
#!/bin/bash
set -e
set -x
rm -rf ./build
mkdir ./build
rm -rf ./bin/
mkdir ./bin/
g++ \
-o ./bin/myprog \
./src/cpp/myprog.c \
-D__int64=int64_t \
-L/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/jre/bin/server/ \
-ljvm \
-I/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/include \
-I/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/include/win32
/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/bin/javac \
-Xlint:all \
-Werror \
-g \
-verbose \
-cp ./build \
-sourcepath ./src/java \
-d ./build \
./src/java/main/MyClass.java
/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/bin/jar \
cvf ./bin/main.jar \
-C ./build \
main/MyClass.class
#mkdir ./bin/main
#cp ./build/main/MyClass.class ./bin/main
cd ./bin
PATH=/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/jre/bin/server/:/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/bin/:$PATH ./myprog
It turns out that the solution is posted here: JNI JVM Invocation Classpath
On x86-64, the Oracle Windows JDK headers define jint as long. This is 32 bits with Microsoft compilers (which the Oracle JDK is written for) but 64 bits with Cygwin gcc. Since
JavaVMInitArgs
contains some fields of this type, its binary layout is changed by this discrepancy.
Also see: Error when compiling in cygwin
My fixes were:
Remove the -D__int64=int64_t
from g++
command line
Add the header file jni_local.h
Then JavaVMInitArgs
were recognized.
The src/cpp/jni_local.h
file contents are:
#include "stdint.h"
#define __int64 int64_t
#define long int32_t
#include "jni_md.h"
#undef long
#include_next "jni.h"
The src/cpp/myprog.c
was reduced to:
#include "jni_local.h"
#include <iostream>
int
main(int argc, char **argv)
{
// Create JVM
JavaVM* vm;
JNIEnv* env;
JavaVMInitArgs vm_args;
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = (char*)"-Djava.class.path=myPackage.jar";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
int ret = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args);
delete options;
if (ret != 0) {
std::cerr << "Failed to create JVM.\n";
return ret;
}
// Call Static Method from JAR
std::string className = "myPackage/MyClass";
jclass cls = env->FindClass(className.c_str());
if (cls == 0) {
std::cerr << "Failed to find class '" << className.c_str() << "'.\n";
return -3;
}
jmethodID mid = env->GetStaticMethodID(cls, "hello", "()V");
if (mid == 0) {
std::cerr << "Failed to find method '" << className.c_str() << ".hello'.\n";
return -4;
}
std::cerr << "Calling 'myPackage." << className.c_str() << ".hello()'\n";
env->CallStaticVoidMethod(cls, mid);
// Clean up
vm->DestroyJavaVM();
std::cerr << "Complete.\n";
return 0;
}
The src/java/myPackage/MyClass.java
was not changed.
The run.sh
file was updated to have:
#!/bin/bash
set -e
set -x
rm -rf ./build
mkdir ./build
rm -rf ./bin/
mkdir ./bin/
g++ \
-o ./bin/myprog \
./src/cpp/myprog.c \
-L/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/jre/bin/server/ \
-ljvm \
-Isrc/cpp \
-I/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/include \
-I/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/include/win32
/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/bin/javac \
-Xlint:all \
-Werror \
-g \
-verbose \
-cp ./build \
-sourcepath ./src/java \
-d ./build \
./src/java/myPackage/MyClass.java
/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/bin/jar \
cvf ./bin/myPackage.jar \
-C ./build \
myPackage/MyClass.class
/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/bin/jar tf ./bin/myPackage.jar
cd ./bin
PATH=/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/jre/bin/server/:/cygdrive/c/Program\ Files/Java/jdk1.8.0_131/bin/:$PATH ./myprog