JNI基本使用

文章介绍了JNI的基本流程,包括如何生成头文件、C/C++实现函数并编译动态链接库。详细讲解了Java与C/C++之间的类型转换,如jstring到constchar*的转换,以及如何使用JNIEnv接口操作数组。此外,还阐述了如何访问Java类的属性和方法,包括获取jfieldID、调用实例和静态方法,以及构造Java对象。
摘要由CSDN通过智能技术生成

编译运行

首先介绍一些编写JNI的大致流程,可以直接调过这部分。

生成头文件

这步可以不做,但是JNI对C/C++函数的命名有严格要求,同时函数的命名会很长,所以还是直接生成头文件,然后从头文件里边复制函数声明。

使用下面的命令生成头文件(记得代码里要有native方法,否则是无法生成的)

javac -h <directory> <source_files>
# example
javac -h . Student.java
C/C++实现函数并编译出动态链接库

执行.class文件

在-Djava.library.path参数中指明lib*.so文件的路径。如果是windows系统,动态链接库会叫*.dll。具体自行了解。下面是我运行时的脚本。

java -Djava.library.path=./lib -classpath ./src/classes com.peijunbo.jnidemo.Main

类型介绍及转换

基本类型

下面是一个类型对应表

Java 类型

Native 类型

描述

boolean

jboolean

unsigned 8 bits

byte

jbyte

signed 8 bits

char

jchar

unsigned 16 bits

short

jshort

signed 16 bits

int

jint

signed 32 bits

long

jlong

signed 64 bits

float

jfloat

32 bits

double

jdouble

64 bits

void

void

not applicable

jstring -- const char *
// jstring 转为 const char *
// 参数说明:JNIEnv *env, jstring jstr
const char *str = env->GetStringUTFChars(/*str:*/jstr, /*isCopy:*/NULL);

// const char * 转为 jstring
jstring jstr = env->NewStringUTF(str);
j<Type>Array -- <Type> *
// jintArray to jint *
// 参数说明:jintArray jarr
jboolean b;
jint *arr = env->GetIntArrayElements(jarr, /*isCopy:*/&b);
int len = env->GetArrayLength(jarr);

// 创建 jintArray
jintArray jarr = env->NewIntArray(2);
jint *arr = env->GetIntArrayElements(jarr, NULL);
arr[0] = 2;
arr[1] = 3;
env->ReleaseIntArrayElements(jarr, arr, /*mode:*/0);
isCopy参数

官方:因为返回的elems数组可能会是Java数组的拷贝,所以对返回数组的更改不一定会反映到原数组中,直到Release<PrimitiveType>ArrayElements()被调用。

大致来说就是如果返回的elems是拷贝,会将传给isCopy的jboolean赋值为JNI_TRUE否则赋值为JNI_FALSE。当返回的是拷贝时,对数组的修改是无法直接体现在原java数组中的,只有调用release函数时才会处理你所进行的修改。

我只遇到过TRUE的情况。个人猜测可能是在内存不充足时为了节省内存会放弃复制并返回原数组。

mode参数

官方:mode参数决定这个数组应该如何被释放。当elems不是原数组的复制时,mode参数不会起作用。否则mode参数的作用参考下表:

如果isCopy为假,说明elems就是原数组,修改会直接体现出来,自然而然mode参数没有意义。

模式

行为

0

将内容复制回去并释放elems

JNI_COMMIT

将内容复制回去但不释放elems

JNI_ABORT

释放elems且不将修改复制回原数组

Java类型签名

JNI使用Java虚拟机的类型签名表示。下表显示了这些类型签名。

Type Signature

Java Type

Z

boolean

B

byte

C

char

S

short

I

int

J

long

F

float

D

double

L fully-qualified-class ;

fully-qualified-class

[ type

type[]

( arg-types ) ret-type

method type

V

void(return type)

例如String类的签名Ljava/lang/String;

例如下面的java方法:

long foo(int n, String s, int[] arr);

有着下边这样的类型签名:

(ILjava/lang/String;[I)J

访问Java类属性

函数说明
  • 获取jfieldID

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)

jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)

参数const char *sig是该属性对应的类型签名。

  • 获取属性值

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID)

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID)

  • 修改属性值

void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value)

void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value)

使用方法

获取jclass->获取jfieldID->根据jfieldID获取属性值和修改属性值

  • 获取属性值

jclass cls = env->GetObjectClass(parent); // jclass
jfieldID fieldID = env->GetFieldID(cls, "name", "Ljava/lang/String;"); // 获取jfieldID
jstring jstr = (jstring)env->GetObjectField(parent, fieldID);
const char *str = env->GetStringUTFChars(jstr, NULL);//获取到属性
  • 修改属性值

// 延续上一个代码块
char cname[] = "这是新的名字";
jstring jname = env->NewStringUTF(cname);// 创建新的jstring
env->SetObjectField(parent, fieldID, jname);// 修改属性

访问方法

函数说明
  • 调用<静态/实例>方法

下面函数中的[A/V]可以没有,分别是三个函数,什么都不加,加A和加V。三者参数略有不同,可以参考其他参数部分。

NativeType Call<type>Method[A/V](JNIEnv *env, jobject obj, jmethodID methodID, ...)

NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

二者的使用很类似,根据需要传入参数即可。

  • 一个很神奇的调用函数(主要是我没有搞懂)

CallNonvirtual<type>Method[A/V]

与上一个函数的区别:Call<type>Method 例程根据对象的类或接口调用方法,而 CallNonvirtual<type>Method 例程根据 jclass 参数指定的类调用方法,从中获取方法 ID。 方法 ID 必须从对象的实际类或其超类型之一获得。

以上为官方所说,但我测试的结果是只与jmethodID参数有关,甚至可以调用这个类不存在的方法。
其他参数

以Call<type>Method[A/V]为例

  • Call<type>Method 直接写参数

  • Call<type>MethodA 传入参数数组

  • Call<type>MethodV 传入va_list

使用方法

获取jclass->获取jmethodID->根据jmethodID调用方法;

// jobject obj
jclass cls = env->GetObjectClass(obj);// 获取jclass
jmethodID jMethod = env->GetMethodID(cls, "javaMethod", "(I)V");// 通过jclass获取实例方法的ID
jmethodID jStatic = env->GetStaticMethodID(cls, "staticJavaMethod", "(I)I");// 通过jclass获取静态方法的ID
env->CallVoidMethod(obj, jMethod, 2);// 调用实例方法
jint ret = env->CallStaticIntMethod(cls, jStatic, 2); // 调用静态方法

构造Java对象

函数说明

jobject NewObject[A/V](JNIEnv *env, jclass clazz, jmethodID methodID, ...)

[A/V]的参数与方法调用时的相同。

使用方法

获取jclass->获取<init>方法的jmethodID->调用NewObject

// Parent类构造函数
public Parent(int i) {
    this.id = i;
}
jmethodID initMethod = env->GetMethodID(cls, "<init>", "(I)V");
jint id = 233;
jobject parent = env->NewObject(cls, initMethod, id);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值