java 本地方法(JNI)


最近搞了一個調用第三方so庫做登錄認證的任務,以前對JNI沒什么概念,最近學習了 《java核心技術》 本地方法 一章,把自己寫的一些例子記錄一下。 自己C語言真是渣渣,所以所有的例子都在可以包括基本API的基礎上盡可能簡單。以下所有例子都是在centos 7中測試的,window不太熟。

調用本地方法

java調用本地方法,首先需要加載包含對應方法的so庫(linux),一般使用下面這種方式加載so庫。

1 public class Test{
2         static
3         {
4                 //so庫的名字是libTest.so
5                 System.loadLibrary("Test");
6         }
7 
8         public static native void hello();
9 }

 

在static代碼塊中加載so庫,這樣就能在這個類被classLoader 加載的時候就被載入。要想正確載入so,必須將so庫放在java.library.path 指定的路徑中,我們可以通過以下兩種方式來指定java.library.path 的值

1. 配置 LD_LIBRARY_PATH 環境變量

2. 通過java的運行參數指定 -Djava.library.path= .....  

 

當我們調用本地方法時,會在加載的so庫中去尋找與我們所調用方法對應的本地方法,比如上面定義的hello方法,就應該有一個對應的本地方法為 

JNIEXPORT void JNICALL Java_Test_hello(JNIEnv *, jclass)

我們可以使用javah產生這個一個頭文件,在其中就包含了這個方法的聲明。

我們編寫完c文件后,就可以用它生成一個對應的so了

gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libTest.so Test.c

其中jdk是含有jdk的目錄,以我的環境為例,jdk目錄為 /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.79-2.5.5.1.el7_1.x86_64/, 配置JAVA_HOME指向這個目錄,所以編譯命令就是:

gcc -fPIC -I ${JAVA_HOME}/include -I ${JAVA_HOME}/include/linux -shared -o libTest.so Test.c

之所以要使用-I 參數指定這兩個目錄,是因為在其中包含了c文件需要的兩個頭文件, <jni.h>和 <jni_md.h>

總結出將一個本地方法鏈接到java程序中的步驟:

1)在java類中聲明一個native方法

2)運行javah 得到一個本地方法需要的頭文件

3)使用C實現本地方法

4)使用C代碼編譯出so文件,並將它放置在java.library.path中

5)使用java調用就可以了

 下面的案例中重要的api都用紅色標記了。

案例1:

計算兩個int的和(傳入int參數並返回int類型)

class Calc
{
        static{
                System.loadLibrary("Calc");
        }

        public static native int add(int a, int b);

        public static void main(String[] args)
        {
                System.out.println(add(11,23));
        }
}

 

對應的C代碼:

#include <stdio.h>
#include "Calc.h"

/* jint 對應着java 的int類型  */
JNIEXPORT jint JNICALL Java_Calc_add(JNIEnv *env, jclass jc, jint a, jint b)
{
        jint ret = a + b;
        return ret;
}

 

 

案例二:給傳入的name加上hello前綴再返回(傳入String參數並返回String類型)

class Hello
{
        static
        {
                System.loadLibrary("Hello");
        }

        public static native String hello(String name);

        public static void main(String[] args){
                System.out.println(hello("zhangsan"));
        }
}

 

對應的C代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Hello.h"

/*拼接字符串 */
char* join(const char *s1, const char *s2)  
{  
    char *result = malloc(strlen(s1)+strlen(s2)+1);//+1 for the zero-terminator  
    //in real code you would check for errors in malloc here  
    if (result == NULL) exit (1);  
      
    strcpy(result, s1);  
    strcat(result, s2);  
          
    return result;  
}  



JNIEXPORT jstring JNICALL Java_Hello_hello(JNIEnv* env, jclass cl, jstring name)
{
    /* 從java String 獲得 C char*  */
    const char* cname;
    cname = (*env)->GetStringUTFChars(env, name, NULL);
    
    char* hello_s = join("hello, ", cname);

    /* 從 C char* 再獲得 java String */
    jstring ret = (*env)->NewStringUTF(env, hello_s);
    
    /* 主動釋放內存, 表明不再需要通過 name 來訪問 cname*/
    (*env)->ReleaseStringUTFChars(env, name, cname);


    return ret;
}

 

案例三: 在C代碼中調用PrintWriter.print方法(調用java對象的實例方法)

感覺這種調用和反射基本類似。

import java.io.*;

public class Hello
{
        static
        {
                System.loadLibrary("Hello");
        }

        public static native void sayHello(PrintWriter out, String message);

        public static void main(String[] args)
        {
                PrintWriter out = new PrintWriter(System.out);
                Hello.sayHello(out, "Hello world!\n");
                out.flush();
        }

}

 

C代碼實現:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Hello.h"

/*java 的Object類型對應jobject */
JNIEXPORT void JNICALL Java_Hello_sayHello(JNIEnv* env, jclass jc, jobject out, jstring message)
{
        const char* cmessage;
        /*從 java String 得到 c char*  */
        cmessage = (*env)->GetStringUTFChars(env, message, NULL);

        /* 處理得到的字符串,加上前綴 */
        const char* append = "I'm say: ";
        char* result = (char*)malloc(strlen(cmessage) + strlen(append) + 1);
        strcpy(result, append);
        strcat(result, cmessage);

        /*從 c char* 得到 java String */
        jstring jresult = (*env)->NewStringUTF(env, result);

        /* 主動釋放, 不再需要通過message獲得cmessage */
        (*env)->ReleaseStringUTFChars(env, message, cmessage);

        /* 下面就是 調用PrintWriter.print(String) */

        /* 獲得class */
        jclass class_PrintWriter = (*env)->GetObjectClass(env, out);

        /* 獲得 method ID , 最后一個參數是 print方法的簽名 返回值為void(V), 參數為java.lang.String */
        jmethodID id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(Ljava/lang/String;)V");

        /* 調用方法 */
        (*env)->CallVoidMethod(env, out, id_print, jresult);
}

 

 

案例四: 在C代碼中調用System.getProperty靜態方法(調用java靜態方法)

public class Test
{
        static
        {
                System.loadLibrary("Test");
        }

        public static native String getClassPath();

        public static void main(String[] args)
        {
                System.out.println(getClassPath());
        }

}

 

c代碼實現:

#include <stdio.h>
#include "Test.h"

JNIEXPORT jstring JNICALL Java_Test_getClassPath(JNIEnv* env, jclass jc)
{
        /*獲得System的class */
        jclass class_System = (*env)->FindClass(env, "java/lang/System");

        /*獲得 getProperty 方法的 方法id */
        jmethodID id_getProperty = (*env)->GetStaticMethodID(env, class_System, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");

        /* 執行 靜態方法 */
        jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty, (*env)->NewStringUTF(env, "version"));

        return (jstring)obj_ret;
}

 

 這個例子在運行的時候 增加 -Dversion=xxxx 就可以得到version運行參數了。

案例五: 在C中修改Employee的靜態和實例屬性(修改實例屬性和靜態屬性)

public class Employee
{
    static
    {
        System.loadLibrary("Employee");
    }
    public static String a  = "Good Employee";
    private String name;
    private double salary;

    public Employee(String name, double salary)
    {
        this.name = name;
        this.salary = salary;
    }

    public String toString(){
        return name + " " + salary;
    }

    public native void raiseSalary(double byPercent);

    public static native void updateDescription(String description);

    public static void main(String[] args)
    {
        Employee e = new Employee("zhangsan", 1000);
        System.out.println(e);
        e.raiseSalary(0.1);
        System.out.println(e);

        System.out.println("###############################");

        System.out.println(e.a);
        Employee.updateDescription("Bad Employee");
        System.out.println(e.a);

    }
}

 

 c代碼:

#include <stdio.h>
#include "Employee.h"

JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv* env, jobject this_obj, jdouble byPercent)
{
    /* get the class */
    jclass class_Employee = (*env)->GetObjectClass(env, this_obj); 

    /* get the field Id */
    jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D"); //"D" 代表類型double
    
    /* get the field value  */
    jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary);

    salary *= 1 + byPercent / 100;

    /* set the field value */
    (*env)->SetDoubleField(env, this_obj, id_salary, salary);
}

JNIEXPORT void JNICALL Java_Employee_updateDescription(JNIEnv* env, jclass jc, jstring description)
{
    /* get static class field */
    /*一定要注意類的簽名方式, 前面的L 和最后的;(分號)都不能少,那個分號不是分隔符,是簽名的一部分 */
    jfieldID desc_id = (*env)->GetStaticFieldID(env, jc, "a", "Ljava/lang/String;");    

    /* set new static description field */
    (*env)->SetStaticObjectField(env, jc, desc_id, description);
}

 

 案例六:訪問修改數組

class Test
{
        static
        {
                System.loadLibrary("Test");
        }

        public static native void scaleArray(double[] arr);

        public static void main(String[] args)
        {
                double[] arr = {1.1, 2.2};
                scaleArray(arr);

                for(double d : arr){
                        System.out.println(d);
                }
        }
}

 

C代碼實現:

#include <stdio.h>
#include "Test.h"

JNIEXPORT void JNICALL Java_Test_scaleArray(JNIEnv* env, jclass jc, jdoubleArray arr)
{
        double scaleFactor = 2.0;
        /*獲得 一個指向 數組的指針 */
        double* a = (*env)->GetDoubleArrayElements(env, arr, NULL);

        int i;
        for(i = 0; i< (*env)->GetArrayLength(env, arr); i++)
                a[i] = a[i] * scaleFactor;

        (*env)->ReleaseDoubleArrayElements(env, arr, a, 0);
}

 

案例七:在C中訪問構造函數並構造對象

import java.util.Random;

public class Test
{
        static
        {
                System.loadLibrary("Test");
        }

        public static native int  nextInt();

        public static void main(String[] args)
        {
                System.out.println(nextInt());
        }
}

 

在C代碼中調用Random類的構造方法構造一個Random實例,然后調用nextInt實例方法。

#include <stdio.h>
#include "Test.h"

JNIEXPORT jint JNICALL Java_Test_nextInt(JNIEnv* env, jclass jc)
{
        /* 獲得 Random 類, 注意表示類的字符串 */
        jclass class_Random = (*env)->FindClass(env, "java/util/Random");
        /* 獲得 Random 構造器 方法id, "<init>"代表構造方法 */
        jmethodID id_Random = (*env)->GetMethodID(env, class_Random, "<init>", "()V");
        /* 構造一個Random類型的對象 */
        jobject obj_random = (*env)->NewObject(env, class_Random, id_Random, NULL);

        /* 下面調用這個對象的 nextInt 方法 */
        jmethodID id_nextInt = (*env)->GetMethodID(env, class_Random, "nextInt", "()I");
        jint ret = (*env)->CallIntMethod(env, obj_random, id_nextInt, NULL);
        return ret;
}

 

 

 案例八: 在本地方法中處理異常

public class Test
{
    static
    {
        System.loadLibrary("Test");
    }
    
    /*
    這里的luckyNumber方法純粹測試目的:
    當name為zhangsan時一定會拋出一個IllegalArgumentException異常
    當name為lisi時,會調用Random.next(-10)主動拋出一個IllegalArgumentException異常,但是可以使用第二個參數來決定是否要拋出到 jvm 
    當name為其他值時,無異常 
    */
    public static native int  luckyNumber(String name, boolean nativeHandleException);

    public static void main(String[] args)
    {
        System.out.println(Test.luckyNumber("zhangsan", false));
    }
}

 

C代碼:

#include <stdio.h>
#include <string.h>
#include "Test.h"

JNIEXPORT jint JNICALL Java_Test_luckyNumber(JNIEnv* env, jclass jc, jstring name, jboolean nativeHandleException)
{
    const char* cname;
    cname = (*env)->GetStringUTFChars(env, name, NULL);

    /* 當name為zhangsan時我們主動拋出一個異常 */    
    if(strcmp(cname, "zhangsan") == 0)
    {
        jclass class_Exception = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
        /* 主動拋出異常 */
        (*env)->ThrowNew(env, class_Exception, "zhangsan is a bad guy, he can't be given a lucky number");
        /* 本地方法拋出異常后並不會主動終止,所以要手動return */
        return;
    }    

    /* 調用Random.nextInt 產生一個隨機幸運數 */
    jclass class_Random = (*env)->FindClass(env, "java/util/Random");
    jmethodID id_Random = (*env)->GetMethodID(env, class_Random, "<init>", "()V");
    jobject obj_random = (*env)->NewObject(env, class_Random, id_Random, NULL);

    jmethodID id_nextInt = (*env)->GetMethodID(env, class_Random, "nextInt", "(I)I");
    jint ret;

    /* 當name為lisi時,我們使用負數來作為nextInt的參數,從而讓他拋出一個異常  */
    if(strcmp(cname, "lisi") == 0)
    {
        ret = (*env)->CallIntMethod(env, obj_random, id_nextInt, (-10) );
        
        /*檢查是否有異常掛起 */
        jboolean hasException = (*env)->ExceptionCheck(env);
        /*當有異常掛起並且要求在native中主動處理異常時,主動clear,這樣就不會通知 虛擬機 了*/
        if(hasException && nativeHandleException)
        {    
            /* 主動清除掛起的異常 */
            (*env)->ExceptionClear(env);        
            printf("the exception is handled in native function/n");
        }else if(hasException){
            return;
        }
    }else{
        ret = (*env)->CallIntMethod(env, obj_random, id_nextInt, 10);
    }    

    return ret;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM