简介
JNI是Java本机接口(Java Native Interface),是本机编程接口,是本机编程接口Java软件开发工具箱(Java Software Development Kit,SDK)的一部分。JNI允许Java用其他语言编写代码和代码库。Invocation API(JNI可用于未来Java虚拟机(JVM)嵌入本机应用程序,允许程序员从本机代码中调用Java代码。
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 staticvoid 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这些目录下。
最后的工程目录如下