JNI(Java Native Interface)是Java平台提供的一种机制,允许Java代码与其他语言(主要是C/C++)编写的代码进行交互。这种技术广泛应用于需要高性能计算、硬件操作或复用已有本地代码库的场景。下面我将从原理到实践全面解析JNI技术。
JNI是Java Native Interface的缩写,它建立了一个桥梁,使得Java代码能够调用本地方法(Native Method),同时本地代码也可以调用Java对象和方法。这种双向交互能力使得Java可以突破自身限制,实现以下功能:
JNI通过特定的命名规则和数据类型映射实现Java与本地代码的交互:
native关键字声明本地方法System.loadLibrary()加载动态链接库图1:JNI调用流程
Java Code → JNI Interface → Native Code (C/C++)
虽然现在官方推荐使用javac -h,但理解传统流程有助于掌握原理:
javapublic class HelloJNI {
static {
System.loadLibrary("hello"); // 加载动态库
}
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello();
}
}
javah命令javac HelloJNI.java javah HelloJNI
生成的头文件包含类似下面的函数声明:
cJNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
c#include "HelloJNI.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
bash# Linux
gcc -shared -fPIC -o libhello.so HelloJNI.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux
# Windows
cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD HelloJNI.c -Fehello.dll
现在官方推荐使用javac -h生成头文件:
javapackage com.pyc.jni;
public class JNIDemo {
public native void testHello();
public static void main(String[] args) {
System.loadLibrary("TestJNI");
new JNIDemo().testHello();
}
}
bashjavac -h . com/pyc/jni/JNIDemo.java
这会生成com_pyc_jni_JNIDemo.h头文件
jni.h和jni_md.h到项目libTestJNI.dylib或TestJNI.dll)JNI定义了Java类型与本地类型的对应关系:
表1:JNI基本类型映射
| Java类型 | JNI类型 | 描述 |
|---|---|---|
| boolean | jboolean | C/C++ 8位整型 |
| byte | jbyte | 带符号的8位整型 |
| char | jchar | 无符号的16位整型 |
| short | jshort | 带符号的16位整型 |
| int | jint | 带符号的32位整型 |
| long | jlong | 带符号的64位整型 |
| float | jfloat | 32位浮点型 |
| double | jdouble | 64位浮点型 |
| Object | jobject | 任何Java对象 |
| String | jstring | Java字符串对象 |
| Array | jarray | Java数组 |
JNIEnv是指向JNI函数表的指针,它提供了以下功能:
在C和C++中使用方式不同:
c// C风格
(*env)->NewStringUTF(env, "Hello");
// C++风格
env->NewStringUTF("Hello");
JNI不仅允许Java调用本地代码,还支持本地代码回调Java方法:
cppjclass clazz = env->FindClass("com/example/Test");
cppjmethodID method = env->GetMethodID(clazz, "add", "(II)I");
其中"(II)I"是方法签名,表示两个int参数和int返回值
cppjint result = env->CallIntMethod(obj, method, 10, 20);
JNI中的异常处理需要注意:
cppjthrowable exc = env->ExceptionOccurred();
if (exc) {
env->ExceptionDescribe();
env->ExceptionClear();
}
cppjclass excCls = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(excCls, "Null pointer exception");
JNI涉及Java与本地内存交互,需要注意:
cppjclass globalClazz = (jclass)env->NewGlobalRef(localClazz);
cppenv->DeleteGlobalRef(globalClazz);
ByteBuffer.allocateDirect()虽然JNI功能强大,但也有更简单的替代方案:
表2:JNI与替代技术对比
| 特性 | JNI | JNA | SWIG |
|---|---|---|---|
| 需要本地代码 | 是 | 否 | 部分 |
| 性能 | 最高 | 中等 | 高 |
| 复杂度 | 高 | 低 | 中等 |
| 适用场景 | 高性能需求 | 快速集成 | 多语言项目 |
UnsatisfiedLinkError-Djava.library.path)NewGlobalRef/DeleteGlobalRef管理引用AttachCurrentThread附加线程DetachCurrentThread分离JNI作为Java与本地代码交互的标准接口,虽然学习曲线较陡峭,但掌握后可以极大扩展Java的能力边界。在实际开发中,应根据项目需求选择合适的技术方案,平衡开发效率与运行性能。
本文作者:JACK WEI
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!