У меня есть программа 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-кода, если целевой модуль (возможно?) уже загружен.
Ну может мой подход совсем неверный, но явных дефектов я не вижу, может быть вы могли бы помочь?
Что, возможно, могло бы помочь (но я не пробовал ни один из этих подходов, потому что я все еще не совсем уверен)
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).




System.loadLibrary() необходим для работы поиска jni. У вас также есть более гибкая альтернатива System.load().
Убедитесь, что реализация нативного метода объявлена с помощью extern "C" и не скрыта компоновщиком.
Одна из возможностей, как уже упоминалось другими, состоит в том, чтобы создать общую библиотеку (.dll) и вызвать ее из собственного кода а также из Java для обмена данными.
Однако, если вы хотите выполнить обратный вызов функции C, определенной в собственном коде в том же модуле, что и тот, который первоначально был создан JVM, вы можете использовать RegisterNatives.
Простой пример
Джава
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;
}
Результат
Ссылки
Создание 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
Большое спасибо, это именно то, что я искал; ваш ответ принят.
Я этого не делал, но кажется, что должна быть возможность выполнить обратный вызов функции в исполняемом файле, который вызвал JVM. Но я ожидаю, что вам нужно правильно построить приложение, чтобы поддерживать это. В противном случае искомая функция имеет хорошие шансы на то, что ее нельзя будет динамически связать. Путь наименьшего сопротивления, вероятно, состоит в том, чтобы просто перенести функцию на DSO. Если основная программа связывает DSO, то вы, вероятно, сможете обойтись без
System.loadLibrary().