JNI Comment appeler Java et rappeler natif, et comment obtenir JVM std io
Je suis développeur sous Windows mais je n'utilise pas la bibliothèque Windows.
Le mode "initiative" JNI qui exécute d'abord java et utilise Systen.load()
ensuite la méthode native d'appel. Soit en mode "passif", L'exécutable crée la JVM JNI_CreateJavaVM
puis appelle la méthode java.
Maintenant, j'essaie de créer un programme C++ avec JNI SDK, donc cette chose doit être un seul exécutable, pas de dll pour System.load()
.
Écrivez d'abord un hello world :
public static native int nativeSum(int a, int b);
public static int sum(int a, int b) {
return nativeSum(a, b);
}
et courir javah Driver
a défini cet en-tête
JNIEXPORT jint JNICALL Java_Driver_nativeSum (JNIEnv *, jclass, jint, jint);
et exécutez javap -s Driver
assurez-vous d'utiliser le bon nom
public static native int nativeSum(int, int);
descriptor: (II)I
public static int sum(int, int);
descriptor: (II)I
écrire le 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;
}
Résultat:
VM created
Test-sum method id = 0x1ebf4888
Test-sum Method called res - 0
Process finished with exit code 0
Eh bien, 1 + 1 = 0, cela n'a absolument aucun sens.
Ensuite, j'essaie d'utiliser System.out/err
et de try catch
trouver les résultats mais j'obtiens le même résultat. Cette chose ne peut même pas être interceptée par une exception Java ou même C++ try catch (...)
.
public static int sum(int a, int b) {
try {
return nativeSum(a, b);
} catch (Exception exception) {
return -1;
}
}
Assurez-vous ensuite qu'il n'y a pas d'autre erreur, je contourne native :
public static int sum(int a, int b) {
return 1234;
}
Travaillant assez bien, j'ai obtenu la valeur 1234 dans la console C++.
※ Première question:
Comment obtenir le flux stdio JVM ? System.out/err.print
ne s'affichera pas dans la console "initiative". Mais DLL std print s'imprimera dans la console Java en mode "passif".
※ Deuxième question:
Que se passe-t-il dans l'appel natif ? Je ne devrais pas obtenir de résultat 0, comment y remédier ? Comment atteindre l'objectif?
BYW - n'a aucun sens mais bon essai : utiliser CallObjectMethod
donnera le même résultat, utiliser GetMethodID
renverra l'ID 0 et une longue sortie bloquée avec 1073741819 (0xC0000005).
Mise à jour 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;
J'ai cette sortie
method id = 0x1ec97350
method result = 0
Mise à jour 2 :
Pour m'assurer qu'il ne s'agit pas d'extern C ou d'autre chose, j'écris simplement le corps de la fonction en 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;
}
Et utilisez l'outil pour vous assurer que le nom de la fonction est correct

Et utilisez env->ExceptionOccurred
pour obtenir une exception, et il y en a une :
java.lang.UnsatisfiedLinkError: Driver.nativeSum(II)I
Et aucun des avec ou sans exter "C" {}
bloc ne fonctionne, tout a échoué comme résultat 0 et UnsatisfiedLinkError
.
Donc, je pense que même la fonction native requise dans le fichier exe, la jvm ne la trouve pas.
Maintenant la situation est :
Mon programme C++ est main
l'entrée, et écrivez le SDK Java pour le développeur de plugins.
Dans l'exécution, C++ crée JVM, charge la classe java, appelle la méthode java lors de l'événement, et le plug-in utilise le natif pour "faire quelque chose", alors comment faire ?
Et j'essaye aussi
public static int sum(int a, int b) {
return a + b;
}
J'en ai 2, qui fonctionnent bien. Le seul problème est l'appel Java natif.
Réponses
Pour accéder aux méthodes natives, vous devez toujours appeler System.LoadLibrary()
. La spécification explique que votre Driver.java devrait contenir :
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);
}
}
et dans votre 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;
}
Assurez-vous que l'éditeur de liens conserve à la fois Java_Driver_nativeSum
et JNI_OnLoad_driver
exporté dans votre fichier binaire.
En ce qui concerne votre première question, il n'y a pas de flux stdio JVM séparé, Java lit à partir du même fd=0
et écrit fd=1
comme tous les autres.