写例程之前先介绍一下代码目录结构吧,以免后面发生找不到library库的路径
so文件需要与java目录的根目录同级
error1:Error: Could not find or load main class com.clay.example.sample1
error2:Exception in thread "main" java.lang.UnsatisfiedLinkError: no sample1 in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at com.clay.example.sample1.<clinit>(sample1.java:6)
例程编写与编译请看下面的例子吧,代码结构目录需要严格对照上面的目录(当然你有自己的习惯也可以按照自己的习惯来,能编译通过都行),按照个人喜好来,不过新手的话,建议保持与上诉目录相同,不然会出现自己都不知道为啥的错误。
Project目录下的 stringJNI是下例(传递字符串)的工程目录名称
com/clay/example 是java 目录
package com.clay.example 是java程序的包名
cpp目录,我们javah 编译生成 .h 文件目录以及实现 .h 方法的 点C或者 点 cpp文件
lib目录,我们生成的动态库目录,个人喜欢这样将其分开,保持代码目录的整洁性
1、传递字符串
本例程将由Java程序向C程序传入一个字符串,C程序对该字符串转成大写形式后回传给Java程序。
Java源程序如下。
1 package com.clay.example;
2
3 public class sample1 {
4
5 static {
6 System.loadLibrary("sample1");
7 }
8
9 public native String stringMethod(String text);
10
11 public static void main(String[] args) {
12 sample1 sample = new sample1();
13 String text = sample.stringMethod("I love Java");
14
15 System.out.println("stringMethod: " + text);
16 }
17 }
sample1.java以“I love Java”为参数调用libsample1.so中的函数stringMethod(),在得到返回的字符串后打印输出。
写完之后,在 com/clay/example 目录下 执行命令:javac sample1.java 编译我们写的 java 程序,生成 sample.class 文件
zhengchuanyu@CLAY:~/Projects/stringJNI/src$ javah -d cpp/ -classpath . -jni -v com.clay.example.sample1
[Creating file RegularFileObject[cpp/com_clay_example_sample1.h]]
切记:javah -d cpp/ -classpath . -jni -v com.clay.example.sample1 此命令生成的 jni 文件的头文件必须在 java 程序的根目录执行,我们这里的根目录在 src 目录下
此命令相关参数 -d <path> -classpath <path> -jni -v,请看上一篇博文,其中有详细的介绍
生成的 .h 文件如下所示
1 /* DO NOT EDIT THIS FILE - it is machine generated */
2 #include <jni.h>
3 /* Header for class com_clay_example_sample1 */
4
5 #ifndef _Included_com_clay_example_sample1
6 #define _Included_com_clay_example_sample1
7 #ifdef __cplusplus
8 extern "C" {
9 #endif
10 /*
11 * Class: com_clay_example_sample1
12 * Method: stringMethod
13 * Signature: (Ljava/lang/String;)Ljava/lang/String;
14 */
15 JNIEXPORT jstring JNICALL Java_com_clay_example_sample1_stringMethod
16 (JNIEnv *, jobject, jstring);
17
18 #ifdef __cplusplus
19 }
20 #endif
21 #endif
下面 c 代码的实现:jni_string.c
1 #include <jni.h>
2 #include "com_clay_example_sample1.h"
3 #include <stdio.h>
4 #include <string.h>
5 #include <ctype.h>
6
7 JNIEXPORT jstring JNICALL JNICALL Java_com_clay_example_sample1_stringMethod
8 (JNIEnv *env, jobject obj, jstring string)
9 {
10 int i = 0;
11
12 const char* str = (*env)->GetStringUTFChars(env, string, 0);
13 char cap[128];
14
15 strcpy(cap, str);
16 (*env)->ReleaseStringUTFChars(env, string, str);
17
18 for(i = 0; i < strlen(cap); i ++)
19 {
20 *(cap + i) = (char)toupper(*(cap + i));
21 }
22
23 return (*env)->NewStringUTF(env, cap);
24 }
首先请注意函数头部分,函数接收一个jstring 类 型的输入参数,并输出一个jstring类型的参数。jstring 是 jni.h 中定义的数据类型,是JNI框架内特有的字符串类型。
程序的第 12 行是从 JNI 调用上下文中获取 UTF 编码的输入字符,将其放在指针 str 所指向的一段内存中。
第 16 行是释放这段内存。
第 23 行是将经过大写转换的字符串予以返回,这一句使用 NewStringUTF() 函数,将 C 语言的字符串指针转换为 JNI 的 jstring 类型。 JNIEnv 也是在 jni.h 中定义的,代表 JNI 调用的上下文。 GetStringUTFChars()、ReleaseStringUTFChars() 和 NewStringUTF() 均是 JNIEnv 的函数。
后面会专门写一篇有关 JNI 相关函数的博文,前期只做例程学习参考,无需理解。
将 jni_string.c 编译生成动态库
命令: ~/Projects/stringJNI/src$ gcc -shared -fPIC -I /opt/jdk1.8.0_221/include/ -I /opt/jdk1.8.0_221/include/linux/ cpp/jni_string.c -o lib/libsample1.so
运行程序,在 src 目录下(Java 程序所在的根目录)执行 :~/Projects/stringJNI/src$ java -Djava.library.path=lib/ com.clay.example.sample1
-Djava.library.path=<path> 是指定动态库的路径
否则会出现 上诉的 error1:Error: Could not find or load main class com.clay.example.sample1
至于为什么会出现这些错误,只有自己多实践就知道为什么了,不要问我为什么知道,我也不知道,一开始目录我也是乱写,经过经验摸索总结而出,这其实就是 JNI 编写规范的一部分。
运行结果如下:
输出:stringMethod: I LOVE JAVA
后面的例程中,我将只列出代码目录结构,不再详细描述编译,本篇第一小节加上两篇博文有专门描述编译以及编写规则
2、传递整型数组
代码目录结构:
本节例程将首次尝试在JNI框架内启用数组:C 程序向 Java 程序返回一个定长的整型数组成的数组,Java程序将该数组打印输出。
Java程序的源代码如下。
1 package com.clay.example;
2
3 public class Sample2 {
4
5 static {
6 System.loadLibrary("Sample2");
7 }
8
9
10 public native int[] intArrayMethod();
11
12 public static void main(String[] args) {
13
14 Sample2 sample = new Sample2();
15 int[] nums = sample.intArrayMethod();
16
17 for(int i = 0; i < nums.length; i ++) {
18 System.out.println("intArrayMethod: " + nums[i]);
19 }
20 }
21 }
jni_intArrayMethodImpl.c
1 #include <jni.h>
2 #include "com_clay_example_Sample2.h"
3 #include <stdio.h>
4
5 JNIEXPORT jintArray JNICALL Java_com_clay_example_Sample2_intArrayMethod
6 (JNIEnv *env, jobject obj)
7 {
8 int i = 0;
9
10 jintArray array;//定义数组对象
11
12 array = (*env)-> NewIntArray(env, 10);
13
14 for(i = 0; i < 10; i++)
15 {
16 (*env)->SetIntArrayRegion(env, array, i, 1, &i);
17 }
18
19 /* 获取数组对象的元素个数 */
20
21 int len = (*env)->GetArrayLength(env, array);
22
23 /* 获取数组中的所有元素 */
24 jint* elems = (*env)-> GetIntArrayElements(env, array, 0);
25
26 for( i = 0; i < len; i++)
27 {
28 printf("jni_intArrayMethodImpl: elems[%d] = %d\n", i, elems[i]);
29 }
30
31 return array;
32 }
jni_intArrayMethodImpl.c 涉及了两个jni.h定义的整型数相关的数据类型:jint和jintArray,jint是在JNI框架内特有的整数类型。程序的第 12 行开辟出一个长度为10 的jint数组,然后依次向该数组中放入元素0-9。21-24行不是程序的必须部分,纯粹是为了演示GetArrayLength() 和GetIntArrayElements()这两个函数的使用方法,前者是获取数组长度,后者则是获取数组的首地址以便于遍历数组。
3、传递字符串数组
未完待续