reverse_xiaoyu

忘记并不可怕,可怕的是你从来就都不知道!

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

  写例程之前先介绍一下代码目录结构吧,以免后面发生找不到library库的路径

             

 

 

   so文件需要与java目录的根目录同级

error1:Error: Could not find or load main class com.clay.example.sample1

error2Exception 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、传递字符串数组

  未完待续

 

posted on 2020-12-16 22:08  Reverse-xiaoyu  阅读(649)  评论(0编辑  收藏  举报