Jimmy Chen

A Programmer

(原创) JNI编程指南与规范 第三章 刻意练习

第三章之刻意练习

Practice 1

  在Java侧初始化两条提示语句,一个提示输入姓名,另一个提示输入住址,然后编写一个native方法,将其中的提示语句传给native方法,然后再native方法中获取输入,将输入的内容返回给Java侧,在Java侧打印native方法中输入的内容。(当然有时间的朋友可以尝试使用静态注册JNI方法和动态注册JNI方法这两种方式)

静态注册方式

** 1.1 Java侧代码: **

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产生的头文件 **

/* 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 静态注册方法编写 **

#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文件

#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侧代码 **

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侧代码 **

#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侧代码 **

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侧代码 **

#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,有很多地方还不够熟悉的,后续有机会找到好方法再改进。

** 此文为博主原创文章,转载请注明出处 **

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d 博主赞过: