JNI Come chiamare java e richiamare nativo e come ottenere JVM std io
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_CreateJavaVM
quindi 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 Driver
ha definito questa intestazione
JNIEXPORT jint JNICALL Java_Driver_nativeSum (JNIEnv *, jclass, jint, jint);
ed esegui javap -s Driver
assicurati 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/err
e try catch
trovare 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.print
non 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 CallObjectMethod
otterrà lo stesso risultato, using GetMethodID
restituirà 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->ExceptionOccurred
per 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 ++ è main
la 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
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_nativeSum
ed JNI_OnLoad_driver
esportati nel tuo file binario.
Per quanto riguarda la tua prima domanda, non esiste un flusso stdio JVM separato, Java legge dallo stesso fd=0
e scrive allo stesso fd=1
modo di tutti gli altri.