Java 运行在虚拟机中,若要调用操作系统动态库,需声明 native 方法,再由 C/C++ 实现。Java 调用这些 native 方法,等价于调用本地代码中的函数。这套机制称为 JNI(Java Native Interface,Java 本地编程接口)。
Android NDK 开发同样建立在 JNI 之上。下文在纯 Java 环境下编译 C 动态库并用 Java 调用,由浅入深给出四个示例。原文参考:CSDN - u_xtian。
相关:System.load 与 System.loadLibrary、JNI 类型签名 [B [C 等。JDK 22 起可用 Project Panama / FFM API 替代多数 JNI 场景;版本变更见 JDK 21 升级到 JDK 25 的变更汇总。
示例一:Hello World
Java:HelloJni.java
public class HelloJni {
static {
System.loadLibrary("hello");
}
public static native void sayHello();
public static void main(String[] args) {
HelloJni.sayHello();
}
}
生成头文件
javac HelloJni.java
javah -jni HelloJni # 旧版工具;JDK 8+ 可用 javac -h . HelloJni.java
生成的 HelloJni.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#ifndef _Included_HelloJni
#define _Included_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
C++ 实现:HelloJni.cpp
#include <stdio.h>
#include "HelloJni.h"
JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *env, jclass cls) {
printf("HelloWorld\n");
}
编译动态库
g++ -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
HelloJni.cpp -shared -o libhello.so
参数说明:
-I:引入 JVM 头文件路径(macOS 将linux换为darwin)-shared:编译为动态链接库-o:输出文件名;Linux 命名惯例为lib+ 库名 +.so,loadLibrary时不要写lib前缀和.so后缀
若 .so 不在系统默认搜索路径,需设置 LD_LIBRARY_PATH 指向其所在目录后再运行 java HelloJni。
示例二:传递 int
C/C++ 对 Java 传入的整数加 1 后返回。
Java:HelloJni.java
public class HelloJni {
static {
System.loadLibrary("hello");
}
public static native void sayHello();
public native int getInt();
public native void setInt(int i);
public static void main(String[] args) {
HelloJni hello = new HelloJni();
hello.setInt(2);
System.out.println(hello.getInt()); // 输出 3
}
}
头文件 HelloJni.h(节选)
JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *, jclass);
JNIEXPORT jint JNICALL Java_HelloJni_getInt(JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_HelloJni_setInt(JNIEnv *, jobject, jint);
C++ 实现
#include <stdio.h>
#include "HelloJni.h"
static int i = 1;
JNIEXPORT void JNICALL Java_HelloJni_sayHello(JNIEnv *env, jclass cls) {
printf("HelloWorld\n");
}
JNIEXPORT jint JNICALL Java_HelloJni_getInt(JNIEnv *env, jobject thiz) {
return i;
}
JNIEXPORT void JNICALL Java_HelloJni_setInt(JNIEnv *env, jobject thiz, jint ji) {
i = ji + 1;
}
示例三:C++ 构造 Java 对象
POJO:User.java
public class User {
private long id;
private String userName;
private boolean isMan;
private int age;
public User() {}
public User(long id, String userName, boolean isMan, int age) {
this.id = id;
this.userName = userName;
this.isMan = isMan;
this.age = age;
}
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public boolean isMan() { return isMan; }
public void setMan(boolean isMan) { this.isMan = isMan; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
Java:HelloJni.java
public class HelloJni {
static {
System.loadLibrary("userbean");
}
public native void setUser(String userName);
public native User getUser();
public static void main(String[] args) {
HelloJni hello = new HelloJni();
hello.setUser("LiangYaotian");
User user = hello.getUser();
System.out.println("user from c/c++");
System.out.println("name:" + user.getUserName());
System.out.println("isMan?:" + user.isMan());
System.out.println("age:" + user.getAge());
}
}
头文件(新增方法)
JNIEXPORT void JNICALL Java_HelloJni_setUser(JNIEnv *, jobject, jstring);
JNIEXPORT jobject JNICALL Java_HelloJni_getUser(JNIEnv *, jobject);
C++ 实现(核心逻辑)
#include <iostream>
#include <stdio.h>
#include "HelloJni.h"
using namespace std;
static jobject user;
JNIEXPORT void JNICALL Java_HelloJni_setUser(JNIEnv *env, jobject thiz, jstring name) {
jclass userClass = env->FindClass("User");
jmethodID userMethod = env->GetMethodID(userClass, "<init>", "()V");
jfieldID mId = env->GetFieldID(userClass, "id", "J");
jfieldID mUserName = env->GetFieldID(userClass, "userName", "Ljava/lang/String;");
jfieldID mIsMan = env->GetFieldID(userClass, "isMan", "Z");
jfieldID mAge = env->GetFieldID(userClass, "age", "I");
jobject userObject = env->NewObject(userClass, userMethod);
env->SetObjectField(userObject, mUserName, name);
env->SetLongField(userObject, mId, 1001);
env->SetBooleanField(userObject, mIsMan, JNI_TRUE);
env->SetIntField(userObject, mAge, 21);
user = userObject;
}
JNIEXPORT jobject JNICALL Java_HelloJni_getUser(JNIEnv *env, jobject thiz) {
return user;
}
要点:FindClass 使用类名 User(无包名时);字段/方法签名需与 Java 类型一致,如 String 为 Ljava/lang/String;,boolean 为 Z。
示例四:互传 ArrayList<User>
Java 将 ArrayList<User> 传入 C++,C++ 遍历打印后再构造新的 ArrayList 返回。
Java:HelloJni.java
import java.util.ArrayList;
public class HelloJni {
static {
System.loadLibrary("userbean");
}
public native void setUserList(ArrayList<User> userList);
public native ArrayList<User> getUserList();
public static void main(String[] args) {
HelloJni hello = new HelloJni();
ArrayList<User> userList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
userList.add(new User(1000L + i, "LiangYaotian" + i, true, 21));
}
hello.setUserList(userList);
userList = hello.getUserList();
System.out.println("ArrayList<User> construct from C++, then Java print it.....");
for (User u : userList) {
System.out.println("id:" + u.getId() + "; name:" + u.getUserName());
}
}
}
C++:setUserList — 遍历 Java 列表
JNIEXPORT void JNICALL Java_HelloJni_setUserList(JNIEnv *env, jobject thiz, jobject userList) {
jclass cls_arraylist = env->GetObjectClass(userList);
jmethodID arraylist_get = env->GetMethodID(cls_arraylist, "get", "(I)Ljava/lang/Object;");
jmethodID arraylist_size = env->GetMethodID(cls_arraylist, "size", "()I");
jint len = env->CallIntMethod(userList, arraylist_size);
printf("get java ArrayList<User> object by C++, then print it.....\n");
for (jint i = 0; i < len; i++) {
jobject obj_user = env->CallObjectMethod(userList, arraylist_get, i);
jclass cls_user = env->GetObjectClass(obj_user);
jmethodID user_getId = env->GetMethodID(cls_user, "getId", "()J");
jmethodID user_getUserName = env->GetMethodID(cls_user, "getUserName", "()Ljava/lang/String;");
jmethodID user_isMan = env->GetMethodID(cls_user, "isMan", "()Z");
jmethodID user_getAge = env->GetMethodID(cls_user, "getAge", "()I");
jstring name = (jstring) env->CallObjectMethod(obj_user, user_getUserName);
jboolean isCopy;
const char *namePtr = env->GetStringUTFChars(name, &isCopy);
jlong id = env->CallLongMethod(obj_user, user_getId);
jboolean sex = env->CallBooleanMethod(obj_user, user_isMan);
jint age = env->CallIntMethod(obj_user, user_getAge);
printf("Id:%lld; Name:%s; isMan? %d; Age:%d\n", (long long) id, namePtr, sex, age);
env->ReleaseStringUTFChars(name, namePtr);
}
}
C++:getUserList — 构造 Java 列表返回
JNIEXPORT jobject JNICALL Java_HelloJni_getUserList(JNIEnv *env, jobject thiz) {
jclass cls_ArrayList = env->FindClass("java/util/ArrayList");
jmethodID construct = env->GetMethodID(cls_ArrayList, "<init>", "()V");
jobject obj_ArrayList = env->NewObject(cls_ArrayList, construct);
jmethodID arrayList_add = env->GetMethodID(cls_ArrayList, "add", "(Ljava/lang/Object;)Z");
jclass cls_user = env->FindClass("User");
jmethodID construct_user = env->GetMethodID(cls_user, "<init>", "()V");
for (int i = 0; i < 10; i++) {
jobject obj_user = env->NewObject(cls_user, construct_user);
jfieldID user_id = env->GetFieldID(cls_user, "id", "J");
jfieldID user_name = env->GetFieldID(cls_user, "userName", "Ljava/lang/String;");
jfieldID user_isMan = env->GetFieldID(cls_user, "isMan", "Z");
jfieldID user_age = env->GetFieldID(cls_user, "age", "I");
env->SetLongField(obj_user, user_id, i);
env->SetObjectField(obj_user, user_name, env->NewStringUTF("LiangYaoTian"));
env->SetBooleanField(obj_user, user_isMan, JNI_TRUE);
env->SetIntField(obj_user, user_age, 21);
env->CallBooleanMethod(obj_ArrayList, arrayList_add, obj_user);
}
return obj_ArrayList;
}
常见问题
| 问题 | 说明 |
|---|---|
UnsatisfiedLinkError |
库名不对、.so 不在 java.library.path / LD_LIBRARY_PATH |
NoSuchMethodError / 字段找不到 |
方法签名或字段类型与 Java 不一致 |
FindClass 失败 |
有包名时需写 com/example/User;注意类加载器与线程上下文 |
全局 jobject 引用 |
示例三用全局变量保存对象,生产代码应使用 NewGlobalRef / DeleteGlobalRef 管理生命周期 |
参考
- JNI Specification
- System.load 和 System.loadLibrary
- JNI 类型签名说明
- 原文:http://blog.csdn.net/u_xtian/article/details/6033963
维护记录
| 时间 | 修改内容 | 原因 |
|---|---|---|
| 2026-06-19 | 重组章节结构;代码块格式化并添加语法高亮;修正 \n、编译命令空格等笔误;更新 categories/tags;补充站内互链与维护记录 |
原文代码无高亮、排版混乱,不便阅读 |
| 2026-06-19 | 概述中 FFM 链接改为指向 project-panama.md | 新增 Panama 专题文档 |