英文:
JNI How to call java and call back native, And how to get JVM std io
问题
#include <iostream>
#include "jni.h"
#include "Driver.h"
JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv* env, 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;
}
Update 1:
jmethodID jMethod_Driver_nativeSum = env->GetStaticMethodID(
    jClass_Driver,
    "nativeSum",
    "(II)I"
);
std::cout << "method id = " << jMethod_Driver_nativeSum << std::endl;
jint jResult_Driver_nativeSum = env->CallStaticIntMethod(
    jClass_Driver,
    jMethod_Driver_sum,
    1, 1
);
std::cout << "method result = " << jResult_Driver_nativeSum << std::endl;
Update 2:
#include <jni.h>
#include <iostream>
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;
}
And use env->ExceptionOccurred to get Exception, And there is one:
java.lang.UnsatisfiedLinkError: Driver.nativeSum(II)I
And None of with or without extern "C" {} block is working, All failed as result 0 and UnsatisfiedLinkError.
So, I think even the native required function in the exe file, The jvm can't find it.
Now the situation is:
My C++ program is main the entry, And write java SDK for plugin developer.
In runtime, C++ create JVM, Load java class, Invoke java method when event, And plugin use native to "do something", So how to ?
And I also try:
public static int sum(int a, int b) {
    return a + b;
}
I got 2, Which is working fine. Only problem is java call native.
<details>
<summary>英文:</summary>
I'm dev in windows but not using windows lib.
The "initiative" mode JNI which run java first and using `Systen.load()` then call native method.
Or "passive" mode, The executable create JVM `JNI_CreateJavaVM` then call java method.
Now, I'm trying make a C++ program with JNI SDK, So that thing must be single executable, No dll for `System.load()`.
First write a hello world:
```java
public static native int nativeSum(int a, int b);
public static int sum(int a, int b) {
    return nativeSum(a, b);
}
and run javah Driver got this header define
JNIEXPORT jint JNICALL Java_Driver_nativeSum (JNIEnv *, jclass, jint, jint);
and run javap -s Driver make sure using right name
  public static native int nativeSum(int, int);
    descriptor: (II)I
  public static int sum(int, int);
    descriptor: (II)I
write the 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;
}
Result:
VM created
Test-sum method id = 0x1ebf4888
Test-sum Method called res - 0
Process finished with exit code 0
Well, 1 + 1 = 0, That absolutely make none sense.
Then I try to using System.out/err and try catch find the issus but get same result, That thing even cannot catch by java exception or even C++ try catch (...).
public static int sum(int a, int b) {
	try {
		return nativeSum(a, b);
	} catch (Exception exception) {
		return -1;
	}
}
Then make sure not anyother mistake, I bypass native:
public static int sum(int a, int b) {
return 1234;
}
Working pretty fine, I got the 1234 value in C++ console.
※ First Question:
How to get JVM stdio stream? System.out/err.print wont show in "initiative" console.
But DLL std print will print in java console when "passive" mode.
※ Second question:
What happen in the native call? I should not get 0 result, How to fix it?
How to achieve the goal?
BYW - make no sense but nice try : using CallObjectMethod will get same result, using GetMethodID will return ID 0 and a long stuck exit with 1073741819 (0xC0000005).
Update 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;
Got this output
method id = 0x1ec97350
method result = 0
Update 2:
To make sure not extern C or thar else I just write the function body in h
#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;
}
And use tool to make sure the function name is correct
And use env->ExceptionOccurred to get Exception, And there is one:
java.lang.UnsatisfiedLinkError: Driver.nativeSum(II)I
And None of with or without exter "C" {} block is working, All failed as result 0 and  UnsatisfiedLinkError.
So, I think even the native required function in the exe file, The jvm can't find it.
Now the situation is :
My C++ program is main the entry, And write java SDK for plugin developer.
In runtime, C++ create JVM, Load java class, Invoke java method when event, And plugin use native to "do something", So how to ?
And I also try
public static int sum(int a, int b) {
    return a + b;
}
I got 2 , Which is working fine. Only problem is java call native.
答案1
得分: 1
要访问本地方法,仍然必须调用 System.LoadLibrary()。规范 解释了你的 Driver.java 应该包含以下内容:
public class Driver {
  static { System.loadLibrary("driver"); } // 必须与此名称匹配!
  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 // 此后缀必须与 Java 中使用的名称匹配
                                       (JavaVM *vm, void *reserved) {
  std::cout << "Native loaded" << std::endl;
  return JNI_VERSION_1_8;
}
确保链接器在你的二进制文件中导出 Java_Driver_nativeSum 和 JNI_OnLoad_driver 两者。
至于你的第一个问题,没有单独的 JVM 标准输入/输出流,Java 从与其他所有内容相同的 fd=0 读取,并写入相同的 fd=1。
英文:
To access native methods, you still must call System.LoadLibrary(). The spec explains that your Driver.java should look contain:
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);
}
}
and in your 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;
}
Make sure that the linker keeps both Java_Driver_nativeSum and JNI_OnLoad_driver exported in your binary.
As for your first question, there is no separate JVM stdio stream, Java reads from the same fd=0 and writes to same fd=1 as all others.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。



评论