JNI (Java Native Interface) 使用指南

JNI 是 Java Native Interface 的缩写,它允许 Java 代码与本地代码(如 C/C++)相互调用。以下是 JNI 的全面使用指南。

1. JNI 基础概念

主要功能

  • 从 Java 调用本地方法(C/C++ 实现)

  • 从本地代码调用 Java 方法

  • 在本地代码中操作 Java 对象

基本原理

  1. Java 代码声明 native 方法

  2. 使用 javah/javac -h 生成头文件

  3. 实现本地方法

  4. 编译生成动态链接库

  5. Java 程序加载库并调用本地方法

2. 从 Java 调用本地代码

步骤 1:声明 native 方法

java

public class NativeDemo {
    // 声明 native 方法
    public native void sayHello();
    public native int addNumbers(int a, int b);
    
    // 加载动态库
    static {
        System.loadLibrary("NativeDemo");
    }
    
    public static void main(String[] args) {
        NativeDemo demo = new NativeDemo();
        demo.sayHello();
        System.out.println("3 + 5 = " + demo.addNumbers(3, 5));
    }
}

步骤 2:生成头文件

bash

javac NativeDemo.java
javac -h . NativeDemo.java

生成的头文件 NativeDemo.h 内容示例:

c

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeDemo */

#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     NativeDemo
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_NativeDemo_sayHello
  (JNIEnv *, jobject);

/*
 * Class:     NativeDemo
 * Method:    addNumbers
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_NativeDemo_addNumbers
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

步骤 3:实现本地方法

创建 NativeDemo.c 文件:

c

#include <stdio.h>
#include "NativeDemo.h"

JNIEXPORT void JNICALL Java_NativeDemo_sayHello(JNIEnv *env, jobject obj) {
    printf("Hello from native code!\n");
}

JNIEXPORT jint JNICALL Java_NativeDemo_addNumbers(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b;
}

步骤 4:编译本地代码

Linux/macOS:

bash

gcc -shared -fpic -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -o libNativeDemo.so NativeDemo.c
Windows:

cmd

cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD NativeDemo.c -FeNativeDemo.dll

步骤 5:运行 Java 程序

bash

java -Djava.library.path=. NativeDemo

3. 从本地代码调用 Java

访问 Java 对象字段

c

JNIEXPORT void JNICALL Java_NativeDemo_modifyField(JNIEnv *env, jobject obj) {
    // 获取类
    jclass cls = (*env)->GetObjectClass(env, obj);
    
    // 获取字段ID
    jfieldID fid = (*env)->GetFieldID(env, cls, "myField", "I");
    
    // 获取字段值
    jint value = (*env)->GetIntField(env, obj, fid);
    
    // 修改字段值
    (*env)->SetIntField(env, obj, fid, value + 1);
}

调用 Java 方法

c

JNIEXPORT void JNICALL Java_NativeDemo_callJavaMethod(JNIEnv *env, jobject obj) {
    // 获取类
    jclass cls = (*env)->GetObjectClass(env, obj);
    
    // 获取方法ID
    jmethodID mid = (*env)->GetMethodID(env, cls, "javaMethod", "(Ljava/lang/String;)V");
    
    // 创建字符串参数
    jstring jstr = (*env)->NewStringUTF(env, "From native code");
    
    // 调用方法
    (*env)->CallVoidMethod(env, obj, mid, jstr);
    
    // 释放本地引用
    (*env)->DeleteLocalRef(env, jstr);
}

4. 数据类型映射

基本类型

Java 类型JNI 类型C/C++ 类型
booleanjbooleanunsigned char
bytejbytechar
charjcharunsigned short
shortjshortshort
intjintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble

引用类型

Java 类型JNI 类型
Objectjobject
Stringjstring
Classjclass
Object[]jobjectArray
boolean[]jbooleanArray
byte[]jbyteArray
......

5. 异常处理

检查异常

c

jthrowable exc = (*env)->ExceptionOccurred(env);
if (exc) {
    (*env)->ExceptionDescribe(env);
    (*env)->ExceptionClear(env);
    // 处理异常...
}

抛出异常

c

jclass excCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
if (excCls != NULL) {
    (*env)->ThrowNew(env, excCls, "Invalid argument from native code");
}

6. 内存管理

局部引用

  • 默认创建的引用都是局部引用

  • 在本地方法返回后自动释放

  • 可以手动释放:DeleteLocalRef()

全局引用

c

jclass globalCls = (*env)->NewGlobalRef(env, localCls);
// 使用...
(*env)->DeleteGlobalRef(env, globalCls);

弱全局引用

c

jclass weakCls = (*env)->NewWeakGlobalRef(env, localCls);
// 使用前检查是否有效
if ((*env)->IsSameObject(env, weakCls, NULL) == JNI_FALSE) {
    // 仍然有效
}
(*env)->DeleteWeakGlobalRef(env, weakCls);

7. 多线程注意事项

附加本地线程到JVM

c

JavaVM *jvm; // 需要保存全局的JavaVM指针
JNIEnv *env;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);

// 执行JNI调用...

(*jvm)->DetachCurrentThread(jvm);

线程同步

c

// 获取对象监视器
(*env)->MonitorEnter(env, obj);
// 临界区代码...
(*env)->MonitorExit(env, obj);

8. 最佳实践

  1. 减少JNI调用:每次JNI调用都有开销,尽量批量处理数据

  2. 正确处理字符串:及时释放GetStringUTFChars获取的字符串

  3. 异常检查:每次JNI调用后检查异常

  4. 资源释放:及时释放创建的全局引用和分配的内存

  5. 线程安全:确保多线程环境下正确使用JNI

  6. 类型签名:确保方法签名完全正确

9. 常见问题解决

库加载失败

  • 确保库路径正确 (java.library.path)

  • 检查库依赖 (ldd/otool -L)

  • 确保库文件名正确 (Linux: libxxx.so, Windows: xxx.dll)

UnsatisfiedLinkError

  • 检查方法名是否完全匹配 (包括包名)

  • 检查方法签名是否匹配

  • 确保库导出所需的符号

内存泄漏

  • 检查全局引用是否释放

  • 检查是否释放了GetXXXArrayElements获取的数组

  • 检查是否释放了GetStringUTFChars获取的字符串

JNI 是连接 Java 和本地代码的强大工具,正确使用可以充分发挥两种语言的优势,但需要注意正确处理内存、异常和线程安全等问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值