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++已經六年沒用過了,真是費勁啊