Java Invocation API: обратный вызов функции C из кода Java

У меня есть программа C (нативная) и файл jar с методом main(). Из моей родной программы я инициализирую JVM и вызываю метод main(). У меня с этим проблем нет, все в полном порядке. Но затем я хотел вызвать функцию C из моего java-кода.

Функция C определена в собственном коде в тот же модуль, что и тот, что создал JVM. Заголовок генерируется автоматически, а тело такое простое:

JNIEXPORT void JNICALL Java_eu_raman_chakhouski_NativeUpdaterBus_connect0(JNIEnv* env, jclass clazz)
{
    return;
}

Итак, из java-кода, который я вызываю NativeUpdaterBus.connect0(), постоянно получаю UnsatisfiedLinkError. У меня нет вызовов System.loadLibrary() в моем java-коде, потому что я мысль, что не будет проблем с вызовом нативного кода из java-кода, если целевой модуль (возможно?) уже загружен.

Ну может мой подход совсем неверный, но явных дефектов я не вижу, может быть вы могли бы помочь?

Что, возможно, могло бы помочь (но я не пробовал ни один из этих подходов, потому что я все еще не совсем уверен)

  • Используйте своего рода динамическую библиотеку "батут" с этими методами JNI, загрузите ее из java-кода, а затем маршалируйте через нее собственные вызовы.
  • Определите анонимного наследника java.lang.Runnable, созданного с помощью jni_env->DefineClass(), но это связано с некоторыми хитростями с байт-кодом.
  • Используйте другой, менее инвазивный подход, например, сокеты, именованные каналы и т. д. Но в моем случае я использую только один собственный процесс, так что это может быть излишним.

Я использую OpenJDK 11.0.3 и Windows 10. Моя программа на C скомпилирована с помощью Microsoft cl.exe 19.16.27031.1 for x64 (Visual Studio 2017).

Я этого не делал, но кажется, что должна быть возможность выполнить обратный вызов функции в исполняемом файле, который вызвал JVM. Но я ожидаю, что вам нужно правильно построить приложение, чтобы поддерживать это. В противном случае искомая функция имеет хорошие шансы на то, что ее нельзя будет динамически связать. Путь наименьшего сопротивления, вероятно, состоит в том, чтобы просто перенести функцию на DSO. Если основная программа связывает DSO, то вы, вероятно, сможете обойтись без System.loadLibrary().

John Bollinger 21.06.2019 23:15
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
1
320
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

System.loadLibrary() необходим для работы поиска jni. У вас также есть более гибкая альтернатива System.load().

Убедитесь, что реализация нативного метода объявлена ​​с помощью extern "C" и не скрыта компоновщиком.

Ответ принят как подходящий

Одна из возможностей, как уже упоминалось другими, состоит в том, чтобы создать общую библиотеку (.dll) и вызвать ее из собственного кода а также из Java для обмена данными.

Однако, если вы хотите выполнить обратный вызов функции C, определенной в собственном коде в том же модуле, что и тот, который первоначально был создан JVM, вы можете использовать RegisterNatives.

Простой пример

  • Программа C создает JVM
  • он вызывает Main класса
  • Java Main вызывает функцию C с именем connect0 в вызывающем коде C
  • чтобы иметь тестовый пример, собственная функция C создает строку Java и возвращает ее
  • сторона Java печатает результат

Джава

package com.software7.test;

public class Main {
    private native String connect0() ;

    public static void main(String[] args) {
        Main m = new Main();
        m.makeTest(args);
    }

    private void makeTest(String[] args) {
        System.out.println("Java: main called");
        for (String arg : args) {
            System.out.println(" -> Java: argument: '" + arg + "'");
        }
        String res = connect0(); //callback into native code
        System.out.println("Java: result of connect0() is '" + res + "'"); //process returned String
    }
}

Программа C

Можно создать виртуальную машину Java в C, как показано здесь (работает не только с cygwin, но и с VS 2019), а затем зарегистрируйтесь с помощью собственных обратных вызовов C RegisterNatives. Таким образом, используя функцию invoke_class из приведенной выше ссылки, это может выглядеть так:

#include <stdio.h>
#include <windows.h>
#include <jni.h>
#include <stdlib.h>
#include <stdbool.h>

... 

void invoke_class(JNIEnv* env) {
    jclass helloWorldClass;
    jmethodID mainMethod;
    jobjectArray applicationArgs;
    jstring applicationArg0;

    helloWorldClass = (*env)->FindClass(env, "com/software7/test/Main");

    mainMethod = (*env)->GetStaticMethodID(env, helloWorldClass, "main", "([Ljava/lang/String;)V");

    applicationArgs = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), NULL);
    applicationArg0 = (*env)->NewStringUTF(env, "one argument");
    (*env)->SetObjectArrayElement(env, applicationArgs, 0, applicationArg0);

    (*env)->CallStaticVoidMethod(env, helloWorldClass, mainMethod, applicationArgs);
}

jstring connect0(JNIEnv* env, jobject thiz);

static JNINativeMethod native_methods[] = {
        { "connect0", "()Ljava/lang/String;", (void*)connect0 },
};

jstring connect0(JNIEnv* env, jobject thiz) {
    printf("C: connect0 called\n");
    return (*env)->NewStringUTF(env, "Some Result!!");
}

static bool register_native_methods(JNIEnv* env) {
    jclass clazz = (*env)->FindClass(env, "com/software7/test/Main");
    if (clazz == NULL) {
        return false;
    }
    int num_methods = sizeof(native_methods) / sizeof(native_methods[0]);
    if ((*env)->RegisterNatives(env, clazz, native_methods, num_methods) < 0) {
        return false;
    }
    return true;
}


int main() {
    printf("C: Program starts, creating VM...\n");

    JNIEnv* env = create_vm();
    if (env == NULL) {
        printf("C: creating JVM failed\n");
        return 1;
    }
    if (!register_native_methods(env)) {
        printf("C: registering native methods failed\n");
        return 1;
    }
    invoke_class(env);

    destroy_vm();
    getchar();
    return 0;
}

Результат

resgister natives

Ссылки

Создание JVM из программы C: http://www.inonit.com/cygwin/jni/invocationApi/c.html

Регистрация собственных методов: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/functions.html#registering-native-methods

Большое спасибо, это именно то, что я искал; ваш ответ принят.

Netherwire 22.06.2019 17:38

Другие вопросы по теме