<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    使用JNI技術實現JAVA程序調用dll

    Posted on 2011-07-28 12:37 無很 閱讀(440) 評論(0)  編輯  收藏

    JAVA的跨平臺的特性深受java程序員們的喜愛,但正是由于它為了實現跨平臺的目的,使得它和本地機器的各種內部聯系變得很少,大大約束了它的功能,比如與一些硬件設備通信,往往要花費很大的精力去設計流程編寫代碼去管理設備端口,而且有一些設備廠商提供的硬件接口已經經過一定的封裝和處理,不能直接使用java程序通過端口和設備通信,這種情況下就得考慮使用java程序去調用比較擅長同系統打交道的第三方程序,從1.1版本開始的JDK提供了解決這個問題的技術標準:JNI技術.
           JNI是Java Native Interface(Java本地接口)的縮寫,本地是相對于java程序來說的,指直接運行在操作系統之上,與操作系統直接交互的程序.從1.1版本的JDK開始,JNI就作為標準平臺的一部分發行.在JNI出現的初期是為了Java程序與本地已編譯語言,尤其是C和C++的互操作而設計的,后來經過擴展也可以與c和c++之外的語言編寫的程序交互,例如Delphi程序.
           使用JNI技術固然增強了java程序的性能和功能,但是它也破壞了java的跨平臺的優點,影響程序的可移植性和安全性,例如由于其他語言(如C/C++)可能能夠隨意地分配對象/占用內存,Java的指針安全性得不到保證.但在有些情況下,使用JNI是可以接受的,甚至是必須的,例如上面提到的使用java程序調用硬件廠商提供的類庫同設備通信等,目前市場上的許多讀卡器設備就是這種情況.在這必須使用JNI的情況下,盡量把所有本地方法都封裝在單個類中,這個類調用單個的本地庫文件,并保證對于每種目標操作系統,都可以用特定于適當平臺的版本替換這個文件,這樣使用JNI得到的要比失去的多很多.
           現在開始討論上面提到的問題,一般設備商會提供兩種類型的類庫文件,windows系統的會包含.dll/.h/.lib文件,而linux系統的會包含.so/.a文件,這里只討論windows系統下的c/c++編譯的dll文件調用方法.
           我把設備商提供的dll文件稱之為第三方dll文件,之所以說第三方,是因為JNI直接調用的是按它的標準使用c/c++語言編譯的dll文件,這個文件是客戶程序員按照設備商提供的.h文件中的列出的方法編寫的dll文件,我稱之為第二方dll文件,真正調用設備商提供的dll文件的其實就是這個第二方dll文件.到這里,解決問題的思路已經產生了,大慨分可以分為三步:
           1>編寫一個java類,這個類包含的方法是按照設備商提供的.h文件經過變形/轉換處理過的,并且必須使用native定義.這個地方需要注意的問題是java程序中定義的方法不必追求和廠商提供的頭文件列出的方法清單中的方法具有相同的名字/返回值/參數,因為一些參數類型如指針等在java中沒法模擬,只要能保證這個方法能實現原dll文件中的方法提供的功能就行了;
           2>按JNI的規則使用c/c++語言編寫一個dll程序;
           3>按dll調用dll的規則在自己編寫的dll程序里面調用廠商提供的dll程序中定義的方法.

           我之前為了給一個java項目添加IC卡讀寫功能,曾經查了很多資料發現查到的資料都是只說到第二步,所以剩下的就只好自己動手研究了.下面結合具體的代碼來按這三個步驟分析.

         1>假設廠商提供的.h文件中定義了一個我們需要的方法:
          __int16 __stdcall readData( HANDLE icdev, __int16 offset, __int16 len, unsigned char *data_buffer );
          a.__int16定義了一個不依賴于具體的硬件和軟件環境,在任何環境下都占16 bit的整型數據(java中的int類型是32 bit),這個數據類型是vc++中特定的數據類型,所以我自己做的dll也是用的vc++來編譯.
         b.__stdcall表示這個函數可以被其它程序調用,vc++編譯的DLL欲被其他語言編寫的程序調用,應將函數的調用方式聲明為__stdcall方式,WINAPI都采用這種方式.c/c++語言默認的調用方式是__cdecl,所以在自己做可被java程序調用的dll時一定要加上__stdcall的聲明,否則在java程序執行時會報類型不匹配的錯誤.
         c.HANDLE icdev是windows操作系統中的一個概念,屬于win32的一種數據類型,代表一個核心對象在某一個進程中的唯一索引,不是指針,在知道這個索引代表的對象類型時可以強制轉換成此類型的數據.
        這些知識都屬于win32編程的范圍,更為詳細的win32資料可以查閱相關的文檔.
        這個方法的原始含義是通過設備初始時產生的設備標志號icdev,讀取從某字符串在內存空間中的相對超始位置offset開始的共len個字符,并存放到data_buffer指向的無符號字符類型的內存空間中,并返回一個16 bit的整型值來標志這次的讀設備是否成功,這里真正需要的是unsigned char *這個指針指向的地址存放的數據,而java中沒有指針類型,所以可以考慮定義一個返回字符串類型的java方法,原方法中返回的整型值也可以按經過一定的規則處理按字符串類型傳出,由于HANDLE是一個類型于java中的Ojbect類型的數據,可以把它當作int類型處理,這樣java程序中的方法定義就已經形成了:
        String readData( int icdev, int offset, int len );
        聲明這個方法的時候要加上native關鍵字,表明這是一個與本地方法通信的java方法,同時為了安全起見,此文方法要對其它類隱藏,使用private聲明,再另外寫一個public方法去調用它,同時要在這個類中把本地文件加載進來,最終的代碼如下:

    package test;

    public class LinkDll
    {
        //從指定地址讀數據
        private native String readData( int icdev, int offset, int len );
        public String readData( int icdev, int offset, int len )
        {
            return this.readDataTemp( icdev, offset, len );
        }

        static 
        {        
            System.loadLibrary( "TestDll" );//如果執行環境是linux這里加載的是SO文件,如果是windows環境這里加載的是dll文件
        }
    }

    2>使用JDK的javah命令為這個類生成一個包含類中的方法定義的.h文件,可進入到class文件包的根目錄下(只要是在classpath參數中的路徑即可),使用javah命令的時候要加上包名javah test.LinkDll,命令成功后生成一個名為test_LinkDll.h的頭文件.
        文件內容如下:

    /* DO NOT EDIT THIS FILE - it is machine generated*/
    #include <jni.h>

    /* Header for class test_LinkDll */
    #ifndef _Included_test_LinkDll #define

    Included_test_LinkDll
    #ifdef __cplusplus extern "C" { #endif
    /*
    * Class:     test_LinkDll
    * Method:    readDataTemp
    * Signature: (III)Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_test_LinkDll_readDataTemp(JNIEnv *, jobject, jint, jint, jint);
    #ifdef __cplusplus } #endif
    #endif

        可以看出,JNI為了實現和dll文件的通信,已經按它的標準對方法名/參數類型/參數數目作了一定的處理,其中的JNIEnv*/jobjtct這兩個參數是每個JNI方法固有的參數,javah命令負責按JNI標準為每個java方法加上這兩個參數.JNIEnv是指向類型為JNIEnv_的一個特殊JNI數據結構的指針,當由C++編譯器編譯時JNIEnv_結構其實被定義為一個類,這個類中定義了很多內嵌函數,通過使用"->"符號,可以很方便使用這些函數,如:
        (env)->NewString( jchar* c, jint len )
        可以從指針c指向的地址開始讀取len個字符封裝成一個JString類型的數據.
        其中的jchar對應于c/c++中的char,jint對應于c/c++中的len,JString對應于java中的String,通過查看jni.h可以看到這些數據類型其實都是根據java和c/c++中的數據類型對應關系使用typedef關鍵字重新定義的基本數據類型或結構體.
        具體的對應關系如下:
    Java類型     本地類型             描述
    boolean       jboolean             C/C++8位整型
    byte             jbyte                   C/C++帶符號的8位整型
    char             jchar                   C/C++無符號的16位整型
    short            jshort                  C/C++帶符號的16位整型
    int                 jint                      C/C++帶符號的32位整型
    long             jlong                   C/C++帶符號的64位整型e
    float             jfloat                   C/C++32位浮點型
    double        jdouble               C/C++64位浮點型
    Object          jobject                 任何Java對象,或者沒有對應java類型的對象
    Class         jclass                  Class對象
    String          jstring                  字符串對象
    Object[]      jobjectArray         任何對象的數組
    boolean[]    jbooleanArray     布爾型數組
    byte[]          jbyteArray           比特型數組
    char[]           jcharArray            字符型數組
    short[]          jshortArray           短整型數組
    int[]             jintArray                整型數組
    long[]          jlongArray             長整型數組
    float[]         jfloatArray              浮點型數組
    double[]     jdoubleArray        雙浮點型數組
        更為詳細的資料可以查閱JNI文檔.
        需要注意的問題:test_LinkDll.h文件包含了jni.h文件;

    3>使用vc++ 6.0編寫TestDll.dll文件,這個文件名是和java類中loadLibrary的名稱一致.
    a>使用vc++6.0 新建一個Win32 Dynamic-Link Library的工程文件,工程名指定為TestDll
    b>把源代碼文件和頭文件使用"Add Fiels to Project"菜單加載到工程中,若使用c來編碼,源碼文件后綴名為.c,若使用c++來編碼,源碼文件擴展名為.cpp,這個一定要搞清楚,因為對于不同的語言,使用JNIEnv指針的方式是不同的.
    c>在這個文件里調用設備商提供的dll文件,設備商一般提供三種文件:dll/lib/h,這里假設分別為A.dll/A.lib/A.h.
    這個地方的調用分為動態調用和靜態調用靜態調用即是只要把被調用的dll文件放到path路徑下,然后加載lib鏈接文件和.h頭文件即可直接調用A.dll中的方法:
    把設備商提供的A.h文件使用"Add Fiels to Project"菜單加載到這個工程中,同時在源代碼文件中要把這個A.h文件使用include包含進來;
    然后依次點擊"Project->settings"菜單,打開link選項卡,把A.lib添加到"Object/library modules"選項中.
    具體的代碼如下:
    //讀出數據,需要注意的是如果是c程序在調用JNI函數時必須在JNIEnv的變量名前加*,如(*env)->xxx,如果是c++程序,則直接使用(env)->xxx

    #include<WINDOWS.H>
    #include<MALLOC.H>
    #include<STDIO.H>
    #include<jni.h>
    #include "test_LinkDll.h"
    #include "A.h"

    JNIEXPORT jstring JNICALL Java_test_LinkDll_readDataTemp( JNIEnv *env, jobject jo, jint ji_icdev, jint ji_len )
    {
        //*************************基本數據聲明與定義******************************
         HANDLE H_icdev = (HANDLE)ji_icdev;//設備標志符
        __int16 i16_len = (__int16)ji_len;//讀出的數據長度,值為3,即3個HEX形式的字符
        __int16 i16_result;//函數返回值
        __int16 i16_coverResult;//字符轉換函數的返回值
            int i_temp;//用于循環的中間變量
          jchar jca_result[3] = { 'e', 'r', 'r' };//當讀數據錯誤時返回此字符串

        //無符號字符指針,指向的內存空間用于存放讀出的HEX形式的數據字符串
        unsigned char* uncp_hex_passward = (unsigned char*)malloc( i16_len );
        //無符號字符指針,指向的內存空間存放從HEX形式轉換為ASC形式的數據字符串
        unsigned char* uncp_asc_passward = (unsigned char*)malloc( i16_len * 2 );
        //java char指針,指向的內存空間存放從存放ASC形式數據字符串空間讀出的數據字符串
        jchar *jcp_data = (jchar*)malloc(i16_len*2+1);
        //java String,存放從java char數組生成的String字符串,并返回給調用者
        jstring js_data = 0;

        //*********讀出3個HEX形式的數據字符到uncp_hex_data指定的內存空間**********
        i16_result = readData( H_icdev, 6, uncp_hex_data );//這里直接調用的是設備商提供的原型方法.

        if ( i16_result != 0 )
        {
            printf( "讀卡錯誤......\n" );
            //這個地方調用JNI定義的方法NewString(jchar*,jint),把jchar字符串轉換為JString類型數據,返回到java程序中即是String
            return (env)->NewString( jca_result, 3 );
        }

        printf( "讀數據成功......\n" );

        //**************HEX形式的數據字符串轉換為ASC形式的數據字符串**************
        i16_coverResult = hex_asc( uncp_hex_data, uncp_asc_data, 3 );
        if ( i16_coverResult != 0 )
        {
            printf( "字符轉換錯誤!\n" );
            return (env)->NewString( jca_result, 3 );
        }

        //**********ASC char形式的數據字符串轉換為jchar形式的數據字符串***********
        for ( i_temp = 0; i_temp < i16_len; i_temp++ ) 
            jcp_data[i_temp] = uncp_hex_data[i_temp];
        //******************jchar形式的數據字符串轉換為java String****************
        js_data = (env)->NewString(jcp_data,i16_len); 
        return js_data;
    }

    動態調用,不需要lib文件,直接加載A.dll文件,并把其中的文件再次聲明,代碼如下:
    #include<STDIO.H>
    #include<WINDOWS.H>
    #include "test_LinkDll.h"

    //首先聲明一個臨時方法,這個方法名可以隨意定義,但參數同設備商提供的原型方法的參數保持一致.
    typedef int ( *readDataTemp )( int, int, int, unsigned char * );//從指定地址讀數據

    //從指定地址讀數據
    JNIEXPORT jstring JNICALL Java_readDataTemp( JNIEnv *env, jobject jo, jint ji_icdev, jint ji_offset, jint ji_len )
    {
        int i_temp;
        int i_result;
        int i_icdev = (int)ji_icdev;
        int i_offset = (int)ji_offset;
        int i_len = (int)ji_len;
        jchar jca_result[5] = { 'e', 'r', 'r' };
        unsigned char *uncp_data = (unsigned char*)malloc(i_len);
        jchar *jcp_data = (jchar *)malloc(i_len);
        jstring js_data = 0;
        //HINSTANCE是win32中同HANDLE類似的一種數據類型,意為Handle to an instance,常用來標記App實例,在這個地方首先把A.dll加載到內存空間,以一個App的形式存放,然后取

    得它的instance交給dllhandle,以備其它資源使用.
        HINSTANCE dllhandle;
        dllhandle = LoadLibrary( "A.dll" );
        //這個地方首先定義一個已聲明過的臨時方法,此臨時方法相當于一個結構體,它和設備商提供的原型方法具有相同的參數結構,可互相轉換
        readDataTemp readData;

        //使用win32的GetProcAddress方法取得A.dll中定義的名為readData的方法,并把這個方法轉換為已被定義好的同結構的臨時方法,
        //然后在下面的程序中,就可以使用這個臨時方法了,使用這個臨時方法在這時等同于使用A.dll中的原型方法.
        readData = (readDataTemp) GetProcAddress( dllhandle, "readData" );

        i_result = (*readData)( i_icdev, i_offset, i_len, uncp_data );

        if ( i_result != 0 )
        {
            printf( "讀數據失敗......\n" );
            return (env)->NewString( jca_result, 3 );
        }

        for ( i_temp = 0; i_temp < i_len; i_temp++ )
        {
            jcp_data[i_temp] = uncp_data[i_temp];
        }

        js_data = (env)->NewString( jcp_data, i_len );

        return js_data;
    }

    4>以上即是一個java程序調用第三方dll文件的完整過程,當然,在整個過程的工作全部完成以后,就可以使用java類LinkDll中的public String radData( int, int, int )方法了,效果同直接使用c/c++調用這個設備商提供的A.dll文件中的readData方法幾乎一樣.

    總結:JNI技術確實是提高了java程序的執行效率,并且擴展了java程序的功能,但它也確確實實破壞了java程序的最重要的優點:平臺無關性,所以除非必須(不得不)使用JNI技術,一般還是提倡寫100%純java的程序.根據自己的經驗及查閱的一些資料,把可以使用JNI技術的情況羅列如下:
        1>需要直接操作物理設備,而沒有相關的驅動程序,這時候我們可能需要用C甚至匯編語言來編寫該設備的驅動,然后通過JNI調用;
        2>涉及大量數學運算的部分,用java會帶來些效率上的損失;
        3>用java會產生系統難以支付的開銷,如需要大量網絡鏈接的場合;
        4>存在大量可重用的c/c++代碼,通過JNI可以減少開發工作量,避免重復開發.
    另外,在利用JNI技術的時候要注意以下幾點:
        1>由于Java安全機制的限制,不要試圖通過Jar文件的方式發布包含本地化方法的Applet到客戶端;
        2>注意內存管理問題,雖然在本地方法返回Java后將自動釋放局部引用,但過多的局部引用將使虛擬機在執行本地方法時耗盡內存;
        3>JNI技術不僅可以讓java程序調用c/c++代碼,也可以讓c/c++代碼調用java代碼.

    注:有一個名叫Jawin開源項目實現了直接讀取第三方dll文件,不用自己辛苦去手寫一個起傳值轉換作用的dll文件,有興趣的可以研究一下.但是我用的時候不太順手,有很多規則限制,像自己寫程序時可以隨意定義返回值,隨意轉換類型,用這個包的話這些都是不可能的了,所以我的項目還沒開始就把它拋棄了.


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 免费在线观看a级毛片| 国产男女猛烈无遮档免费视频网站 | 最新国产精品亚洲| 欧洲乱码伦视频免费| 亚洲美女自拍视频| 久久天天躁狠狠躁夜夜免费观看| 亚洲性猛交xx乱| AV免费网址在线观看| 亚洲人成人无码.www石榴| 日日操夜夜操免费视频| 日韩在线视频播放免费视频完整版| 亚洲高清无码在线观看| 中文字幕一区二区三区免费视频 | 亚洲精品久久久www | 亚洲综合色一区二区三区| 大陆一级毛片免费视频观看 | 久久精品亚洲男人的天堂| 久久久WWW成人免费精品| 久久精品国产96精品亚洲 | 亚洲成色在线综合网站| 8888四色奇米在线观看免费看| 亚洲啪啪免费视频| 精品国产免费观看久久久| WWW国产成人免费观看视频| 亚洲AV永久无码精品水牛影视| 51在线视频免费观看视频| 亚洲国产精品网站在线播放 | 亚洲精品无码少妇30P| 亚洲精品国产综合久久一线| 免费看一区二区三区四区| 亚洲国产精品白丝在线观看| 日本免费v片一二三区| 久青草视频在线观看免费| 亚洲一级毛片免观看| 亚洲国产午夜中文字幕精品黄网站| a级毛片在线视频免费观看| 亚洲卡一卡二卡乱码新区| 国产成人精品日本亚洲专区61| 免费观看黄色的网站| 一级成人毛片免费观看| 亚洲最大黄色网址|