JNI入门值得注意的一些小东西(C语言)

一、java调用C代码步骤

第一步:编写java代码

写一个Java类,在成员位置声明需要用到的C程序的函数

public class TestInt {

    public native int testInt(int num1,int num2);
    
}

上述代码就声明了一个名为testInt的本地函数,这个函数的实现将在C程序中实现。

第二步:编译java文件生成class文件,并由此生成.h文件

在命令行中在TestInt.java文件所在地用javac TestInt.java命令,生成TestInt.class文件,或者用eclipse等java编辑器将其编译也行,反正就是要得到TestInt.class文件。
然后在该目录下用javah TestInt命令生成TestInt.h文件,TestInt.h文件便是我们编写C代码的前提。

第三步:编写C代码并生成可被java调用的 dll文件(利用vs 2010)

1、打开TestInt.h,可以看到其中已经生成了一部分代码

flutter获取Android路劲_JNI

这个函数就是从TestInt.class中得到的,下面就按照这个将testInt函数在C程序中实现即可。

2、在vs2010中新建项目,选择Win32项目,然后给项目取一个名字(这个名字和最后生成的dll文件的名字一致)

依次选择dll和空项目

flutter获取Android路劲_JNI数组参数传递_02


然后点击完成。

接下来将刚刚生成的TestInt.h文件复制到这个项目目录下

flutter获取Android路劲_JNI_03


如图,放到TestInt文件夹(即项目目录)中

并去JDK文件夹中的include文件夹内的jni.h文件和include//win32文件夹内的jni_md.h文件也复制到项目目录中。

然后去vs中将那些头文件添加到当前工程中

flutter获取Android路劲_flutter获取Android路劲_04


添加完成后,将TestInt.h的函数声明中的#include<jni.h>修改为#include “jni.h”,可消除错误。然后新建一个类,用于编写C代码

flutter获取Android路劲_JNI(C语言)_05

在产生的.cpp文件中编写c代码
将刚刚TestInt.h中的函数部分直接复制过来,写上参数和函数体,在函数体类编写实现代码

JNIEXPORT jint JNICALL Java_TestInt_testInt
(JNIEnv *env, jobject obj ,jint num1,jint num2) {
    
	int sum = 0;
	 sum = num1 + num2;
	return sum;
}

然后右键点击解决方案->“属性”,点配置管理器,将配置信息改成如下图所示

flutter获取Android路劲_JNI_06


然后右键点击项目->“生成”,即可在项目目录中看到TestInt.dll文件

flutter获取Android路劲_flutter获取Android路劲_07


然后在java代码中用System.loadlibrary("TestInt");并在编辑器中将dll文件添加到库中,即可调用testInt方法。

[注] 如果在java代码中声明的native函数是非静态方法,那么在TestInt.h中产生的函数的第二个参数是jobject,这意味着这个函数到时候调用时是用对象调用,若在java代码中声明的函数是静态方法,那么在TestInt.h中产生的函数的第二个参数则是jclass,这意味着该函数到时候将是用类名直接调用。

二、数组参数传递时值得注意的一些小东西

1、函数中的参数为数组时要注意参数传递

比如说将Java中的一个int类型的数组传进来的时候,它的类型是jintArray
必须通过env参数将其用C语言中的数组进行处理
如:传进来的数组是jintArray arr,则应该建立一个指针来指向它

jint *arrPointer = env->GetIntArrayElements(arr,0);

这样就能操作arrPointer从而操作数组元素
不过最后必须释放内存才能将修改后的数组返回给jintArray arr

env->ReleaseIntArrayElements(arr,arrPointer,0);
//以上是释放内存的语句。用于取消C代码和java代码中数组数据的同步。

2、C中生成一个数组返回给java

可参考以下代码

JNIEXPORT jintArray JNICALL Test_jnitest_JniTest_outputArray
(JNIEnv *env, jobject jobj, jint len) {
	// 创建一个jintArray数组变量
	jintArray jint_Arr = (*env)->NewIntArray(env, len);
	// 将jintArray转换成c的jint*指针进行数组赋值
	jint *elements = (*env)->GetIntArrayElements(env, jint_Arr, NULL);
	int i = 0;
	for (; i < len; i++) {
		elements[i] = i;
	}
	// 将C中的创建修改的数组同步到Java中
	(*env)->ReleaseIntArrayElements(env, jint_Arr, elements, 0);
	return jint_Arr;
}

3、如何处理java中传进来的二维数组

java代码如下:

public class Test {
    public native int getArrayElement(int[][] arr);

    public static void main(String [] args){

        System.loadLibrary("ObjectArrayTest");
        Test test = new Test();
        int[][] array = new int[][]{{1,2,3},{4,5,6}};
        int num = test.getArrayElement(array);
        System.out.println(num);

    }

C语言代码如下:

#include "Taste.h"
#include "jni.h"
#include "Test.h"

/*
 * Class:     Test
 * Method:    getArrayElement
 * Signature: ([[I)I
 */
JNIEXPORT jint JNICALL Java_Test_getArrayElement
  (JNIEnv *env, jobject obj, jobjectArray arr)
{
	//获取二维数组的长度
	int len = env->GetArrayLength(arr);
	printf("len is%d\n",len);
	//获取二维数组中存的第一个一维数组
	jintArray arr1 = (jintArray)env->GetObjectArrayElement(arr,0);
	//获取一维数组的长度
	int len2 = env->GetArrayLength(arr1);
	printf("len2 is%d\n",len2);
	jint *arr1Pointer = env->GetIntArrayElements(arr1,0);
	//通过jint类型的指针引用该一维数组,并打印该数组中的元素
	printf("the first element in arr1 is %d\n",arr1Pointer[0]);
	printf("the second element in arr1 is %d\n",arr1Pointer[1]);
	printf("the third element in arr1 is %d\n",arr1Pointer[2]);

	//获取二维数组中存放的第二个一维数组
	jintArray arr2 = (jintArray)env->GetObjectArrayElement(arr,1);
	jint *arr2Pointer = env->GetIntArrayElements(arr2,0);
	//打印该数组的元素
	printf("the first element in arr2 is %d\n",arr2Pointer[0]);
	printf("the second element in arr2 is %d\n",arr2Pointer[1]);
	printf("the third element in arr2 is %d\n",arr2Pointer[2]);

	return arr1Pointer[0];
}

运行得到的结果为:

1
len is2
len2 is3
the first element in arr1 is 1
the second element in arr1 is 2
the third element in arr1 is 3
the first element in arr2 is 4
the second element in arr2 is 5
the third element in arr2 is 6

但是以上只是读取了Java那个二维数组的元素,并没有真正改变那个二维数组,要想真正改变那个数组中的元素,必须release语句实现。
我在C代码中添加以下语句:

//将第一个元素改变为10
	arr1Pointer[0] = 10;
	printf("the first element in arr1 is %d\n",arr1Pointer[0]);

上条打印语句的打印结果为:

the first element in arr1 is 10

说明此时arr1Pointer[0]的值确实已经改变了,但是并不知道Java中那个数组到底改变没有。

在Java代码中添加打印语句,检查上述C代码是否改变了Java中原有数组的元素值。

System.out.println("the first element in array is :"+array[0][0]);

打印结果为:

the first element in array is :1

因此单纯改变arr1Pointer并不能改变Java中二维数组array的元素值。只是获取了它的元素值。
在C代码中添加如下代码后成功改变了Java中那个二维数组的元素值

env->ReleaseIntArrayElements(arr1,arr1Pointer, 0);

再次打印的结果为:

the first element in array is :10