JNI Как вызвать java и перезвонить родным, и как получить JVM std io

Aug 18 2020

Я разрабатываю в Windows, но не использую Windows lib.

JNI режима "инициативы", который сначала запускает java, а Systen.load()затем вызывает собственный метод. Или «пассивный» режим. Исполняемый файл создает JVM, а JNI_CreateJavaVMзатем вызывает метод java.

Теперь я пытаюсь создать программу на C ++ с JNI SDK, поэтому эта вещь должна быть единственным исполняемым файлом, без DLL для System.load().

Сначала напишите привет, мир:

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

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

и запустите, чтобы javah Driverэтот заголовок определил

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

и запустите, javap -s Driverубедитесь, что используете правильное имя

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

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

напишите 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;

}

Результат:

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

Process finished with exit code 0

Ну, 1 + 1 = 0, в этом нет никакого смысла.

Затем я пытаюсь использовать System.out/errи try catchнайти issus, но получаю тот же результат. Эта вещь даже не может быть обнаружена с помощью исключения java или даже C ++ try catch (...).

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

Тогда убедитесь, что нет других ошибок, я обхожу родное:

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

Работая нормально, я получил значение 1234 в консоли C ++.

※ Первый вопрос:

Как получить поток stdio JVM? System.out/err.printне будет отображаться в консоли "инициативы". Но DLL std print будет печататься в java-консоли в «пассивном» режиме.

※ Второй вопрос:

Что происходит в родном звонке? У меня не должно быть 0 результатов, как это исправить? Как достичь цели?

BYW - не имеет смысла, но хорошая попытка: использование CallObjectMethodдаст тот же результат, использование GetMethodIDвернет ID 0 и долгое залипание выхода с 1073741819 (0xC0000005).

Обновление 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;

Получил этот вывод

method id = 0x1ec97350
method result = 0

Обновление 2:

Чтобы убедиться, что не extern C или что-то еще, я просто пишу тело функции в 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;
}

И используйте инструмент, чтобы убедиться, что имя функции правильное.

И используйте, env->ExceptionOccurredчтобы получить исключение, и есть одно:

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

И ни один из exter "C" {}блоков с блоком или без него не работает, все не работают как результат 0 и UnsatisfiedLinkError.

Итак, я думаю, что даже встроенная необходимая функция в exe-файле, jvm не может ее найти.

Теперь ситуация такая:

Моя программа mainна C ++ - это запись, и я пишу java SDK для разработчика плагинов.

Во время выполнения C ++ создает JVM, загружает java-класс, вызывает java-метод при событии, и плагин использует native, чтобы «что-то делать». Итак, как это сделать?

И я тоже пробую

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

У меня 2, что нормально работает. Единственная проблема - это собственный вызов java.

Ответы

AlexCohn Aug 19 2020 at 14:16

Чтобы получить доступ к собственным методам, вы все равно должны вызвать System.LoadLibrary(). Спецификации объясняет , что ваш Driver.java должен выглядеть содержать:

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

и в вашем 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;
}

Убедитесь, что компоновщик сохранил оба файла Java_Driver_nativeSumи JNI_OnLoad_driverэкспортировал их в ваш двоичный файл.

Что касается вашего первого вопроса, нет отдельного потока stdio JVM, Java читает из того же fd=0и записывает в то же, fd=1что и все остальные.