简体   繁体   中英

When invoking JavaVM from C++ (using Cygwin) how can I load a JAR?

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:

  1. Remove the -D__int64=int64_t from g++ command line

  2. Add the header file jni_local.h

  3. 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM