如何通过JNI从C++调用带Functional Interface参数的Java方法?
解决JNI调用带Functional Interface参数Java方法的段错误问题
问题描述
需要通过JNI从C调用带有Consumer<String>参数的Java方法,实现Java向C的回调,但运行编译脚本后出现SIGSEGV段错误。
原有代码
Main.java
import java.util.function.Consumer; public class Main { static { System.loadLibrary("Main"); } public static void main(String[] args) { Main m = new Main(); m.nativeMethod(); } public void test() { System.out.println("test"); } public void getCallback(Consumer<String> consumer) { consumer.accept("Data from Java to cpp"); } public native void nativeMethod(); }
Main.cpp
#include <iostream> #include <string> #include "Main.h" extern "C" { JNIEXPORT void JNICALL myCallback(JNIEnv *env, jobject obj, jstring data) { jboolean isCopy; const char *cData = env->GetStringUTFChars(data, &isCopy); std::string cppData(cData); env->ReleaseStringUTFChars(data, cData); std::cout << cppData << '\n'; } JNIEXPORT void JNICALL Java_Main_nativeMethod(JNIEnv *env, jobject obj) { std::cout << "Hello from Java_Main_nativeMethod" << '\n'; jmethodID jTest = env->GetMethodID( env->GetObjectClass(obj), "test", "()V"); env->CallVoidMethod(obj, jTest); jmethodID jFunction = env->GetMethodID( env->GetObjectClass(obj), "getCallback", "(Ljava/util/function/Consumer;)V"); env->CallVoidMethod(obj, jFunction, myCallback); } }
compile.bash
#!/bin/bash cd "$(dirname "$0")" javac -h . Main.java # generates header javac Main.java gcc -lstdc++ -shared -I /Library/Java/JavaVirtualMachines/temurin-20.jdk/Contents/Home/include -o libMain.jnilib Main.cpp java Main
错误输出
Hello from Java_Main_nativeMethod test # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x0000000108554b14, pid=72842, tid=10499 # # JRE version: OpenJDK Runtime Environment Temurin-20.0.2+9 (20.0.2+9) (build 20.0.2+9) # Java VM: OpenJDK 64-Bit Server VM Temurin-20.0.2+9 (20.0.2+9, mixed mode, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64) # Problematic frame: # V [libjvm.dylib+0x514b14] jni_CallVoidMethodV+0xe0 # # No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again # # An error report file with more information is saved as: # hs_err_pid72842.log # # If you would like to submit a bug report, please visit: # https://github.com/adoptium/adoptium-support/issues # zsh: abort java Main
问题根源
直接将C函数指针myCallback作为参数传给Java的Consumer类型参数是错误的。Java的Functional Interface要求传入的是实现该接口的Java对象,而原生函数指针是C内存地址,Java虚拟机无法识别,导致内存访问错误。
修改方案
通过Java层封装回调逻辑,创建实现Consumer<String>的对象,该对象的accept方法调用原生静态方法,再在C++中获取这个Java对象并传给getCallback方法。
修改后的代码
Main.java
import java.util.function.Consumer; public class Main { static { System.loadLibrary("Main"); } public static void main(String[] args) { Main m = new Main(); m.nativeMethod(); } public void test() { System.out.println("test"); } public void getCallback(Consumer<String> consumer) { consumer.accept("Data from Java to cpp"); } // 静态原生方法,作为Java回调C++的入口 public static native void onCallback(String data); // 返回绑定了原生方法的Consumer对象 public Consumer<String> getConsumer() { return Main::onCallback; } public native void nativeMethod(); }
Main.cpp
#include <iostream> #include <string> #include "Main.h" extern "C" { // 实现静态原生回调方法 JNIEXPORT void JNICALL Java_Main_onCallback(JNIEnv *env, jclass clazz, jstring data) { jboolean isCopy; const char *cData = env->GetStringUTFChars(data, &isCopy); std::string cppData(cData); env->ReleaseStringUTFChars(data, cData); std::cout << cppData << '\n'; } JNIEXPORT void JNICALL Java_Main_nativeMethod(JNIEnv *env, jobject obj) { std::cout << "Hello from Java_Main_nativeMethod" << '\n'; // 调用test方法 jmethodID jTest = env->GetMethodID( env->GetObjectClass(obj), "test", "()V"); env->CallVoidMethod(obj, jTest); // 获取getConsumer方法ID,调用得到Consumer对象 jmethodID getConsumerMethod = env->GetMethodID( env->GetObjectClass(obj), "getConsumer", "()Ljava/util/function/Consumer;"); jobject consumerObj = env->CallObjectMethod(obj, getConsumerMethod); // 获取getCallback方法ID,传入Consumer对象调用 jmethodID getCallbackMethod = env->GetMethodID( env->GetObjectClass(obj), "getCallback", "(Ljava/util/function/Consumer;)V"); env->CallVoidMethod(obj, getCallbackMethod, consumerObj); // 释放局部引用(规范操作) env->DeleteLocalRef(consumerObj); } }
运行结果
执行编译脚本后,将正常输出:
Hello from Java_Main_nativeMethod test Data from Java to cpp
内容的提问来源于stack exchange,提问作者justcodin
相关产品推荐
相关产品推荐

