简介
JNI是Java本机接口(Java Native Interface),是一个本机编程接口,它是Java软件开发工具箱(Java Software Development Kit,SDK)的一部分。JNI允许Java代码使用以其他语言编写的代码和代码库。Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。
Java调用动态库的步骤一般如下:
- 编写带有native声明的方法的Java类
- 使用javac命令编译编写的Java类
- 使用java -jni ****来生成后缀名为.h的头文件
- 使用其他语言(C、C++)实现本地方法
- 将本地方法编写的文件生成动态链接库
- 将动态库打包到java工程中调用
下面就一步一步的来实现:
一、编写Java类
public class JniSignLib {
static {
System.loadLibrary("certlib");
}
public static native byte[] sign(String profile, byte[] data, int datalen);
public static native int check(String certpath, byte[] data, int datalen, byte[] sigdata, int siglen);
public static native int certFileCheck(String certfile);
public static native int certVerify(String certPath, String caPath);
public static native CertInfo getcertinfo(String certPath);
}
对应的一个javabean
public class CertInfo {
String c_begindate;
String c_enddate;
String c_subject;
String c_issuer;
String c_sn;
String c_ver;
}
二、生成class文件
进入工程的src\main\java\ 目录,执行命令
javah -classpath . -jni com.test.jni.JniSignLib
在java类的目录下生成了class文件。
三、生成.h文件
还是在java目录下,执行命令
javah -classpath . -jni -encoding UTF-8 com.test.jni.JniSignLib
在工程的java目录下会生成响应的com_test_jni_JniSignLib.h文件
四、实现c++方法
这里着重讲一下函数的返回值,
第一个函数返回的java的byte[],从C++的char[]转换过来
JNIEXPORT jbyteArray Java_com_test_jni_JniSignLib_sign(JNIEnv *env, jclass obj, jstring prifile, jbyteArray data, jint datalen)
{
int rv=0;
jboolean b = true;
unsigned char * pindata=NULL;
unsigned char sigdata[64]={0};
unsigned int siglen=0;
const char *pk = env->GetStringUTFChars(prifile, &b);
jbyte *pdata = env->GetByteArrayElements(data,0);
pindata= (unsigned char *)malloc(datalen);
memcpy(pindata,pdata,datalen);
rv=edge_sign((char*)pk,pindata,datalen,sigdata,&siglen);//
if (rv!=0)
{
free(pindata);
return NULL;
}
free(pindata);
// 返回给java的byte[]
jbyteArray array = env->NewByteArray(64);
env->SetByteArrayRegion(array, 0, 64, reinterpret_cast<jbyte *>(sigdata));
return array;
}
第二个是Java的CertInfo,通过,env构造一个对象,再通过C++中的结果给bean的每个字段赋值
JNIEXPORT jobject Java_com_test_jni_JniSignLib_getcertinfo(JNIEnv *env, jclass obj, jstring certpath)
{
int rv=0;
C_INFO certinfo;
jboolean b = true;
const char *certPtr = env->GetStringUTFChars(certpath, &b);
rv=getcertinfo(certPtr,certinfo);
if (rv!=0)
{
return NULL;
}
jobject objValue = NULL;
//获取Java中的实例类
jclass objectClass = (env)->FindClass("com/test/jni/CertInfo");
// 获取构造函数新建对象
jmethodID construct_Result = env->GetMethodID(objectClass, "<init>", "()V");
objValue = env->NewObject(objectClass, construct_Result, "");
//获取类中每一个变量的定义
jfieldID bdt = (env)->GetFieldID(objectClass,"c_begindate","Ljava/lang/String;");
jfieldID edt = (env)->GetFieldID(objectClass,"c_enddate","Ljava/lang/String;");
jfieldID sub = (env)->GetFieldID(objectClass,"c_subject","Ljava/lang/String;");
jfieldID iss = (env)->GetFieldID(objectClass,"c_issuer","Ljava/lang/String;");
jfieldID csn = (env)->GetFieldID(objectClass,"c_sn","Ljava/lang/String;");
jfieldID ver = (env)->GetFieldID(objectClass,"c_ver","Ljava/lang/String;");
//给每一个实例的变量付值
(env)->SetObjectField(objValue,bdt,(env)->NewStringUTF(certinfo.c_begindate));
(env)->SetObjectField(objValue,edt,(env)->NewStringUTF(certinfo.c_enddate));
(env)->SetObjectField(objValue,sub,(env)->NewStringUTF(certinfo.c_subject));
(env)->SetObjectField(objValue,iss,(env)->NewStringUTF(certinfo.c_issuer));
(env)->SetObjectField(objValue,csn,(env)->NewStringUTF(certinfo.c_sn));
(env)->SetObjectField(objValue,ver,(env)->NewStringUTF(certinfo.c_ver));
return objValue;
}
五、打包成动态库
这儿要注意的是32位还是64位,与Java的环境一致。
六、打包Java工程
这儿的坑比较多。
将打包好的dll或so文件放到工程的resources目录下,
在最开始的jni定义中,写了System.loadLibrary("certlib");
在实际打成jar包运行的时候是找不到这个文件的。原因是jar包运行时resources文件不会存放在本地路径中,因此需要换一种方式来读取动态库,新建一个工具类:
public class LibLoader {
public static void loadLib(String libName) {
String resourcePath = "/" + libName;
String folderName = System.getProperty("java.io.tmpdir") + "/lib/";
File folder = new File(folderName);
folder.mkdirs();
File libFile = new File(folder, libName);
if (libFile.exists()) {
System.load(libFile.getAbsolutePath());
} else {
try {
InputStream in = LibLoader.class.getResourceAsStream(resourcePath);
FileUtils.copyInputStreamToFile(in, libFile);
in.close();
System.load(libFile.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to load required lib", e);
}
}
}
}
大致原理就是使用JavaClass的getResourceAsStream
函数获取动态库文件的流,然后将这个流保存在本地路径中,最后使用System.load
从本地路径读取动态库。
修改JniSignLib
public class JniSignLib {
static {
LibLoader.loadLib("libsecurejni.so");
}
public static native byte[] sign(String profile, byte[] data, int datalen);
public static native int check(String certpath, byte[] data, int datalen, byte[] sigdata, int siglen);
public static native int certFileCheck(String certfile);
public static native int certVerify(String certPath, String caPath);
public static native CertInfo getcertinfo(String certPath);
}
另外还有一点,如果在windows环境下,dll引用的windows的动态库,如msvcr100d.dll,这个库放到resources目录下也找不到,得放到window的system32下面去。同样如果在linux环境下的系统库,也得放到/lib64或/usr/lib64这些目录下。
最后的工程目录如下