JNI Comment appeler Java et rappeler natif, et comment obtenir JVM std io

Aug 18 2020

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_CreateJavaVMpuis 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 Drivera défini cet en-tête

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

et exécutez javap -s Driverassurez-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/erret de try catchtrouver 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.printne 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 CallObjectMethoddonnera le même résultat, utiliser GetMethodIDrenverra 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->ExceptionOccurredpour 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 mainl'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

AlexCohn Aug 19 2020 at 14:16

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_nativeSumet JNI_OnLoad_driverexporté 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=0et écrit fd=1comme tous les autres.