JNI Cómo llamar a Java y devolver la llamada nativa, y cómo obtener JVM std io

Aug 18 2020

Soy desarrollador en Windows pero no uso Windows lib.

El modo "iniciativo" JNI que ejecuta Java primero y Systen.load()luego llama al método nativo. O modo "pasivo", el ejecutable crea JVM JNI_CreateJavaVMy luego llama al método java.

Ahora, estoy tratando de hacer un programa C ++ con JNI SDK, por lo que debe ser un solo ejecutable, sin dll para System.load().

Primero escribe un hola mundo:

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

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

y ejecutar javah Drivertiene este encabezado definido

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

y ejecute javap -s Driverasegúrese de usar el nombre correcto

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

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

escribir el 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;

}

Resultado:

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

Process finished with exit code 0

Bueno, 1 + 1 = 0, eso no tiene absolutamente ningún sentido.

Luego trato de usar System.out/erry try catchencontrar el problema pero obtengo el mismo resultado, esa cosa ni siquiera puede detectarse por excepción de Java o incluso C++ try catch (...).

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

Entonces asegúrese de que no haya ningún otro error, omito nativo:

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

Trabajando bastante bien, obtuve el valor 1234 en la consola C++.

※ Primera pregunta:

¿Cómo obtener la transmisión JVM stdio? System.out/err.printno se mostrará en la consola de "iniciativa". Pero la impresión estándar de DLL se imprimirá en la consola Java cuando esté en modo "pasivo".

※ Segunda pregunta:

¿Qué sucede en la llamada nativa? No debería obtener 0 resultado, ¿Cómo solucionarlo? ¿Cómo lograr el objetivo?

BYW: no tiene sentido, pero buen intento: el uso CallObjectMethodobtendrá el mismo resultado, el uso GetMethodIDdevolverá la ID 0 y una salida larga atascada con 1073741819 (0xC0000005).

Actualización 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;

Tengo esta salida

method id = 0x1ec97350
method result = 0

Actualización 2:

Para asegurarme de que no sea C externo o de lo contrario, solo escribo el cuerpo de la función 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;
}

Y use la herramienta para asegurarse de que el nombre de la función sea correcto

Y use env->ExceptionOccurredpara obtener una excepción, y hay una:

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

Y ninguno con o sin exter "C" {}bloque funciona, todos fallaron como resultado 0 y UnsatisfiedLinkError.

Entonces, creo que incluso la función nativa requerida en el archivo exe, el jvm no puede encontrarla.

Ahora la situación es:

Mi programa C ++ es mainla entrada, y escribo Java SDK para el desarrollador de complementos.

En tiempo de ejecución, C ++ crea JVM, carga la clase java, invoca el método java cuando ocurre un evento y el complemento usa nativo para "hacer algo", entonces, ¿cómo?

y tambien intento

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

Tengo 2, que está funcionando bien. El único problema es la llamada nativa de Java.

Respuestas

AlexCohn Aug 19 2020 at 14:16

Para acceder a los métodos nativos, aún debe llamar a System.LoadLibrary(). La especificación explica que su Driver.java debería contener:

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

y en su 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;
}

Asegúrese de que el enlazador mantenga ambos Java_Driver_nativeSumy los JNI_OnLoad_driverexporte en su binario.

En cuanto a su primera pregunta, no hay una secuencia JVM stdio separada, Java lee de la misma fd=0y escribe igual fd=1que todos los demás.