从 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();
}
}
接下来,我们有javah
或javac -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;
}
我正在寻找有关如何使用 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();
}
}
接下来,我们有javah
或javac -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;
}