跳转至

Java Native

JNI, JNA, JNR, Panama:

Native Call Java:底层都是基于 JNI.

  • JNI, JavaCPP.

JNI(C)

标准的 Java native通信工具,但复杂度高,需要很多native code到java的回调

详见 JNI 使用文档

  • 性能最好,但是使用比较复杂;

JNA(C)

https://github.com/java-native-access/jna

  • 不需要 JNI 代码 ,只需要定义接口和函数(匹配);
  • 相较于JNR,更为成熟;

原理

  • 基于反射,数据转换从本地代码跳到 Java 代码非常昂贵;
  • 基于libffi 进行函数调用约定;

Native.loadLibrary("c", LibC.class) 获取了接口实现

  • 通过动态代理(dynamic proxy)实现,根据返回参数的不同,分派到 Native 类的,invokeXxx 本地方法:
/**
 * Call the native function.
 *
 * @param function  Present to prevent the GC to collect the Function object
 *                  prematurely
 * @param fp        function pointer
 * @param callFlags calling convention to be used
 * @param args      Arguments to pass to the native function
 *
 * @return The value returned by the target native function
 */
static native int invokeInt(Function function, long fp, int callFlags, Object[] args);

static native long invokeLong(Function function, long fp, int callFlags, Object[] args);

static native Object invokeObject(Function function, long fp, int callFlags, Object[] args);
...

invokeXxx 本地方法的实现 dispatch.c#L2122invokeIntinvokeLong 实现源码类似):

/*
 * Class:     com_sun_jna_Native
 * Method:    invokeInt
 * Signature: (Lcom/sun/jna/Function;JI[Ljava/lang/Object;)I
 */
JNIEXPORT jint JNICALL
Java_com_sun_jna_Native_invokeInt(JNIEnv *env, jclass UNUSED(cls), 
                                  jobject UNUSED(function), jlong fp, jint callconv,
                                  jobjectArray arr)
{
    ffi_arg result;
    dispatch(env, L2A(fp), callconv, arr, &ffi_type_sint32, &result);
    return (jint)result;
}

即,全部 invokeXxx 本地方法统一被分派到 dispatch 函数 dispatch.c#L439

static void
dispatch(JNIEnv *env, void* func, jint flags, jobjectArray args,
ffi_type *return_type, void *presult)

dispatch 函数是,需要调用的函数指针地址、输入参数和返回参数,全部是运行时确定。要想完成这个函数调用逻辑,就要运行时构造栈帧,生成参数压栈和清理堆栈的工作。

  • JNA 3.0 之前,实现运行时构造栈帧的逻辑的对应代码 dispatch_i386.cdispatch_ppc.cdispatch_sparc.s,分别实现 Intel x86、PowerPC 和 Sparc 三种 CPU 架构。

  • JNA 3.0 开始,摒弃了原先手动构造栈帧的做法,把 libffi 集成进了 JNA。

JNR(C)

https://github.com/jnr/jnr-ffi

  • 基于libffi,但使用字节码生成技术而不是反射;
  • 不需要JNI代码 ,只需要定义接口和函数(匹配);

jnr

原理

JNA 使用动态代理生成实现类,而 JNR 使用 ASM 字节码操作库生成直接实现类,去除了每次调用本地方法时额外的动态代理的逻辑。

但其调用栈,相对于直接使用 JNI,还是比较繁琐。

Project Panama(C)

JDK 19 :JEP 424: Foreign Function & Memory API (Preview) (openjdk.org)

JDK 20 :JEP 434: Foreign Function & Memory API (Second Preview) (openjdk.org)

JavaCPP(C++)

JavaCPP provides efficient access to native C++ inside Java, also access Java from C++.

对很多C/C++库,提供相应的封装(bytedeco/javacpp-presets: The missing Java distribution of native C++ libraries)。

原理

根据 C++ 的头文件,进行解析,自动生成 JNI 的 SO 的封装函数。

javacpp flow