JNI Come chiamare java e richiamare nativo e come ottenere JVM std io

Aug 18 2020

Sono dev in Windows ma non utilizzo Windows lib.

La modalità "iniziativa" JNI che esegue prima java e Systen.load()poi chiama il metodo nativo. O modalità "passiva", l'eseguibile crea JVM JNI_CreateJavaVMquindi chiama il metodo java.

Ora, sto provando a creare un programma C++ con JNI SDK, quindi quella cosa deve essere un singolo eseguibile, nessuna dll per System.load().

Per prima cosa scrivi un ciao mondo:

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

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

e run javah Driverha definito questa intestazione

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

ed esegui javap -s Driverassicurati di usare il nome giusto

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

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

scrivi il 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;

}

Risultato:

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

Process finished with exit code 0

Beh, 1 + 1 = 0, non ha assolutamente senso.

Quindi provo a usare System.out/erre try catchtrovare l'issus ma ottengo lo stesso risultato, quella cosa non può nemmeno catturare l'eccezione java o anche C++ try catch (...).

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

Quindi assicurati di non commettere altri errori, ignoro native:

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

Funzionando abbastanza bene, ho ottenuto il valore 1234 nella console C++.

※ Prima domanda:

Come ottenere il flusso stdio JVM? System.out/err.printnon verrà mostrato nella console "iniziativa". Ma DLL std print verrà stampato nella console java in modalità "passiva".

※ Seconda domanda:

Cosa succede nella chiamata nativa? Non dovrei ottenere 0 risultati, come risolverlo? Come raggiungere l'obiettivo?

BYW - non ha senso ma bel tentativo: using CallObjectMethodotterrà lo stesso risultato, using GetMethodIDrestituirà ID 0 e un'uscita lungamente bloccata con 1073741819 (0xC0000005).

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

Ho ottenuto questo risultato

method id = 0x1ec97350
method result = 0

Aggiornamento 2:

Per essere sicuro che non sia esterno a C o altro scrivo solo il corpo della funzione 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;
}

E usa lo strumento per assicurarti che il nome della funzione sia corretto

E usa env->ExceptionOccurredper ottenere l'eccezione, e ce n'è una:

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

E nessuno con o senza exter "C" {}blocco funziona, tutti falliti come risultato 0 e UnsatisfiedLinkError.

Quindi, penso che anche la funzione nativa richiesta nel file exe, JVM non riesca a trovarla.

Ora la situazione è:

Il mio programma C ++ è mainla voce e scrive Java SDK per lo sviluppatore di plug-in.

In runtime, C ++ crea JVM, carica la classe java, richiama il metodo java quando l'evento e usa il plug-in nativo per "fare qualcosa", quindi come fare?

E ci provo anche

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

Ne ho 2, che funziona bene. L'unico problema è la chiamata Java nativa.

Risposte

AlexCohn Aug 19 2020 at 14:16

Per accedere ai metodi nativi, devi comunque chiamare System.LoadLibrary(). La specifica spiega che il tuo Driver.java dovrebbe contenere:

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

e nel tuo 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;
}

Assicurati che il linker mantenga entrambi Java_Driver_nativeSumed JNI_OnLoad_driveresportati nel tuo file binario.

Per quanto riguarda la tua prima domanda, non esiste un flusso stdio JVM separato, Java legge dallo stesso fd=0e scrive allo stesso fd=1modo di tutti gli altri.