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

Aug 18 2020

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

O zaman başka bir hata olmadığından emin olun, yerliyi atlıyorum

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

Oldukça iyi çalışıyor, C ++ konsolunda 1234 değerini aldım.

※ İlk soru:

JVM standart akışı nasıl elde edilir? System.out/err.print"girişim" konsolunda gösterilmeyecek. Ancak DLL standart yazdırma, "pasif" moddayken java konsolunda yazdırılacaktır.

※ İkinci soru:

Yerel aramada ne olur? 0 sonuç almamalıyım, nasıl düzeltilir? Hedefe nasıl ulaşılır?

BYW - mantıklı değil ama güzel deneyin: kullanmak CallObjectMethodaynı sonucu GetMethodIDalacak , kullanmak ID 0 ve 1073741819 (0xC0000005) ile uzun bir takılı çıkış döndürecektir.

Güncelleme 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;

Bu çıktıyı aldım

method id = 0x1ec97350
method result = 0

Güncelleme 2:

C veya başka bir şeyin dışında olmadığından emin olmak için işlev gövdesini h olarak yazıyorum

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

Ve işlev adının doğru olduğundan emin olmak için aracı kullanın

env->ExceptionOccurredİstisna almak için kullanın Ve bir tane var:

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.

Yanıtlar

AlexCohn Aug 19 2020 at 14:16

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.