从 C++(使用 Cygwin)调用 JavaVM 时,如何加载 JAR?

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

我正在用 C++ 编写应用程序。我想启动 JVM 并从此应用程序调用 Java 方法。我在 运行 Windows 10 上使用 Cygwin。

根据 Java Invocation API 页面,我相信我正确地调用了 JVM;但是,当我打印系统 属性 "java.class.path" 时,它显示为空。

如果我将 class 文件复制到 $PWD/main/MyClass.class 一切正常,但如果我将此 class 文件打包到 $PWD/main.jar 并尝试以相同的方式引用它,然后我得到:

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)

以下程序的输出是:

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

这是 C++ 代码:

#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;

}

这里是 Java 代码:

package main;

public class MyClass {
  public MyClass() {
  }

  public static void hello() {
    System.out.println("From Java: Hello World!");
  }
}

这是我用来编译的BASH脚本:

#!/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

原来这里贴出了解决方案: 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.

另见: Error when compiling in cygwin

我的修复是:

  1. g++ 命令行中删除 -D__int64=int64_t

  2. 添加头文件jni_local.h

  3. 然后JavaVMInitArgs被认出来了

src/cpp/jni_local.h文件内容为:

#include "stdint.h"

#define __int64 int64_t
#define long int32_t
#include "jni_md.h"
#undef long

#include_next "jni.h"

src/cpp/myprog.c 缩减为:

#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;
}

src/java/myPackage/MyClass.java没有改变。

run.sh 文件已更新为:

#!/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