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

1

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:

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

Dependencies tool

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.

java
c++
java-native-interface
asked on Stack Overflow Aug 18, 2020 by Alceatraz • edited Aug 19, 2020 by Alceatraz

1 Answer

0

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.

answered on Stack Overflow Aug 19, 2020 by Alex Cohn

User contributions licensed under CC BY-SA 3.0