【问题标题】:Java Invocation API: Call the C function back from the java codeJava 调用 API:从 java 代码中调用 C 函数
【发布时间】:2019-06-21 20:39:09
【问题描述】:

我有一个 C (navive) 程序和一个带有 main() 方法的 jar 文件。从我的本机程序中,我正在初始化 JVM,并调用 main() 方法。我对此没有任何问题,一切都很好。但后来我想从我的 java 代码中回调一个 C 函数。

C 函数在 与创建 JVM 的模块相同的模块中的本机代码中定义。 header 是自动生成的,body 就是这么简单:

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

所以,从我调用NativeUpdaterBus.connect0() 的java 代码中,不断得到UnsatisfiedLinkError。我的 java 代码中没有 System.loadLibrary() 调用,因为我认为,如果目标模块是(可能?)已经加载。

好吧,也许我的方法完全不正确,但我看不出有任何明显的缺陷,也许你能帮忙?

什么可能有帮助(但我没有尝试任何这些方法,因为我仍然不太确定)

  • 对这些 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() 就可以逃脱。

标签: java c callback java-native-interface invoke


【解决方案1】:

正如其他人已经提到的,一种可能性是创建一个共享库 (.dll) 并从本机代码调用它和从 Java 来交换数据。

但是,如果您想回调在与 JVM 最初创建的模块相同的模块中的本地代码中定义的 C 函数,则可以使用 RegisterNatives。

简单示例

  • C 程序创建 JVM
  • 它调用一个类的 Main
  • Java Main 在调用 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 程序

可以用 C 语言创建 Java 虚拟机,如图所示 here (不仅适用于 cygwin,还适用于 VS 2019),然后注册 RegisterNatives 本机 C 回调。因此,使用上面链接中的函数 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;
}

结果

链接

从 C 程序创建 JVM: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

【讨论】:

  • 非常感谢,这正是我要找的;你的回答被接受了。
【解决方案2】:

System.loadLibrary() 是 jni 查找正常工作的必要条件。您还有一个更灵活的System.load() 替代方案。

确保本地方法实现是用extern "C" 声明的,并且没有被链接器隐藏。

【讨论】:

    猜你喜欢
    • 2018-07-20
    • 2018-09-27
    • 2010-09-08
    • 1970-01-01
    • 2014-06-01
    • 2017-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多