Java通過JNI調用dll詳細過程

jopen 12年前發布 | 31K 次閱讀 JavaScript

最近項目有這樣一個需求,在已有的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++已經六年沒用過了,真是費勁啊

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!