前言
我們知道Java是一種編譯解釋型語(yǔ)言焕檬,編譯后得到.class文件再經(jīng)由jvm解釋執(zhí)行饱搏,盡管jvm現(xiàn)在已經(jīng)相當(dāng)高效眉踱,但和C/C++這種可以直接編譯為可執(zhí)行文件的語(yǔ)言相比效率還是略遜一籌荠耽。為了讓Java更加高效也為了跨平臺(tái)能力,JNI(Java Native Interface)應(yīng)運(yùn)而生蜀铲。
JNI簡(jiǎn)單來(lái)說(shuō)就是讓Java方法中可以調(diào)用C函數(shù),C函數(shù)也可以調(diào)用Java属百。這樣做的好處有很多:在計(jì)算密集型場(chǎng)景適當(dāng)使用jni可以顯著提高效率蝙茶;需要直接操作內(nèi)存時(shí)JNI更加合適,例如Object
類中的clone()
方法诸老;本地鏈接庫(kù)不易破解隆夯,核心算法和一些加/解密操作可以放在本地方法中。
下面我會(huì)寫(xiě)一個(gè)小示例别伏,用到的工具和環(huán)境如下:
- windows系統(tǒng)
- jdk環(huán)境
- IntelliJ IDEA(Java IDE)
- Clion(C IDE蹄衷,一般來(lái)說(shuō)用Visual Studio的多一些,但是我比較喜歡jetbrains的風(fēng)格)
- MinGW(Win下的編譯器厘肮,Clion需要靠這個(gè)來(lái)編譯)
1. 在Java文件定義本地方法并生成頭文件
package com.leqi.example;
public class Hello {
public static native String helloFromNative();
public static void main(String[] args) {
System.out.println(helloFromNative());
}
}
IDEA新建一個(gè)類Hello愧口,定義一個(gè)native方法helloFromNative()
,方法返回一個(gè)String
類类茂。
如果你是Kotlin愛(ài)好者耍属,可以這么寫(xiě),新建Koltin File(名字隨便起):
@file:JvmName("Hello")
package com.leqi.example
external fun helloFromNative(): String
fun main() {
System.load("C:\\Users\\LG\\Desktop\\libNativeHello.dll")
println(helloFromNative())
}
Kotlin和Java并無(wú)本質(zhì)上的區(qū)別巩检,只是要注意Kotlin File編譯后文件名會(huì)發(fā)生改變厚骗,只需要指定JvmName用起來(lái)就差不多了。
點(diǎn)擊IDEA的綠色小錘子編譯項(xiàng)目兢哭,然后在這個(gè)目錄下找到編譯后的.class文件领舰。
隨后打開(kāi)命令行工具,cd到項(xiàng)目文件夾名\out\production\項(xiàng)目文件夾名
下迟螺,輸入命令javah com.leqi.example.Hello
冲秽,不指定輸出目錄的話頭文件會(huì)自動(dòng)生成在當(dāng)前目錄下。
如果jdk版本高矩父,javah
命令被移除的話可以用javac -h
命令代替锉桑。用命令行生成只是為了省事,后期熟練了自己手寫(xiě)頭文件也是沒(méi)關(guān)系的窍株。
2. 編譯動(dòng)態(tài)鏈接庫(kù)
打開(kāi)Clion新建C++Library項(xiàng)目民轴,Library type選擇shared攻柠。
第一次新建項(xiàng)目時(shí)如果沒(méi)有配置編譯工具的話點(diǎn)擊file - setting中,找到Toolchains然后添加MinGW杉武,把MinGW的根目錄選中即可辙诞。
接下來(lái)把上一步得到的頭文件copy到項(xiàng)目中,再找到j(luò)dk安裝目錄轻抱,在include目錄下找到j(luò)ni.h文件飞涂,include/win32目錄下找到j(luò)ni_md.h,這兩個(gè)文件也copy到項(xiàng)目中祈搜。
新建Hello.cpp较店,代碼如下:
#include "com_leqi_example_Hello.h"
#include "string"
extern "C" {
JNIEXPORT jstring
JNICALL Java_com_leqi_example_Hello_helloFromNative
(JNIEnv *env, jclass) {
std::string hello = "Hello World from C++";
return env->NewStringUTF(hello.c_str());
}
}
extern "C"
很重要,它的功能是讓編譯器以處理 C 語(yǔ)言代碼的方式處理C++代碼容燕,否則jni調(diào)用時(shí)會(huì)出現(xiàn)問(wèn)題梁呈。
JNI函數(shù)命名是有一些規(guī)則的,并不是我們想怎么寫(xiě)就怎么寫(xiě)蘸秘,首先是JNIEXPORT
表明這還是個(gè)可被外部調(diào)用的函數(shù)官卡。
接下來(lái)是函數(shù)的返回值jstring
,這個(gè)東西可以展開(kāi)講講醋虏,我們知道Java和C是兩套東西寻咒,它們的基本數(shù)據(jù)類型是不可以直接互相調(diào)用的,所以jni頭文件中就定義了各種各樣的映射颈嚼,我在別人博客盜了個(gè)表格:
Java類型 | 本地類型 | 描述 |
---|---|---|
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++帶符號(hào)的8位整型 |
char | jchar | C/C++無(wú)符號(hào)的16位整型 |
short | jshort | C/C++帶符號(hào)的16位整型 |
int | jint | C/C++帶符號(hào)的32位整型 |
long | jlong | C/C++帶符號(hào)的64位整型e |
float | jfloat | C/C++32位浮點(diǎn)型 |
double | jdouble | C/C++64位浮點(diǎn)型 |
Object | jobject | 任何Java對(duì)象毛秘,或者沒(méi)有對(duì)應(yīng)java類型的對(duì)象 |
Class | jclass | Class對(duì)象 |
String | jstring | 字符串對(duì)象 |
Object[] | jobjectArray | 任何對(duì)象的數(shù)組 |
boolean[] | jbooleanArray | 布爾型數(shù)組 |
byte[] | jbyteArray | 比特型數(shù)組 |
char[] | jcharArray | 字符型數(shù)組 |
short[] | jshortArray | 短整型數(shù)組 |
int[] | jintArray | 整型數(shù)組 |
long[] | jlongArray | 長(zhǎng)整型數(shù)組 |
float[] | jfloatArray | 浮點(diǎn)型數(shù)組 |
double[] | jdoubleArray | 雙浮點(diǎn)型數(shù)組 |
JNICALL
表明這個(gè)函數(shù)是個(gè)jni函數(shù),jni函數(shù)名的命名也是有規(guī)則的阻课,Java_Java包名_Java類名_native函數(shù)名
叫挟,函數(shù)都會(huì)有兩個(gè)固定的參數(shù)JNIEnv *env, jclass
是由jvm虛擬機(jī)傳入的,env
指針提供了大量的訪問(wèn)java變量和方法的函數(shù)限煞,后面會(huì)常用到抹恳。
接下來(lái)修改CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.17)
project(NativeHello)
set(CMAKE_CXX_STANDARD 14)
add_library(NativeHello SHARED com_leqi_example_Hello.h Hello.cpp)
其實(shí)CMakeLists也很容易理解,主要把你要編譯的文件名寫(xiě)在add_library
中的SHARED
后面即可晰骑。
最后點(diǎn)擊綠色的小錘子編譯一下項(xiàng)目适秩,然后可以看到libNativeHello.dll文件,這個(gè)就是我們要的C函數(shù)庫(kù)硕舆,現(xiàn)在把它c(diǎn)opy到桌面或者容易找的地方。
3. Java中調(diào)用C函數(shù)庫(kù)
回到IDEA骤公,在Hello.java中略作修改:
package com.leqi.example;
public class Hello {
public static native String helloFromNative();
public static void main(String[] args) {
System.load("剛才編譯好的.dll文件的絕對(duì)路徑");
System.out.println(helloFromNative());
}
}
運(yùn)行看一下結(jié)果
OK抚官,大功告成,接下來(lái)我會(huì)寫(xiě)一篇C和Java之間傳遞復(fù)雜數(shù)據(jù)類型的文章阶捆。