简介

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这些目录下。

最后的工程目录如下

springboot 调用static python springboot调用c++动态库_java