JNI Como chamar java e chamar de volta nativo, e como obter JVM std io

Aug 18 2020

Sou desenvolvedor no Windows, mas não estou usando a biblioteca do Windows.

O modo "iniciativo" JNI que executa o java primeiro e Systen.load()depois chama o método nativo. Ou modo "passivo", o executável cria JVM JNI_CreateJavaVMe chama o método java.

Agora, estou tentando fazer um programa C ++ com JNI SDK, então essa coisa deve ser um único executável, sem dll para System.load().

Primeiro escreva um hello world:

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

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

e execute javah Drivereste cabeçalho definido

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

e corra javap -s Drivercertifique-se de usar o nome certo

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

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

escreva o 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

Bem, 1 + 1 = 0, isso absolutamente não faz sentido.

Então eu tento usar System.out/erre try catchencontrar o issus, mas obtenho o mesmo resultado. Essa coisa nem pode ser capturada por exceção java ou mesmo C++ try catch (...).

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

Então certifique-se de não cometer nenhum outro erro, eu ignoro o nativo:

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

Trabalhando muito bem, obtive o valor 1234 no console C++.

※ Primeira pergunta:

Como obter o fluxo do JVM stdio? System.out/err.printnão será exibido no console de "iniciativa". Mas a impressão std DLL será impressa no console java no modo "passivo".

※ Segunda questão:

O que acontece na chamada nativa? Não devo obter 0 resultado, como corrigir isso? Como atingir o objetivo?

BYW - não faz sentido, mas boa tentativa: usar CallObjectMethodobterá o mesmo resultado, usar GetMethodIDretornará ID 0 e uma saída longa travada com 1073741819 (0xC0000005).

Atualização 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;

Obteve esta saída

method id = 0x1ec97350
method result = 0

Atualização 2:

Para ter certeza de que não é C externo ou outro, apenas escrevo o corpo da função em 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 use a ferramenta para garantir que o nome da função esteja correto

E use env->ExceptionOccurredpara obter Exception, e há um:

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

E nenhum com ou sem exter "C" {}bloco está funcionando, todos falharam como resultado 0 e UnsatisfiedLinkError.

Portanto, acho que mesmo a função nativa necessária no arquivo exe, o jvm não consegue encontrá-la.

Agora a situação é:

Meu programa C ++ é maina entrada e escrevo Java SDK para desenvolvedor de plugins.

Em tempo de execução, C++ cria JVM, carrega classe java, invoca método java quando evento e plug-in usa nativo para "fazer algo", então como?

E eu também tento

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

Eu tenho 2, que está funcionando bem. O único problema é a chamada java nativa.

Respostas

AlexCohn Aug 19 2020 at 14:16

Para acessar métodos nativos, você ainda deve chamar System.LoadLibrary(). A especificação explica que seu Driver.java deve conter:

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 em seu 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;
}

Certifique-se de que o vinculador mantenha ambos Java_Driver_nativeSume JNI_OnLoad_driverexportados em seu binário.

Quanto à sua primeira pergunta, não há fluxo JVM stdio separado, o Java lê o mesmo fd=0e grava o mesmo fd=1que todos os outros.