標籤:
源: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 __cplusplusextern "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++已經六年沒用過了,真是費勁啊
Java通過JNI調用dll詳細過程(轉)