用CLion實現本地方法並給java調用


眾所周知,PHP是世界上最好的語言,java排第二,因為PHP無所不能。但是在某些場景下java還要調用本地方法來提高執行的效率,故java只能排第二。java提供了jni(Java Native Interface)來實現在java中調用本地方法。本地方法在java中用native關鍵字標識,它是一種和機器有關的方法,一般用C或C++實現,而本地方法不是跨平台的,不同的平台需要重新編譯。jdk中就有不少地方用了native方法,比如Object類中的hashCode方法:

public native int hashCode();

下面開始使用jni:

(一)創建一個帶有native方法的類

package com.example.jni;

public class JNIObject {
    private String name;

    public JNIObject(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
    
    public int add(int param1, int param2) {
        return param1 + param2;
    }

    public int sub(int param1, int param2) {
        return param1 - param2;
    }

    public native int multi(int param1, int param2);

    public native int div(int param1, int param2);
}

我們假定加減法執行效率高可以直接用java實現,而乘除法比較慢,需要用C語言來實現。寫好了類我們先編譯,把java文件編譯成class文件,然后再用javah命令生成c頭文件,執行javah命令時要注意,我們需要先把當前的工作目錄切換到class所在的根目錄,就是包的第一級目錄所在的目錄。比如我們的包名是com.example.jni,那么我們需要切換到com目錄所在的目錄,執行的命令格式是javah [-option] 包名.類名

javah -jni com.example.jni.JNIObject

成功后會在當前的工作目錄生成一個.h的文件(com_example_jni_JNIObject.h),到此我們就得到了本地方法的接口了,如果有c程序員,可以讓他們實現,否則看第2步。

(二)在CLion中實現native方法

如果沒有CLion先請自行安裝。

  • 創建一個C Library項目,填好路徑和項目名,Library type選擇shared,Language standard是指c語言的不同標准,類似於我們的jdk的版本,如果你不是c程序員,直接用默認的C99標准就好了

  • 設置編譯環境

如果你使用過Visual Studio的話,你可能安裝之后直接創建項目就能寫代碼了,但是CLion有點不同,它只是一個開發集成環境,只提供了構建工具cmake,並沒有提供編譯器(Visual Studio全套都提供好了),這可以讓開發者自由去選擇自己喜歡的編譯器,我們打開File->Settings,並選中Build,Execution,Deployment下的Toolchains

可以看到CLion列出了兩個常用的編譯器MinGW和Cygwin,這里我們使用MinGW作為我們的編譯器。需要注意的是如果你的jdk是64位的,那么也要選擇64位的MinGW,不然在調用的時候會出錯,下面是windows下的MinGW64位下載地址

http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download

下載后直接安裝就行了,安裝成功后在上圖中的Environment選擇MinGW的home目錄,然后下面的C、C++編譯器還有調試器都會根據選擇的目錄自動找到相關工具,然后點OK就完成了我們的編譯器的設置。

  • 實現native方法

我們先把第一步生成的c頭文件(com_example_jni_JNIObject.h)復制到CLion項目中,這時在打開的com_example_jni_JNIObject.h文件頂部出現一行提示:This file dose not belong to any project target, code insight features might not work properly. 這時我們打開CMakeLists.txt文件,在add_library中加入我們的頭文件,完成后點擊提示的reload changes,完成后的CMakeLists.txt的內容如下:

cmake_minimum_required(VERSION 3.15)
project(jni C)

set(CMAKE_C_STANDARD 99)

add_library(jni SHARED library.c library.h com_example_jni_JNIObject.h)

再切換到com_example_jni_JNIObject.h可以發現剛才的警告已經消失了,但是第二行的#include <jni.h>報錯了,這時因為MinGW編譯器沒有jni.h這個頭文件,打開JDK的home目錄,在include目錄中可以找到jni.h頭文件,除此之外,我們還需要include/win32目錄下的jni_md.h頭文件,一共兩個,把這兩個頭文件都復制到MinGW安裝目錄(就是設置編譯環境時的那個Environment的值)下的x86_64-w64-mingw32中的include目錄中,注意這兩個頭文件是一起放在MinGW的這個目錄的,jni_md.h不需要另外創建一個win32目錄來存放。完成后發現com_example_jni_JNIObject.h的報錯消失了。

我們右鍵點擊項目,選擇New->C/C++ Source File,然后創建一個源碼文件,type選擇.c,如果你習慣使用C++就選.cpp

點擊OK完成,下面是具體的實現

#include "com_example_jni_JNIObject.h"

JNIEXPORT jint JNICALL Java_com_example_jni_JNIObject_multi
        (JNIEnv *env, jobject o, jint param1, jint param2) {
    return param1 * param2;
}

JNIEXPORT jint JNICALL Java_com_example_jni_JNIObject_div
        (JNIEnv *env, jobject o, jint param1, jint param2) {
    return param1 / param2;
}

解釋一下上面的源文件,#include是把后面的com_example_jni_JNIObject.h頭文件包含進來,和java的import作用類似,下面的兩個方法就是在這個頭文件中聲明的函數,c語言在聲明函數時可以忽略參數名,只寫參數類型,但是現在我們是在實現函數,所以必須加上參數名。如果是學習過c語言的很容易理解,沒學過的了解一下就好。

  • 編譯動態鏈接庫

到此,我們的代碼就完成了,點擊菜單欄的Build->Build Project成功后在在左側的項目結構里生成了一些文件,其中cmake-build-debug目錄下的libjni.dll就是我們需要的動態鏈接庫了,如果是linux系統,生成的是.so格式的文件。

(3)在java中調用native方法

回到java目錄,我們在項目的根目錄下創建一個jni目錄,把我們的dll文件復制進去,復制好之后會自動打開,發現是亂碼,因為dll文件是二進制格式的,我們直接關掉。

在JNIObject類中添加一個靜態代碼塊,用來加載我們的動態鏈接庫,完成后的JNIObject類如下:

public class JNIObject {

    static {
        System.loadLibrary("libjni");    
    }
    
    private String name;

    public JNIObject(String name) {
        this.name = name;
    }

    ...
}

注意loadLibrary方法不用寫dll后綴名。

我們新建一個測試類Main,代碼如下:

package com.example.main;

import com.example.jni.JNIObject;

public class Main {

    public static void main(String[] args) {
        JNIObject jniObject = new JNIObject("jni");
        System.out.println(jniObject.getName()); // 調用java方法
        System.out.println(jniObject.add(1, 2)); // 調用java方法
        System.out.println(jniObject.sub(1, 2)); // 調用java方法
        System.out.println(jniObject.multi(2, 3)); // 調用native方法
        System.out.println(jniObject.div(6, 2)); // 調用native方法
    }
}

我們先運行一個Main類的main方法,發現報錯了:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no libjni in java.library.path
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
	at java.lang.Runtime.loadLibrary0(Runtime.java:870)
	at java.lang.System.loadLibrary(System.java:1122)
	at com.example.jni.JNIObject.<clinit>(JNIObject.java:6)
	at com.example.main.Main.main(Main.java:8)

這是因為我們還沒有指定jni庫的加載路徑,導致loadLibrary方法無法找到我們的dll庫。點開運行按鈕下拉菜單的Edit Configurations,我們給Main類加一個啟動參數-Djava.library.path,這個參數就是異常信息出現的參數,指定值為jni目錄

重新運行main方法,可以看到已經可以正常執行native方法了。

總結

總結一下jni的調用過程,先定義好native方法,然后通過javah生成頭文件,然后用C或C++實現函數,編譯成動態鏈接庫,把動態鏈接庫加入到java項目當中,通過System.loadLibrary(String)方法加載,最后就可以調用了。


免責聲明!

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



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