JNI基础概念与环境准备
JNI即Java Native Interface,是Java平台提供的一套标准编程接口,允许Java代码与用其他编程语言(如C、C++)编写的本地代码进行交互。在C++和Java交互的场景中,JNI主要承担桥梁的作用,让C++可以访问Java层的类、方法、对象等元素。

要使用JNI进行开发,首先需要准备对应的环境:安装JDK并配置好环境变量,确保可以正常编译运行Java程序;安装C++编译工具,比如Windows下的MinGW或者Visual Studio,Linux下的GCC等。同时需要了解JNI中常用的数据类型映射,比如Java的int对应JNI的jint,Java的String对应JNI的jstring,这些映射关系是后续编写交互代码的基础。
C++调用Java静态方法
调用Java静态方法的流程相对简单,不需要先创建Java对象,直接通过类引用即可完成调用。下面通过一个示例来演示具体步骤。
第一步:编写Java类并声明本地方法
首先创建一个Java类,在其中声明一个静态方法和一个本地方法,本地方法由C++实现,用于触发对Java静态方法的调用。
public class JniDemo {
// 静态方法,将被C++调用
public static int add(int a, int b) {
return a + b;
}
// 本地方法,C++实现该方法,内部调用add静态方法
public native int callJavaStaticAdd(int a, int b);
static {
// 加载本地动态库,Windows下为JniDemo.dll,Linux下为libJniDemo.so
System.loadLibrary("JniDemo");
}
public static void main(String[] args) {
JniDemo demo = new JniDemo();
int result = demo.callJavaStaticAdd(3, 5);
System.out.println("调用Java静态方法结果:" + result);
}
}
第二步:生成JNI头文件
使用javac编译Java类,然后通过javah命令生成对应的JNI头文件,头文件中会包含本地方法的函数声明。
javac JniDemo.java javah -jni JniDemo
生成的JniDemo.h头文件中会包含类似如下的函数声明:
JNIEXPORT jint JNICALL Java_JniDemo_callJavaStaticAdd (JNIEnv *, jobject, jint, jint);
第三步:实现C++本地方法
编写C++代码实现上述声明的函数,在函数中通过JNIEnv指针找到Java类,再获取静态方法ID,最后调用静态方法。
#include <jni.h>
#include "JniDemo.h"
#include <iostream>
JNIEXPORT jint JNICALL Java_JniDemo_callJavaStaticAdd
(JNIEnv *env, jobject obj, jint a, jint b) {
// 1. 找到对应的Java类,参数是类的全限定名,点换成/
jclass clazz = env->FindClass("JniDemo");
if (clazz == nullptr) {
std::cout << "找不到JniDemo类" << std::endl;
return -1;
}
// 2. 获取静态方法ID,参数分别是类、方法名、方法签名
// 方法签名格式:(参数类型)返回值类型,int类型对应I,所以(int,int)int的签名是(II)I
jmethodID staticMethodId = env->GetStaticMethodID(clazz, "add", "(II)I");
if (staticMethodId == nullptr) {
std::cout << "找不到add静态方法" << std::endl;
return -1;
}
// 3. 调用静态方法,传入类、方法ID、参数
jint result = env->CallStaticIntMethod(clazz, staticMethodId, a, b);
return result;
}
第四步:编译运行
将C++代码编译为动态库,然后运行Java程序即可看到调用结果。Windows下编译命令参考:
g++ -I"%JAVA_HOME%include" -I"%JAVA_HOME%includewin32" -shared -o JniDemo.dll JniDemo.cpp
运行Java程序后,会输出调用Java静态方法结果:8,说明C++成功调用了Java的静态方法。
C++调用Java实例方法
调用Java实例方法需要先创建或者获取到Java对象实例,再通过实例对象调用对应的方法,流程和调用静态方法类似,只是多了获取对象实例的步骤。
Java类定义
修改之前的JniDemo类,添加一个实例方法和对应的本地方法:
public class JniDemo {
// 实例变量
private int num;
// 实例方法
public int multiply(int a) {
return num * a;
}
// 设置num值的方法
public void setNum(int num) {
this.num = num;
}
// 本地方法,调用实例方法multiply
public native int callJavaInstanceMultiply(int a);
static {
System.loadLibrary("JniDemo");
}
public static void main(String[] args) {
JniDemo demo = new JniDemo();
demo.setNum(4);
int result = demo.callJavaInstanceMultiply(5);
System.out.println("调用Java实例方法结果:" + result);
}
}
C++实现代码
在C++的本地方法中,首先获取Java对象实例,再获取实例方法ID,最后调用实例方法。
#include <jni.h>
#include "JniDemo.h"
#include <iostream>
JNIEXPORT jint JNICALL Java_JniDemo_callJavaInstanceMultiply
(JNIEnv *env, jobject obj, jint a) {
// 1. 获取对象对应的类
jclass clazz = env->GetObjectClass(obj);
if (clazz == nullptr) {
std::cout << "获取对象类失败" << std::endl;
return -1;
}
// 2. 获取实例方法ID,multiply方法参数是int,返回int,签名是(I)I
jmethodID instanceMethodId = env->GetMethodID(clazz, "multiply", "(I)I");
if (instanceMethodId == nullptr) {
std::cout << "找不到multiply实例方法" << std::endl;
return -1;
}
// 3. 调用实例方法,传入对象实例、方法ID、参数
jint result = env->CallIntMethod(obj, instanceMethodId, a);
return result;
}
运行Java程序后,会输出调用Java实例方法结果:20,说明C++成功调用了Java的实例方法。
C++中创建Java对象
在C++中也可以主动创建Java对象,不需要依赖Java层先创建好实例,主要步骤是找到类的构造方法,然后调用构造方法创建对象。
Java类定义
创建一个简单的Java类User,包含构造方法和实例方法:
public class User {
private String name;
private int age;
// 构造方法
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 获取用户信息的方法
public String getUserInfo() {
return "姓名:" + name + ",年龄:" + age;
}
}
修改JniDemo类,添加创建User对象的本地方法:
public class JniDemo {
// 本地方法,创建User对象并调用getUserInfo方法
public native String createJavaUser();
static {
System.loadLibrary("JniDemo");
}
public static void main(String[] args) {
JniDemo demo = new JniDemo();
String userInfo = demo.createJavaUser();
System.out.println("创建的Java用户信息:" + userInfo);
}
}
C++实现创建对象逻辑
构造方法在JNI中对应的方法名是<init>,通过获取构造方法ID,再调用NewObject方法即可创建对象。
#include <jni.h>
#include "JniDemo.h"
#include <iostream>
JNIEXPORT jstring JNICALL Java_JniDemo_createJavaUser
(JNIEnv *env, jobject obj) {
// 1. 找到User类
jclass userClass = env->FindClass("User");
if (userClass == nullptr) {
std::cout << "找不到User类" << std::endl;
return nullptr;
}
// 2. 获取构造方法ID,构造方法参数是String和int,返回void,签名是(Ljava/lang/String;I)V
jmethodID constructorId = env->GetMethodID(userClass, "<init>", "(Ljava/lang/String;I)V");
if (constructorId == nullptr) {
std::cout << "找不到User构造方法" << std::endl;
return nullptr;
}
// 3. 创建构造方法的参数,jstring类型需要转换
jstring name = env->NewStringUTF("张三");
jint age = 25;
// 4. 调用构造方法创建User对象
jobject userObj = env->NewObject(userClass, constructorId, name, age);
if (userObj == nullptr) {
std::cout << "创建User对象失败" << std::endl;
return nullptr;
}
// 5. 获取getUserInfo方法ID,返回String,签名是()Ljava/lang/String;
jmethodID getUserInfoId = env->GetMethodID(userClass, "getUserInfo", "()Ljava/lang/String;");
if (getUserInfoId == nullptr) {
std::cout << "找不到getUserInfo方法" << std::endl;
return nullptr;
}
// 6. 调用getUserInfo方法获取用户信息
jstring userInfo = (jstring)env->CallObjectMethod(userObj, getUserInfoId);
// 释放局部引用,避免内存泄漏
env->DeleteLocalRef(name);
env->DeleteLocalRef(userObj);
return userInfo;
}
运行Java程序后,会输出创建的Java用户信息:姓名:张三,年龄:25,说明C++成功创建了Java对象并调用了其方法。
JNI交互注意事项
- JNI中的引用分为局部引用和全局引用,局部引用在函数执行结束后会自动释放,如果需要长期保存对象,需要转换为全局引用。
- 方法签名的编写要准确,错误的签名会导致找不到方法,常见的类型签名:void对应V,boolean对应Z,byte对应B,char对应C,short对应S,int对应I,long对应J,float对应F,double对应D,类对应L全限定名;,数组对应[类型。
- 及时释放不再使用的JNI引用,避免造成内存泄漏,尤其是创建大量对象或者频繁调用本地方法时。
- JNIEnv指针是每个线程独有的,不能跨线程使用,如果需要跨线程操作,需要通过JavaVM指针获取对应线程的JNIEnv。