一、前言
JNI(Java Native Interface)的作用是實現(xiàn)java調(diào)用C/C++寫的方法或開源庫。由于java語言自身的局限性,一些特定功能的開源庫往往是沒有java語言版本的,比如本人項目中需要用到DSP庫,DSP庫C/C++的開源庫數(shù)不勝數(shù),但是JAVA版本的根本找不到,但是我的項目需要開發(fā)出一款app,因此必須使用java編程。為了解決這個矛盾,JNI就派上用場了。
本文將結(jié)合基于C++的DSP開源庫SP++3.0中的傅立葉變換方法講解JNI編程方法和常見錯誤及其解決方法。
二、具體過程
1.編寫java代碼
本文涉及兩個java文件,一個聲明java native方法,另一個含有主函數(shù)main,代碼如下:
class testjava
{
//java文件只需要聲明方法即可,具體實現(xiàn)在c或cpp文件中
public native double[] fft(double[] xn); //返回的數(shù)組的前半部分是傅立葉系數(shù)的實部,
//后半部分是虛部,方法需用native修飾
static {//必須用static修飾
System.loadLibrary("javadll");//加載動態(tài)庫dll,不需要后綴名
}
}
public class main
{
public static void main(String[] args){
testjava fourier=new testjava();
int len=100;
double[] x=new double[len];
int j=0;
for(double t=0;t<len;t++){
x[j++]=Math.cos(2*Math.PI*t);
}
double[]Xk=new double[2*len];
Xk=fourier.fft(x);//調(diào)用fft方法
for(int i=0;i<len;i++){
System.out.println(Xk[i]+" "+Xk[i+len]);
}
}
}
2.編譯testjava.java,并生成.h頭文件
本文全程使用cmd命令行,使用IDE方法類似,具體操作有細微區(qū)別
2.1 cd命令切換至java文件所在目錄后使用javac命令編譯,生成testjava.class
cd D:\java
javac testjava.java
2.2 使用javah命令生成testjava.h頭文件
這里需要注意的是,如果java文件所在路徑?jīng)]有添加進classpath環(huán)境變量中,cmd命令為
javah -classpath . testjava//注意中間有一個英文句號
3.編寫C/C++實現(xiàn)
3.1 頭文件添加
首先要把需要用到的頭文件jni.h,jni_md.h(jni.h需要用到),testjava.h,以及和傅立葉變換方法有關(guān)的fftpf.h,vectormath.h復(fù)制到cpp文件所在路徑,當(dāng)然也可以放到 VS2010的安裝路徑\VC\include。其中jni.h、jni_md.h在JDK安裝路徑下的include文件夾里
3.2 方法實現(xiàn)
javadll.cpp
#include <iostream>
#include <cstdlib>
#include<stdio.h>
#include <jni.h>
#include <fftpf.h>
#include <vectormath.h>
#include "testjava.h"
using namespace std;
using namespace splab;
JNIEXPORT jdoubleArray JNICALL Java_testjava_fft
(JNIEnv *env, jobject jo, jdoubleArray xn){
jsize len=env->GetArrayLength(xn); //獲取數(shù)組長度
jdouble *p = env->GetDoubleArrayElements(xn,0);
Vector<double> signal(len,p);
FFTPF<double> Fourier;
Vector<complex<double>> XK;
XK.resize(len);
Fourier.fft(signal,XK);
Vector<double> a;
Vector<double> b;
a.resize(len);
b.resize(len);
a=real(XK);
b=imag(XK);
jdoubleArray result;
result=env->NewDoubleArray(2*len);
jdouble *p2 = env->GetDoubleArrayElements(result,0);
for(int i=0;i<XK.size();i++){
p2[i]=a[i];
}
for(int i=XK.size();i<2*len;i++){
p2[i]=b[i-XK.size()];
}
env->ReleaseDoubleArrayElements(xn,p,0);
env->ReleaseDoubleArrayElements(result,p2,0);
return result;
}
對比我們在testjava.java和testjava.h中的函數(shù)聲明(如下),我們發(fā)現(xiàn)java中的double[]變?yōu)榱薺doubleArray,這里怎么理解呢?可以這樣理解,在C/C++中,既可以使用C/C++原本的數(shù)據(jù)類型,又可以使用jdoubleArray這些數(shù)據(jù)類型。其他數(shù)據(jù)類型的對應(yīng)關(guān)系以及jint、jObjectArray、jsize這類數(shù)據(jù)的操作方法見附件(很好的一份文檔)。特別要注意的是文檔中提到的C和C++語法的差異,比如javadll.cpp中有注釋的一行,如果是使用C語言,則應(yīng)寫為
jsize len=(*env)->GetArrayLength(env,xn); //獲取數(shù)組長度
很好理解,這是因為C++是面向?qū)ο蟮模蓄愡@一概念
testjava.h中的函數(shù)聲明
JNIEXPORT jdoubleArray JNICALL Java_testjava_fft
(JNIEnv *, jobject, jdoubleArray);//testjava.h中輸入?yún)?shù)是沒有參數(shù)名的,
//在javadll.cpp中實現(xiàn)方法時應(yīng)當(dāng)加上參數(shù)名
testjava.java中的函數(shù)聲明
public native double[] fft(double[] xn);
4.編譯cpp文件得到動態(tài)鏈接庫文件javadll.dll
cd命令切換到VS2010安裝路徑\VC\bin\amd64,之后輸入vcvars64并回車啟動編譯器
再切換至cpp文件所在路徑,之后使用cl命令生成dll文件
cl -LD javadll.cpp
之后可以在cpp所在路徑下看到生成了javadll.dll,javadll.lib等文件
在這個過程中,需要注意的是dll的位數(shù)和所安裝的JDK的位數(shù)要一致,否則下一步運行時會報錯。
具體來說,VS2010的安裝路徑下有兩個編譯器,分別是32位和64位的,64位的路徑見上面,32位的路徑為VS2010的安裝路徑\Common7\Tools\vcvars32
如果你的JDK是64位的,就要使用vcvars64,否則使用vcvars32。JDK版本可以通過命令行的java -version命令獲得,若執(zhí)行命令后出現(xiàn)64-bits,則為64位,沒出現(xiàn)的為32位。
如果你是使用IDE,不是cmd,怎么辦呢?見文末附件。
5.運行java程序
首先要把javadll.dll復(fù)制到j(luò)ava文件所在路徑并切換至java文件所在路徑,其次用javac命令編譯,java命令運行。
javac命令編譯時注意,testjava.java和main.java需要一起編譯,否則會報錯。
javac *.java
最后運行,
java -classpath . main
引入-classpath的原因和javah命令一樣
運行截圖:

6.附件
1.附件1講解了JNI的常見操作方法
http://www.doc88.com/p-403985462945.html
2.附件2是Youtube視頻,講解了cmd模式下的jni編程示例
https://www.youtube.com/watch?v=tDhOPYi-rYE&spfreload=1
3.附件3是本人自己寫的用VS2010創(chuàng)建dll的過程以及Eclipse中如何修改只單個項目所用的JDK版本而不用改變本機的JDK版本。
鏈接: https://pan.baidu.com/s/1o8A0OOi 密碼: 238d