Java通過JNI調用dll詳細過程
最近項目有這樣一個需求,在已有的CS軟件中添加一個鏈接,將當前登錄用戶的用戶名加密后放在url地址中,在BS的login方法里通過解密判斷,如果為合法用戶則無需再次登錄直接進入平臺,CS軟件方提供了一個加密解密的dll文件,我們需要在action中通過該dll解密,那么就涉及到java調用dll的問題。
首先我選擇了JNI方式(因為網上說的另兩種方式Jawin, Jacob更不會),大體流程如下:
1、寫一個java的class,在類里聲明所調用的庫名稱和需要使用的函數(注意:需要對方法做本地聲明,關鍵字為native。且只需要聲明,而不需要具體實現)
package com; public class javacall { static { System.loadLibrary("htgsjencrypt"); } public native static String DecodeString(char[] szSrc); public native static String EncodeString(char[] szSrc); private static void printCharArray(char[] content) { String temp=new String(content); System.out.println(temp); } public static void main(String[] args) { String s="123"; char[] src=new char[100]; src=s.toCharArray(); String encode=""; printCharArray(src); encode=javacall.EncodeString(src); System.out.println("encode="+encode); String decode=""; src=encode.toCharArray(); decode=javacall.DecodeString(src); System.out.println("decode="+decode); } }
這個地方需要提一下,新建這個class時最好不要建在默認包中,將來對這個工程打包后,在引用的工程中無法找到默認包中的class(也許是我寫的不對,不過寫在默認包中確實會帶來不必要的麻煩)
2、對于以上編譯好的class文件通過使用javah命令生成頭文件javacall.h,這個文件需要被C++程序調用來生成所需的庫文件
/* DO NOT EDIT THIS FILE - it is machine generated */ #include "jni.h" /* Header for class com_javacall */ #ifndef _Included_com_javacall #define _Included_com_javacall #ifdef __cplusplus extern "C" { #endif /* * Class: com_javacall * Method: DecodeString * Signature: ([C)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_javacall_DecodeString (JNIEnv *, jclass, jcharArray); /* * Class: com_javacall * Method: EncodeString * Signature: ([C)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_javacall_EncodeString (JNIEnv *, jclass, jcharArray); #ifdef __cplusplus } #endif #endif
這里需要提到一點,默認生成的頭文件中寫的是
#include <jni.h>
在C++的calss中引用時編譯報錯找不到jni.h,可以去jdk安裝包的include文件夾中拷貝jni.h、jni_md.h、jawt_md.h三個文件到程序目錄,這時再編譯可能還報找不到jni.h的錯誤,可以將#include <jni.h>改為#include "jni.h",因為前者是引用系統頭文件的寫法
3、在VC中新建一個庫文件htgsjencrypt,在新建的class文件中實現java頭文件中聲明的兩個加密解密方法,因為第三方沒有提供.lib文件,也沒有.h文件,那么只能用動態使用鏈接庫的方式來調用dll了,具體代碼如下:
#include "gsjencrypt.h" #include "com_javacall.h" #include "windows.h" #include <iostream> ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// gsjencrypt::gsjencrypt() { } gsjencrypt::~gsjencrypt() { } typedef int (WINAPI *FDecodeString)(char szSrc[100], char szDest[100]); typedef int (WINAPI *FEncodeString)(char szSrc[100], char szDest[100]); JNIEXPORT jstring JNICALL Java_com_javacall_EncodeString(JNIEnv * env, jclass jobject, jcharArray src) { HINSTANCE hDLL; hDLL=LoadLibrary("gsjencrypt.dll");//加載動態鏈接庫gsjencrypt.dll文件; if(hDLL==NULL) return 0; FEncodeString encodeString=(FEncodeString)GetProcAddress(hDLL,"EncodeString"); jsize size = (env)->GetArrayLength(src); jchar * arrayBody = (env)->GetCharArrayElements(src,0); //char * csrc=(char *)arrayBody; char csrctemp[100]=""; int k=0; while(size!=0) { csrctemp[k]=*arrayBody; *arrayBody++; size--; k++; } char cdesttemp[100]=""; encodeString(csrctemp,cdesttemp); (env)->ReleaseCharArrayElements(src,arrayBody,0); return (env)->NewStringUTF(cdesttemp); } JNIEXPORT jstring JNICALL Java_com_javacall_DecodeString(JNIEnv * env, jclass jobject, jcharArray src) { HINSTANCE hDLL; hDLL=LoadLibrary("gsjencrypt.dll");//加載動態鏈接庫gsjencrypt.dll文件; if(hDLL==NULL) return 0; FDecodeString decodeString=(FDecodeString)GetProcAddress(hDLL,"DecodeString"); jsize size = (env)->GetArrayLength(src); jchar * arrayBody = (env)->GetCharArrayElements(src,0); char * csrc=(char *)arrayBody; char csrctemp[100]=""; int k=0; while(size!=0) { csrctemp[k]=*arrayBody; *arrayBody++; size--; k++; } //arrayBody=(env)->GetCharArrayElements(dest,0); //char * cdest=(char *)arrayBody; char cdesttemp[100]=""; decodeString(csrctemp,cdesttemp); (env)->ReleaseCharArrayElements(src,arrayBody,0); return (env)->NewStringUTF(cdesttemp); }
這里需要注意的是
typedef int (WINAPI *FDecodeString)(char szSrc[100], char szDest[100]); typedef int (WINAPI *FEncodeString)(char szSrc[100], char szDest[100]);
以上兩個方法是原始dll中提供給外界調用的函數接口,聲明時一定要記得加WINAPI,否則調用時始終報錯。
4、最后將第三方提供的gsjencrypt.dll和新生成的htgsjencrypt.dll同時拷貝到java.library.path里(jdk或者jre的bin文件中),然后將最開始寫的java程序打包即可被別的工程調用。
記錄一下最難解決的問題,就是不知道怎樣在c++中返回給java解密后的串,因為總是想把指針的概念與java中的某個byte數組或者char數組關聯起來,始終不能成功,最后嘗試使用NewStringUTF才解決問題,哎,C++已經六年沒用過了,真是費勁啊