Java Invocation API: вызов функции C обратно из кода Java - PullRequest
1 голос
/ 21 июня 2019

У меня есть программа на C (navive) и файл 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).

Ответы [ 2 ]

2 голосов
/ 22 июня 2019

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

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

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

  • Программа C создает JVM
  • , она вызывает Main класса
  • Основные вызовы Javaобратно функцию C с именем connect0 в вызывающем коде C
  • , чтобы иметь контрольный пример, нативная функция C создает строку Java и возвращает ее
  • сторона 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), а затем регистрируется с помощью обратных вызовов 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

1 голос
/ 22 июня 2019

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...