Java调用API:从java代码中调用C函数

Java Invocation API: Call the C function back from the java code

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

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

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

因此,从 java 我调用的代码 NativeUpdaterBus.connect0(),不断得到一个 UnsatisfiedLinkError。我的 java 代码中没有 System.loadLibrary() 调用,因为我 认为 ,调用如果目标模块(可能?)已经加载,则从 java 代码返回本机代码。

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

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

我正在使用 OpenJDK 11.0.3 和 Windows10。我的 C 程序是使用 Microsoft cl.exe 19.16.27031.1 for x64(Visual Studio 2017)编译的。

System.loadLibrary() 对于 jni 查找工作是必不可少的。您还有一个更灵活的 System.load() 替代方案。

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

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

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

简单示例

  • C 程序创建 JVM
  • 它调用 class
  • 的 Main
  • JavaMain 在调用 C 代码中回调名为 connect0 的 C 函数
  • 要有一个测试用例,本机 C 函数构造一个 Java 字符串和 returns 它
  • 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 VM,如图 here (不仅适用于 cygwin,还适用于 VS 2019),然后使用 RegisterNatives 本机 C 回调进行注册。所以使用上面 link 中的函数 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