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 代码返回本机代码。
好吧,也许我的方法完全不正确,但我看不出任何明显的缺陷,也许你能帮忙?
可能有什么帮助(但我没有尝试任何这些方法,因为我仍然不太确定)
- 使用一种带有这些 JNI 方法的 "trampoline" 动态库,从 java 代码加载它,然后通过它编组本机调用。
- 定义一个
java.lang.Runnable
的匿名继承者,用 jni_env->DefineClass()
创建,但这涉及一些字节码技巧。
- 使用另一种侵入性较小的方法,如套接字、命名管道等。但就我而言,我只使用一个本机进程,因此这可能有点矫枉过正。
我正在使用 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
我有一个 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 代码返回本机代码。
好吧,也许我的方法完全不正确,但我看不出任何明显的缺陷,也许你能帮忙?
可能有什么帮助(但我没有尝试任何这些方法,因为我仍然不太确定)
- 使用一种带有这些 JNI 方法的 "trampoline" 动态库,从 java 代码加载它,然后通过它编组本机调用。
- 定义一个
java.lang.Runnable
的匿名继承者,用jni_env->DefineClass()
创建,但这涉及一些字节码技巧。 - 使用另一种侵入性较小的方法,如套接字、命名管道等。但就我而言,我只使用一个本机进程,因此这可能有点矫枉过正。
我正在使用 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