从 C 调用 Java 方法而不从 C 启动 JVM

Calling Java methods from C without starting the JVM from C

我正在寻找有关如何使用 JNI 从 C 调用 Java 方法的教程。到目前为止,在我找到的所有教程中,示例都展示了如何首先从 C 创建 JVM。

我的应用程序从Java开始,使用JNI调用一些C函数。我现在需要从 C 调用一些 Java 函数,我不想从 C 启动 JVM。

例如,是否可以创建一个用 C 实现的 "native" 方法,并使用它来保存 JNIEnv 指针,然后重用它,而不是创建来自 C 的新 JVM 实例,以便调用 Java 方法?

有例子吗?


编辑:

任何阅读此线程的人都应该小心! 仅使用来自当前 JNI 调用的 JNIEnv* 实例!如果您不想让您的程序崩溃,请不要保存和重复使用您之前获得的任何 JNIEnv* 指针。

使用旧保存的指针,在最好的情况下可能会使您的应用程序崩溃。在更糟糕的情况下,它可能会导致难以调试和理解的低级不一致问题

不应该缓存 JNIEnv* 实例,因为如果您的程序从 Java 开始,那么您的 C 代码将无论如何只能作为本机执行 java 方法,它的 C 实现总是得到一个相关的 JNIEnv* 实例 。在您的本机方法 returns 之前,这是您应该使用的唯一实例,而不是任何旧的已保存实例。

正如JJF所说,这绝对是可以的!我将在下面展示一个示例,假设您已经知道如何从 C 调用 Java 方法,但不必创建 JVM!

首先,我们有一个Javaclasstest.Test.java调用本地方法methodA:

package test;

public class Test {
    static {
        System.loadLibrary("test");
    }

    private native void methodA();

    public static void methodB() {
        System.out.println("Java: Method B has executed!");
    }

    public static void main(String[] args) {
        new Test().methodA();
    }
}

接下来,我们有javahjavac -h创建的头文件:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_Test */

#ifndef _Included_test_Test
#define _Included_test_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test_Test
 * Method:    methodA
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_test_Test_methodA
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

下面是包含本机方法的 C 文件 methodA:

#include <jni.h>
#include <stdio.h>
#include "test_Test.h"


void callMethodB(JNIEnv *env);

JNIEXPORT void JNICALL Java_test_Test_methodA(JNIEnv *env, jobject thisObj) {
    printf("C: Method A executed!\n"); fflush(stdout);
    callMethodB(env);
    return;
}

void callMethodB(JNIEnv *env) {
    jclass testClass = (*env) -> FindClass(env, "test/Test");
    jmethodID methodB = (*env) -> GetStaticMethodID(env, testClass, "methodB", "()V");
    (*env) -> CallStaticVoidMethod(env, testClass, methodB, NULL);
    return;
}

这个程序的输出是:

C: Method A executed!

Java: Method B has executed!

记得用您喜欢的 GNU 编译器创建一个 test.dll 文件。

不是创建一个新的 Java 虚拟机(当当前进程已经有一个虚拟机时这是不可能的)你可以使用 JNI_GetCreatedJavaVMs 获得现有的 Java 虚拟机(https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNI_GetCreatedJavaVMs). Use AttachCurrentThread (https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#AttachCurrentThread) 获取环境指针。

这也适用于多线程环境,而传递 env 指针仅适用于单线程。

这是一个可能行不通的实验性解决方案。它通过使用 JNIEnv_ 结构而不是创建 JNIEnv 对象来工作,这样就无需创建 Java 虚拟机。只能调用JNI原生镜像,不能调用jar文件

#include <jni.h>
using namespace std;

int main(int argc, char **argv) {
    JNIEnv_ *jni;
    jclass mainClass = jni->FindClass("net/minecraft/client/main/Main");
    jmethodID constructor = jni->GetStaticMethodID(mainClass, "Main", "()V");
    jni->CallStaticVoidMethod(mainClass, constructor);
    return 0;
}