第三章之刻意练习
Practice 1
在Java侧初始化两条提示语句,一个提示输入姓名,另一个提示输入住址,然后编写一个native方法,将其中的提示语句传给native方法,然后再native方法中获取输入,将输入的内容返回给Java侧,在Java侧打印native方法中输入的内容。(当然有时间的朋友可以尝试使用静态注册JNI方法和动态注册JNI方法这两种方式)
静态注册方式
** 1.1 Java侧代码: **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Practice1 { private native String getInformation(String prompt); public static void main(String[] args) { String NamePrompt = "Please enter your name:"; String AddrPrompt = "Please enter your addr:"; Practice1 p = new Practice1(); String Name = p.getInformation(NamePrompt); String Addr = p.getInformation(AddrPrompt); System.out.println("Name: " + Name); System.out.println("Addr: " + Addr); } static { System.loadLibrary("Practice1"); } } |
** 1.2 javah -jni产生的头文件 **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Practice1 */ #ifndef _Included_Practice1 #define _Included_Practice1 #ifdef __cplusplus extern "C" { #endif /* * Class: Practice1 * Method: getInformation * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_Practice1_getInformation (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif |
** 1.3 静态注册方法编写 **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <jni.h> #include <stdio.h> #include <string.h> #include "Practice1.h" JNIEXPORT jstring JNICALL Java_Practice1_getInformation (JNIEnv * env, jobject obj, jstring prompt) { // 下面这里用jbyte * 和 char *都是可以的 char * c_prompt; char input[128]; c_prompt = (*env)->GetStringUTFChars(env, prompt, NULL); if(c_prompt == NULL) { printf("GetStringUTFChars return NULL.\n"); return NULL; } printf("%s", c_prompt); // 这里记得释放字符串占用的空间 (*env)->ReleaseStringUTFChars(env, prompt, c_prompt); // scanf("%s", input); gets(input); // 返回创建的String对象 return (*env)->NewStringUTF(env, input); } |
动态注册方式
动态注册JNI方法,签名的Java文件不用改,需要改的是Native侧的实现部分,下面是修改后的c文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include <jni.h> #include <stdio.h> JNIEXPORT jstring JNICALL native_getInformation(JNIEnv * env, jobject obj, jstring prompt) { jbyte * c_prompt; jbyte input[128]; c_prompt = (*env)->GetStringUTFChars(env, prompt, NULL); if (c_prompt == NULL) { printf("GetStringUTFChars return NULL.\n"); return NULL; } printf("%s", c_prompt); (*env)->ReleaseStringUTFChars(env, prompt, c_prompt); gets(input); return (*env)->NewStringUTF(env, input); } const static JNINativeMethod gMethods[] = { "getInformation", "(Ljava/lang/String;)Ljava/lang/String;", (void *)native_getInformation }; static jclass myClass; static const char * ClassName = "Practice1"; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * vm, void * reversed) { JNIEnv * env = NULL; jint result = -1; if((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) return -1; myClass = (*env)->FindClass(env, ClassName); if(myClass == NULL) { printf("FindClass return NULL.\n"); return -1; } if((*env)->RegisterNatives(env, myClass, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0) { printf("RegisterNatives return error.\n"); return -1; } printf("-----JNI_OnLoad Success-----\n"); return JNI_VERSION_1_6; } |
Practice 2
现在玩点特别的,现在我们设计一种简单的字符串加密算法,实际的算法部分我们都在native层实现,这样就可以通过编译成动态库(应该不会那么容易被破解查看里面的代码吧?),将算法部分保存起来,达到保护的作用。具体的设置想法如下:在Java侧处理字符串的输入,然后我们将字符串传给native层处理,native层的算法我们设计得简单点咯,第一个字符加1,、第二个字符加2、第三个字符加3、依次类推。字符串解密算法就是加密算法的逆过程,肯定有很多不严谨的地方,所以仅供娱乐练习JNI
** 2.1 Java侧代码 **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import java.util.Scanner; class Practice2 { private native String string_encode(String input); private native String string_decode(String input); public static void main(String[] args) { Scanner sc = new Scanner(System.in); Practice2 p = new Practice2(); System.out.println("Enter the string you want to encode: "); String need_encode_string = sc.nextLine(); String encode_string = p.string_encode(need_encode_string); System.out.println("After encoded, the string is " + encode_string); System.out.println("Enter the string you want to decode: "); String need_decode_string = sc.nextLine(); String decode_string = p.string_decode(need_decode_string); System.out.println("After decoded, the string is " + decode_string); } static { System.loadLibrary("Practice2"); } } |
** 2.2 Native侧代码 **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
#include <jni.h> #include <stdio.h> #include <stdlib.h> JNIEXPORT jstring JNICALL native_encode_string(JNIEnv * env, jobject obj, jstring input) { // 直接使用JNI函数获取字符串长度 jsize input_length = (*env)->GetStringUTFLength(env, input); printf("input_length = %d\n", input_length); // 分配C缓冲区,这里需要是char *类型,曾试过使用jchar *类型分配,到调用free的时候会出现segment fault char * input_buf = (char *)malloc(input_length + 1); if(input_buf == NULL) { printf("malloc buffer return error.\n"); return NULL; } // 将字符串内容复制到C缓冲区中 (*env)->GetStringUTFRegion(env, input, 0, input_length, input_buf); // 按照算法处理 for(int i = 0; i < input_length; i++) { input_buf[i] += i; } input_buf[input_length] = '\0'; // 将加密后的内容生成新的字符串 jstring result = (*env)->NewStringUTF(env, (const char *)input_buf); free(input_buf); if(result == NULL) { printf("NewStringUTF return error.\n"); return NULL; } else return result; } JNIEXPORT jstring JNICALL native_decode_string(JNIEnv * env, jobject obj, jstring input) { jsize input_length = (*env)->GetStringUTFLength(env, input); char * input_buf = (char *)malloc(input_length + 1); if(input_buf == NULL) { printf("malloc buffer return error.\n"); return NULL; } (*env)->GetStringUTFRegion(env, input, 0, input_length, input_buf); // 按照算法解密 for(int i = 0; i < input_length; i++) { input_buf[i] -= i; } input_buf[input_length] = '\0'; jstring result = (*env)->NewStringUTF(env, (const char *)input_buf); free(input_buf); if(result == NULL) { printf("NewStringUTF return error.\n"); return NULL; } else return result; } const static JNINativeMethod gMethods[] = { {"string_encode", "(Ljava/lang/String;)Ljava/lang/String;", native_encode_string}, {"string_decode", "(Ljava/lang/String;)Ljava/lang/String;", native_decode_string} }; static jclass myClass; static const char * ClassName = "Practice2"; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * vm, void * reversed) { JNIEnv * env = NULL; if((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) { printf("GetEnv return error.\n"); return -1; } myClass = (*env)->FindClass(env, ClassName); if(myClass == NULL) { printf("FindClass return error.\n"); return -1; } if((*env)->RegisterNatives(env, myClass, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) { printf("RegisterNatives return error.\n"); return -1; } return JNI_VERSION_1_6; } |
最后编译运行就可以了。
Practice 3
下面测试下时间性能,在Java侧编写一个冒泡排序算法,在native也编写一个冒泡排序算法,比较这两个时间性能,这里不能仅仅侧native的时间性能,还应该包括调用JNI的时间。一样仅供JNI编程练习,别太较真。
** 3.1 Java侧代码 **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
import java.util.Random; class Practice3 { private native void bubble_sort(int[] iarray, int arr_length); private static final int ARRAY_SIZE = 100000; private void BubbleSort(int[] iarray, int arr_length) { int temp; for (int n=0; n<arr_length; n++) { for (int m=n+1; m<arr_length; m++) { if(iarray[n] > iarray[m]) { temp = iarray[n]; iarray[n] = iarray[m]; iarray[m] = temp; } } } } public static void main(String args[]) { int max = 10000; int[] OriArray = new int[ARRAY_SIZE]; int[] NativeArray = new int[ARRAY_SIZE]; int[] JavaArray = new int[ARRAY_SIZE]; Random random = new Random(); Practice3 p = new Practice3(); System.out.println("General Array:"); for(int i = 0; i < ARRAY_SIZE; i++) { int s = random.nextInt(max); OriArray[i] = s; NativeArray[i] = s; JavaArray[i] = s; } System.out.println("Java Bubble test:"); long JavaStart = System.currentTimeMillis(); p.BubbleSort(JavaArray, ARRAY_SIZE); long JavaEnd = System.currentTimeMillis(); System.out.println("Java bubble sort need " + ((JavaEnd - JavaStart) / 1000.0) + " seconds"); System.out.println("Native Bubble test:"); long NativeStart = System.currentTimeMillis(); p.bubble_sort(NativeArray, ARRAY_SIZE); long NativeEnd = System.currentTimeMillis(); System.out.println("Native bubble sort need " + ((NativeEnd - NativeStart) / 1000.0) + " seconds"); System.out.println("Java sorted array: "); for (int i=0; i<30; i++) { System.out.print(JavaArray[i]); if((i + 1) % 10 == 0) System.out.println(); else System.out.print(" "); } System.out.println("Native sorted array: "); for (int i=0; i<30; i++) { System.out.print(NativeArray[i]); if((i + 1) % 10 == 0) System.out.println(); else System.out.print(" "); } } static { System.loadLibrary("Practice3"); } } |
** 3.2 Native侧代码 **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include <jni.h> #include <stdio.h> JNIEXPORT void JNICALL native_bubble_sort(JNIEnv * env, jobject obj, jintArray array, jint size) { // 在C空间中获取数组内容 jint * iArray = (*env)->GetIntArrayElements(env, array, NULL); jint temp = 0; // 对获取到的数组内容进行排序 for(int i = 0; i < size; i++) for(int j = i+1; j < size; j++) { if(iArray[i] > iArray[j]) { temp = iArray[i]; iArray[i] = iArray[j]; iArray[j] = temp; } } // 对排序后的数组内容写回到Java虚拟机中 (*env)->SetIntArrayRegion(env, array, 0, size, iArray); // 释放资源 (*env)->ReleaseIntArrayElements(env, array, iArray, 0); return; } const static JNINativeMethod gMethods[] = { {"bubble_sort", "([II)V", native_bubble_sort} }; static jclass myClass; static const char * ClassName = "Practice3"; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * vm, void * reversed) { JNIEnv * env = NULL; if((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) { printf("GetEnv return error.\n"); return -1; } myClass = (*env)->FindClass(env, ClassName); if(myClass == NULL) { printf("FindClass return error.\n"); return -1; } if((*env)->RegisterNatives(env, myClass, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) { printf("RegisterNatives return error.\n"); return -1; } return JNI_VERSION_1_6; } |
最后发现,上面的代码,在Java侧做冒泡排序,I5-3320M的CPU,需要15.几秒,而native侧就要18.几秒,一方面Java调用native方法比Java调用Java方法要耗时,其次从虚拟机中复制数组数据到C缓冲区中也要时间。所以怎么看好像在native侧做排序都没有占到好处,不过也可能是native方法编写得不够有效率,不过目前就先这样了,毕竟才重新开始学JNI,有很多地方还不够熟悉的,后续有机会找到好方法再改进。
** 此文为博主原创文章,转载请注明出处 **