Jimmy Chen

A Programmer

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

第七章之刻意练习

练习1: 学会在C代码中创建虚拟机,复习类、方法ID的查找

在native侧,使用random函数生成一个包含1000个整型的数组,然后在native侧调用Java编程语言中的Arrays.sort(int [])方法对数组进行排序,然后打印数组的前100个数。

** 下面是博主自己写的答案 **

首先直接给上代码

#include <jni.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char **argv)
{
    srand(time(0));
    jint res = 0;
    JavaVM *jvm;
    JNIEnv *env;
    JavaVMInitArgs vm_args;
    
    // 设置JavaVM的参数
    JavaVMOption *options = (JavaVMOption *)malloc(sizeof(JavaVMOption) * 1);
    // java.class.path需要换成java.util.Arrays类所在的路径,也就是Java在主机的安装路径了
    // 博主的JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
    options[0].optionString = "-Djava.class.path=$JAVA_HOME/";
    vm_args.version = JNI_VERSION_1_8;
    vm_args.nOptions = 1;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = JNI_FALSE;
    
    // 创建JavaVM
    res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    if(res < 0) {
        printf("Creat Java VM failed!\n");
        return -1;
    }
    free(options);
    
    // 查找对应的class
    jclass cls = (*env)->FindClass(env, "java/util/Arrays");
    if(cls == NULL) {
        printf("FindClass return NULL!\n");
        return -1;
    }
    
    // 查找sort的方法ID
    jmethodID mid = (*env)->GetStaticMethodID(env, cls, "sort", "([I)V");
    if(mid == NULL) {
        printf("GetStaticMethodID return NULL!\n");
        return -1;
    }
    
    // 产生1000个int型的数组
    int arr[1000];
    for(int i = 0; i < 1000; i++) {
        arr[i] = random() % 10000;
    }
    
    // 上面的数组不能直接使用,需要创建jintArray数组,然后将上面的数组复制到jintArray中
    jintArray jarr = (*env)->NewIntArray(env, 1000);
    (*env)->SetIntArrayRegion(env, jarr, 0, 1000, arr);
    
    // 调用Java编程语言的Arrays.sort方法,对数组元素排序
    (*env)->CallStaticVoidMethod(env, cls, mid, jarr);
    
    // 将排序后的元素复制回arr数组中
    (*env)->GetIntArrayRegion(env, jarr, 0, 1000, arr);
    
    for(int i = 0; i < 100; i++) {
        printf("%4d ", arr[i]);
        if((i + 1) % 10 == 0)
            printf("\n");
    }
    
    (*jvm)->DestroyJavaVM(jvm);
    
    return 0;
}

上面的代码主要是学会在C程序中创建JavaVM以及调用Java编程语言中的方法。自己写过一两次应该也就会了。然后就是编译了,下面是博主的编译指令,博主使用的是Ubuntu 16.04 LTS版本。首先将编译指令:

gcc -g -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -L$JAVA_HOME/jre/lib/amd64/server  Parctice1.c -o Practice1 -ljvm

其中-I$JAVA_HOME/include -I$JAVA_HOME/include/linux主要是为了提供头文件的包含目录,而-L$JAVA_HOME/jre/lib/amd64/server提供Java虚拟机动态库的链接目录,最后-ljmv表示链接到libjvm.so库。奇怪的是,-ljvm必须放到最后,编译才能成功,而放到其他位置的话都会报错:Parctice1.c:26: undefined reference to 'JNI_CreateJavaVM'。大家要想编译通过记得将-ljvm放最后就好了。

同样的JAVA_HOME需要改成自己的环境变量,博主已经在.bashrc中添加了自己的JAVA_HOME环境:

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64

编译通过后,直接通过./Practice1运行就好了,不需要做特殊的设置,C程序就自动会根据代码中设置JavaVMOption路径查找到需要的库的了。

练习2: 学会多线程中使用JavaVM

编写一个线程函数,在函数内打印被调用的次数并且调用Java的java.util.Date类的方法,打印调用的时间按照hh:mm:ss方式打印,main函数就创建Java虚拟机环境,循环调用前面设计的打印时间线程函数,每次循环都使用random来产生1-5秒之间的数来决定休眠时间。

下面是我写的代码:

#include <jni.h>
#include <stdio.h>
#include <unistd.h>   // for sleep()
#include <pthread.h>
#include <time.h>
#include <stdlib.h>


// 文件全局变量
static JavaVM *jvm = NULL;
static unsigned call_count = 0;

void *thread_fun(void *arg)
{
    jint res = 0;
    jclass clazz;
    jmethodID dateConstructorID;
    jmethodID getHoursID;
    jmethodID getMinutesID;
    jmethodID getSecondsID;
    JNIEnv *env;
    
    // JavaVM还没有初始化
    if(jvm == NULL) {
        printf("Obtain JavaVM error!\n");
        goto detach;
    }
    
    printf("thread_fun have been called %d times!\n", ++call_count);
    
    // 将线程附加到JavaVM上
    res = (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
    
    if(res < 0) {
        printf("AttachCurrentThread errir!\n");
        goto detach;
    }   
    
    // 下面开始查找Date类以及获取时间的方法ID
    clazz = (*env)->FindClass(env, "java/util/Date");
    if(clazz == NULL) {
        printf("FindClass error!\n");
        goto detach;
    }
    
    getHoursID = (*env)->GetMethodID(env, clazz, "getHours", "()I");
    if(getHoursID == NULL) {
        printf("getHoursID error!\n");
        goto detach;
    }
    
    getMinutesID = (*env)->GetMethodID(env, clazz, "getMinutes", "()I");
    if(getMinutesID == NULL) {
        printf("getMinutesID error!\n");
        goto detach;
    }
    
    getSecondsID = (*env)->GetMethodID(env, clazz, "getSeconds", "()I");
    if(getSecondsID == NULL) {
        printf("getSecondsID error!\n");
        goto detach;
    }
    
    // 获取构造函数方法ID
    dateConstructorID = (*env)->GetMethodID(env, clazz, "", "()V");
    if(dateConstructorID == NULL) {
        printf("dateConstructorID error!\n");
        goto detach;
    }
    
    // 调用Date的构造函数来创建一个对象
    jobject dateObject = (*env)->NewObject(env, clazz, dateConstructorID);
    if(dateObject == NULL) {
        printf("call constructor error!\n");
        goto detach;
    }
    
    // 调用方法获取时间值
    jint hours = (*env)->CallIntMethod(env, dateObject, getHoursID);
    jint minutes = (*env)->CallIntMethod(env, dateObject, getMinutesID);
    jint seconds = (*env)->CallIntMethod(env, dateObject, getSecondsID);
 
detach:
    // 下面需要括号括起来,不然会报错:a label can only be part of a statement and a declaration is not a statemen
    {
        jthrowable exc = (*env)->ExceptionOccurred(env);
        if(exc) {
            (*env)->ExceptionDescribe(env);
            (*env)->ExceptionClear(env);
            (*jvm)->DetachCurrentThread(jvm);
            pthread_exit((void *)-1);
        }
    }
    
    printf("Now Time: %02d:%02d:%02d\n", hours, minutes, seconds);  
    pthread_exit((void *)0);
}

int main(int argc, char **argv)
{
    JNIEnv *env;
    int i = 0;
    jint res = 0;
    pthread_t tid;
    
    srand(time(0));
    
    // 初始化虚拟机参数
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];
    options[0].optionString = "-Djava.class.path=$JAVA_HOME/";
    vm_args.version = JNI_VERSION_1_8;
    vm_args.options = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized = JNI_FALSE;
    
    // 创建虚拟机
    res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    if(res < 0) {
        printf("Can't create Java VM\n");
        return -1;
    }
    
    for(i = 0; i < 10; i++) {
        res = pthread_create(&tid, NULL, thread_fun, NULL);
        if(res != 0) {
            printf("#%d, pthread_create error!\n", i);
            continue;
        }
        
        // 随机休眠1-5秒
        unsigned sleepTime = 1 + (random() % 4);
        printf("go to sleep %d seconds!\n", sleepTime);
        sleep(sleepTime);
    }
    
    return 0;   
}

最后再按照Practice 1中的编译方法编译就好了。其实这个还可以改进下,提高执行的性能,大家发现没上面的每次创建线程并执行的时候都要再次查找工作,我们完全可以建立缓存,将方法ID保存起来提高性能的。这个就让大家自己试一试了。

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

发表评论

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

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

%d 博主赞过: