本文共 8200 字,大约阅读时间需要 27 分钟。
JNI
JNI概述
JNI(java native interface),提供了java调用本地库(c/c++编写的生成的动态链接库.dll
)的接口
java项目中,本地方法接口使用native
字段标识
入门案例 基于Windows操作系统
- java项目编写接口
package net.cn.console.natlib;public class Hello { public native String hello();}
- 生成头文件
- 使用
javah
命令,如果找不到javah命令,则可能是新版本jdk将javah命令集成到javac
命令里面 - 使用
java -h <dir> <source>
- dir 生成头文件所在目录名
- source 源文件
- 使用
javac -h jni net.cn.console.natlib.Hello.java
编译成功会在当前工作目录下的 jni目录下生成头文件net_cn_console_natlib_Hello.h
文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */#include/* Header for class net_cn_console_natlib_Hello */#ifndef _Included_net_cn_console_natlib_Hello#define _Included_net_cn_console_natlib_Hello#ifdef __cplusplusextern "C" {#endif/* * Class: net_cn_console_natlib_Hello * Method: hello * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_net_cn_console_natlib_Hello_hello (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
- 新建一个c++项目,项目类型为动态链接库项目
- 将生成的头文件导入到项目中
- 除了生成的头文件,还需要以下文件
JAVA_HOME\include\jni.h
JAVA_HOME\include\win32\jni_md.h
- 除了生成的头文件,还需要以下文件
这里为了方便直接将这两个头文件导入到当前项目中
如果net_cn_console_natlib_Hello.h
文件报错,一般是头文件引入方式不正确引起
/* DO NOT EDIT THIS FILE - it is machine generated *///将这里的include修改为 include "jni.h"#include "jni.h"/* Header for class net_cn_console_natlib_Hello */#ifndef _Included_net_cn_console_natlib_Hello#define _Included_net_cn_console_natlib_Hello#ifdef __cplusplus
- 新建源文件
net_cn_console_natlib_Hello.cpp
,实现net_cn_console_natlib_Hello.h
中的方法
#include "pch.h" //预编译相关,vs报错后提示引入这个头文件#include "net_cn_console_natlib_Hello.h"#include#include using namespace std;JNIEXPORT jstring JNICALL Java_net_cn_console_natlib_Hello_hello(JNIEnv*env, jobject) { cout << "this is print by c++"< NewStringUTF((const char*)res); return value;}
- 配置项目,生成
dll
,这里配置为release
,x64
- 项目右键,生成
会生成jni-demo1.dll
文件
- 将生成的dll文件导入java项目中,这里使用idea
- 编写测试代码
public class MainApp { static { System.loadLibrary("jni-demo1"); } public static void main(String[] args) { Hello hello=new Hello(); final String hello1 = hello.hello(); System.out.println(hello1); }}
如果控制台成功打印以下内容,说明程序运行正常
this is print by c++hello JNI
linux环境
linux下动态链接库的后缀一般是.so
(shared object)
使用gcc编译的话,直接将相关文件放在同一个目录下,使用下面命令编译成动态链接库(.so)
gcc -fPIC -shared net_cn_console_natlib_Hello.cpp -o libdemo.so
jni常用操作
基本数据类型
本地方法中,java数据类型都会以j*
的格式命名,其中基本数据类型和c++数据类型基本可以无缝衔接,
String
本地方法中,使用jstring
表示java中的String类型,需要转换成c++的char*
才能处理
- jstring-> char*
const char * nametmp =env->GetStringUTFChars(name,NULL);
- char* -> jstring
char str[]="hello jni";jstring result= env->NewStringUTF(str);
对象操作
对象操作和java中的反射比较类似
- 获取类信息
jclass personClass = env->FindClass("com/pojo/User");//也可以使用下面的方法直接获取传入对象的类jclass personClass = env->GetObjectClass(person);
- 获取/设置属性
- 获取属性需要使用env的
Get*Field()
来进行,如GetIntField,GetObjectField… - 这个方法需要两个参数,一个是对象,另一个是属性的id
- 属性id可以通过
GetFieldID
来获取
- 获取属性需要使用env的
//获取属性idjfieldID nameId = env->GetFieldID(personClass,"name","Ljava/lang/String;");jfieldID ageId = env->GetFieldID(personClass,"age","I");//获取对象属性jstring name=static_cast(env->GetObjectField(person,nameId));jint age = env->GetIntField(person,ageId);//设置对象属性env->SetObjectField(person,nameId,newName);env->SetIntField(person,ageId,age);
🔴 ⚠️ 这里有个大坑: 获取属性id时,"Ljava/lang/String;"这个参数最后面的分号“;”千万不能省略,必须加,否则就会找不到属性id
- 执行方法
- 执行方法需要使用
Call*Method
或者Call*MethodA
,*
代表返回类型,没有A代表没有参数,有A代表需要参数 - 需要方法Id,方法id使用
GetMethodID
方法获取- 这个方法需要传入类信息,方法名和参数签名
- 参数签名示例
- 执行方法需要使用
参数 | 返回值 | 参数签名字符串 |
---|---|---|
void | void | ()V |
int,String | void | (I;Ljava/lang/String;)V |
void | String | ()Ljava/lang/String; |
示例,调用对象的toString
方法
jmethodID toStingId = env->GetMethodID(personClass,"toString","()Ljava/lang/String;");jstring user2Str = (jstring)env->CallObjectMethod(user2,toStingId);
- 创建对象
- 创建对象本质上也是在调用对象的方法,需要先获取构造方法id,再用
NewObject
创建对象
- 创建对象本质上也是在调用对象的方法,需要先获取构造方法id,再用
//使用无参数构造器创建对象jmethodID initMethodIdNoArgs = env->GetMethodID(personClass,"","()V");jobject user1 = env->NewObject(personClass,initMethodIdNoArgs);//使用有参数构造创建对象 //创建参数数组jvalue argName;argName.l=env->NewStringUTF("zhangsna");jvalue argAge;argAge.i=55;jvalue args[]={argName,argAge};jmethodID initMethodIdWithArgs = env->GetMethodID(personClass," ","(Ljava/lang/String;I)V");jobject user2= env->NewObjectA(personClass,initMethodIdWithArgs,args);
ℹ️ 上述示例中参数数组的数据类型是 jvalue
,jvalue
是一个union
,定义如下
typedef union jvalue { jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l;} jvalue;
示例
java项目准备
一个User
对象
package com.pojo;public class User { private String tel; private int age; private String name; public User(){ } public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; }}
native
方法类
public class JniDemo { public native String hello(); public native int avg(int[] ints); public native void sort(int[] data ); public native User proc(User user);}
下面演示如何在c++项目中实现这些方法
- hello(),返回一个字符串
/* * return a String "hello jni" * Class: JniDemo2 * Method: hello * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_JniDemo2_hello (JNIEnv * env, jobject obj) { char str[]="hello jni"; jstring result= env->NewStringUTF(str); return result;}
- avg(int[] ints)
JNIEXPORT jint JNICALL Java_JniDemo2_avg (JNIEnv * env, jobject obj, jintArray data) { jint length=0,sum=0; length = env->GetArrayLength(data); jint* ints = env->GetIntArrayElements(data,0); for (size_t i = 0; i < length; i++) { sum+=ints[i]; } return sum/length; }
- sort(int[] data ) 数组排序
JNIEXPORT void JNICALL Java_JniDemo2_sort (JNIEnv * env, jobject obj, jintArray data) { jint len = env->GetArrayLength(data); jint* value = env->GetIntArrayElements(data,0); for (size_t i = 0; i < len; i++) { for (size_t j = i; j < len; j++) { if (value[i]ReleaseIntArrayElements(data,value,0); }
- proc(User user)
- 方法传入一个
User
对象,本地方法修改其属性值;方法内部创建一个新的User
对象并返回
JNIEXPORT jobject JNICALL Java_JniDemo2_proc(JNIEnv * env, jobject instace, jobject person){ jclass personClass = env->FindClass("com/pojo/User"); //也可以使用下面的方法直接获取传入对象的类 // jclass personClass = env->GetObjectClass(person); cout<<"userClass "<< GetFieldID(personClass,"name","Ljava/lang/String;"); jfieldID ageId = env->GetFieldID(personClass,"age","I"); jstring name=static_cast (env->GetObjectField(person,nameId)); // 修改传入的User对象 //modify name jboolean isCopy=0; // cout<<"char utf"< GetStringUTFChars(name,NULL); cout< < NewStringUTF(name2); env->SetObjectField(person,nameId,nameJstr); env->ReleaseStringUTFChars(name,nametmp); // modify age jint age = env->GetIntField(person,ageId); age++; env->SetIntField(person,ageId,age); // 使用没有参数构造器创建一个User对象 jmethodID initMethodIdNoArgs = env->GetMethodID(personClass," ","()V"); jmethodID initMethodIdWithArgs = env->GetMethodID(personClass," ","(Ljava/lang/String;I)V"); jobject user1 = env->NewObject(personClass,initMethodIdNoArgs); env->SetIntField(user1,ageId,12); env->SetObjectField(user1,nameId,env->NewStringUTF("lisi")); // 使用有参数构造器创建一个User对象 jvalue argName; argName.l=env->NewStringUTF("zhangsna"); jvalue argAge; argAge.i=55; jvalue args[]={argName,argAge}; jobject user2= env->NewObjectA(personClass,initMethodIdWithArgs,args); jmethodID toStingId = env->GetMethodID(personClass,"toString","()Ljava/lang/String;"); jstring user2Str = (jstring)env->CallObjectMethod(user2,toStingId); const char* user2Chars= env->GetStringUTFChars(user2Str,0); cout< <
参考资料
- https://www.runoob.com/w3cnote/jni-getting-started-tutorials.html
- https://blog.csdn.net/qhs1573/article/details/87704431
- https://www.cnblogs.com/yongdaimi/p/14023154.html
转载地址:https://console.blog.csdn.net/article/details/116590605 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!