JNI如何调用Java并调用本地代码,以及如何获取JVM标准输入输出。

huangapple go评论76阅读模式
英文:

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&#39;m dev in windows but not using windows lib.

The &quot;initiative&quot; mode JNI which run java first and using `Systen.load()` then call native method.
Or &quot;passive&quot; mode, The executable create JVM `JNI_CreateJavaVM` then call java method.

Now, I&#39;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 &lt;iostream&gt;
#include &quot;jni.h&quot;
#include &quot;Driver.h&quot;


JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv*, jclass, jint a, jint b) {
    std::cout &lt;&lt; &quot;Native invoked &quot; &lt;&lt; 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 = &quot;-Djava.class.path=../class/out/production/class&quot;;
	
    options[0].optionString = const_cast&lt;char*&gt;(cmd.c_str());
	
    vm_args.options = options;
	
    JavaVM* jvm;
    JNIEnv* env;
	
    jint rc = JNI_CreateJavaVM(&amp;jvm, (void**) &amp;env, &amp;vm_args);

    delete[] options;


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

    _jclass* jClass_Driver = env-&gt;FindClass(&quot;Driver&quot;);

    _jmethodID* jMethod_Driver_sum = env-&gt;GetStaticMethodID(
            jClass_Driver,
            &quot;sum&quot;,
            &quot;(II)I&quot;
    );

    std::cout &lt;&lt; &quot;Test-sum method id = &quot; &lt;&lt; jMethod_Driver_sum &lt;&lt; std::endl;

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

    std::cout &lt;&lt; &quot;Test-sum Method called res - &quot;
              &lt;&lt; jResult_Driver_sum
              &lt;&lt; std::endl;


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



    jvm-&gt;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-&gt;GetStaticMethodID(
	jClass_Driver,
	&quot;nativeSum&quot;,
	&quot;(II)I&quot;
);

std::cout &lt;&lt; jMethod_Driver_nativeSum &lt;&lt; std::endl;

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

std::cout &lt;&lt; jResult_Driver_nativeSum &lt;&lt; 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 &lt;jni.h&gt;

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

And use tool to make sure the function name is correct

JNI如何调用Java并调用本地代码,以及如何获取JVM标准输入输出。

And use env-&gt;ExceptionOccurred to get Exception, And there is one:

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

And None of with or without exter &quot;C&quot; {} 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_nativeSumJNI_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(&quot;driver&quot;); } // 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 &quot;C&quot; JNIEXPORT jint JNICALL Java_Driver_nativeSum(JNIEnv*, jclass, jint a, jint b) {
std::cout &lt;&lt; &quot;Native invoked &quot; &lt;&lt; std::endl;
return a + b;
}
extern &quot;C&quot; JNIEXPORT jint JNI_OnLoad_driver // this suffix must match the name used in Java
(JavaVM *vm, void *reserved) {
std::cout &lt;&lt; &quot;Native loaded&quot; &lt;&lt; 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.

huangapple
  • 本文由 发表于 2020年8月18日 16:34:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/63464804.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定