JNI Bagaimana memanggil java dan memanggil kembali native, Dan bagaimana mendapatkan JVM std io

Aug 18 2020

Saya dev di windows tetapi tidak menggunakan windows lib.

Mode "inisiatif" JNI yang menjalankan java terlebih dahulu dan Systen.load()kemudian memanggil metode native. Atau mode "pasif", Eksekusi buat JVM JNI_CreateJavaVMlalu panggil metode java.

Sekarang, saya mencoba membuat program C ++ dengan JNI SDK, Jadi hal itu harus dapat dieksekusi tunggal, Tidak ada dll untuk System.load().

Pertama tulis hello world:

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

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

dan jalankan javah Driverheader ini yang ditentukan

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

dan jalankan javap -s Driverpastikan menggunakan nama yang benar

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

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

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

}

Hasil:

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

Process finished with exit code 0

Nah, 1 + 1 = 0, Itu sama sekali tidak masuk akal.

Kemudian saya mencoba untuk menggunakan System.out/errdan try catchmenemukan issusnya tetapi mendapatkan hasil yang sama, bahkan hal itu tidak dapat ditangkap oleh java exception atau bahkan C ++ try catch (...).

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

Kemudian pastikan tidak ada kesalahan lain, saya melewati native:

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

Bekerja dengan cukup baik, saya mendapatkan nilai 1234 di konsol C ++.

※ Pertanyaan pertama:

Bagaimana cara mendapatkan streaming JVM stdio? System.out/err.printtidak akan ditampilkan di konsol "inisiatif". Tapi DLL std print akan mencetak di konsol java saat mode "pasif".

※ Pertanyaan kedua:

Apa yang terjadi dengan panggilan asli? Saya tidak mendapatkan hasil 0, Bagaimana cara memperbaikinya? Bagaimana cara mencapai tujuan tersebut?

BYW - tidak masuk akal tetapi percobaan yang bagus: menggunakan CallObjectMethodakan mendapatkan hasil yang sama, menggunakan GetMethodIDakan mengembalikan ID 0 dan keluar macet lama dengan 1073741819 (0xC0000005).

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

Punya keluaran ini

method id = 0x1ec97350
method result = 0

Perbarui 2:

Untuk memastikan tidak ekstern C atau lainya saya tulis saja function body di 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;
}

Dan gunakan alat untuk memastikan nama fungsi sudah benar

Dan gunakan env->ExceptionOccurreduntuk mendapatkan Pengecualian, Dan ada satu:

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

Dan Tak satu pun dari dengan atau tanpa exter "C" {}blok bekerja, Semua gagal sebagai hasil 0 dan UnsatisfiedLinkError.

Jadi, saya pikir bahkan fungsi yang diperlukan asli dalam file exe, jvm tidak dapat menemukannya.

Sekarang situasinya adalah:

Program C ++ saya adalah mainentri, Dan tulis java SDK untuk pengembang plugin.

Dalam runtime, C ++ membuat JVM, Memuat kelas java, Memanggil metode java saat acara, Dan plugin menggunakan native untuk "melakukan sesuatu", Jadi bagaimana caranya?

Dan saya juga mencoba

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

Saya mendapat 2, Yang berfungsi dengan baik. Satu-satunya masalah adalah panggilan java asli.

Jawaban

AlexCohn Aug 19 2020 at 14:16

Untuk mengakses metode asli, Anda masih harus memanggil System.LoadLibrary(). The spek menjelaskan bahwa Anda Driver.java harus melihat mengandung:

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

dan di main.cpp Anda ,

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

Pastikan linker menyimpan keduanya Java_Driver_nativeSumdan JNI_OnLoad_driverdiekspor ke biner Anda.

Adapun pertanyaan pertama Anda, tidak ada aliran stdio JVM yang terpisah, Java membaca dari yang sama fd=0dan menulis sama fd=1seperti yang lainnya.