JNI如何调用java和回调native,以及如何获取JVM std io

JNI How to call java and call back native, And how to get JVM std io

我是 windows 的开发人员,但没有使用 windows 库。

“主动”模式 JNI,首先 运行 java 并使用 Systen.load() 然后调用本地方法。 或者“被动”模式,可执行文件创建 JVM JNI_CreateJavaVM 然后调用 java 方法。

现在,我正在尝试使用 JNI SDK 制作一个 C++ 程序,因此该程序必须是单个可执行文件,System.load() 没有 dll。

先写一个hello world:

public static native int nativeSum(int a, int b);

public static int sum(int a, int b) {
    return nativeSum(a, b);
}

和运行 javah Driver得到这个header定义

JNIEXPORT jint JNICALL Java_Driver_nativeSum (JNIEnv *, jclass, jint, jint);

和运行 javap -s Driver确保使用正确的名称

  public static native int nativeSum(int, int);
    descriptor: (II)I

  public static int sum(int, int);
    descriptor: (II)I

写main.cpp

#include <iostream>
#include "jni.h"
#include "Driver.h"


JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv*, jclass, jint a, jint b) {
    std::cout << "Native invoked " << std::endl;
    return a + b;
}


int main() {
    
    JavaVMInitArgs vm_args;
    
    vm_args.version = JNI_VERSION_1_8;
    vm_args.ignoreUnrecognized = true;
    vm_args.nOptions = 1;
    
    auto* options = new JavaVMOption[1];
    
    std::string cmd = "-Djava.class.path=../class/out/production/class";
    
    options[0].optionString = const_cast<char*>(cmd.c_str());
    
    vm_args.options = options;
    
    JavaVM* jvm;
    JNIEnv* env;
    
    jint rc = JNI_CreateJavaVM(&jvm, (void**) &env, &vm_args);

    delete[] options;


    // ==========================================================

    _jclass* jClass_Driver = env->FindClass("Driver");

    _jmethodID* jMethod_Driver_sum = env->GetStaticMethodID(
            jClass_Driver,
            "sum",
            "(II)I"
    );

    std::cout << "Test-sum method id = " << jMethod_Driver_sum << std::endl;

    long jResult_Driver_sum = env->CallStaticIntMethod(
            jClass_Driver,
            jMethod_Driver_sum,
            1, 1
    );

    std::cout << "Test-sum Method called res - "
              << jResult_Driver_sum
              << std::endl;


    // ==========================================================



    jvm->DestroyJavaVM();


    return 0;

}

结果:

VM created
Test-sum method id = 0x1ebf4888
Test-sum Method called res - 0

Process finished with exit code 0

嗯,1 + 1 = 0,这绝对 none 有意义。

然后我尝试使用 System.out/errtry catch 找到问题但得到相同的结果, thing 甚至无法被 java 捕获异常甚至 C++ try catch (...).

public static int sum(int a, int b) {
    try {
        return nativeSum(a, b);
    } catch (Exception exception) {
        return -1;
    }
}

然后确保没有其他错误,我绕过原生:

public static int sum(int a, int b) {
    return 1234;
}

工作得很好,我在 C++ 控制台中得到了 1234 值。

※ 第一题:

如何获取JVM stdio流? System.out/err.print 不会显示在“主动”控制台中。 但是当“被动”模式时,DLL 标准打印将在 java 控制台中打印。

※ 第二题:

本机调用中发生了什么?我不应该得到 0 结果,如何解决? 如何实现目标?

BYW - 没有任何意义,但很好的尝试:使用 CallObjectMethod 将得到相同的结果,使用 GetMethodID 将 return ID 0 和 1073741819 (0xC0000005) 的长时间卡住退出。

更新 1:

jmethodID jMethod_Driver_nativeSum = env->GetStaticMethodID(
    jClass_Driver,
    "nativeSum",
    "(II)I"
);

std::cout << jMethod_Driver_nativeSum << std::endl;

jint jResult_Driver_nativeSum = env->CallStaticIntMethod(
    jClass_Driver,
    jMethod_Driver_sum,
    1, 1
);

std::cout << jResult_Driver_nativeSum << std::endl;

得到这个输出

method id = 0x1ec97350
method result = 0

更新 2:

为了确保不是 extern C 或 thar else 我只是在 h

中写了函数 body
#include <jni.h>

/*
 * Class:     Driver
 * Method:    nativeSum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv* env, jclass cls, jint a, jint b) {
    std::cout << "Java_Driver_nativeSum invoked" << std::endl;
    return a + b;
}

并使用工具确认函数名是否正确

并使用 env->ExceptionOccurred 获取异常,并且有一个:

java.lang.UnsatisfiedLinkError: Driver.nativeSum(II)I

和 None 有或没有 exter "C" {} 块正在工作,所有失败结果为 0 和 UnsatisfiedLinkError

所以,我觉得连exe文件中native需要的函数,jvm也找不到。

现在的情况是:

我的C++程序是main入门,并为插件开发者编写javaSDK。

在运行时间,C++创建JVM,加载javaclass,在事件发生时调用java方法,插件使用native来“做某事”,所以如何?

我也试试

public static int sum(int a, int b) {
    return a + b;
}

我有 2 个,工作正常。唯一的问题是 java 调用本机。

要访问本机方法,您仍然必须调用 System.LoadLibrary()spec 说明您的 Driver.java 应该包含:

public class Driver {
  static { System.loadLibrary("driver"); } // this name must be matched!
  public static native int nativeSum(int a, int b);

  public static int sum(int a, int b) {
    return nativeSum(a, b);
  }
}

在你的main.cpp,

extern "C" JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv*, jclass, jint a, jint b) {
  std::cout << "Native invoked " << std::endl;
  return a + b;
}

extern "C" JNIEXPORT jint JNI_OnLoad_driver // this suffix must match the name used in Java
                                           (JavaVM *vm, void *reserved) {
  std::cout << "Native loaded" << std::endl;
  return JNI_VERSION_1_8;
}

确保链接器在您的二进制文件中同时导出 Java_Driver_nativeSumJNI_OnLoad_driver

关于您的第一个问题,没有单独的 JVM stdio 流,Java 从相同的 fd=0 读取并写入与所有其他相同的 fd=1