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

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

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

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

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

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

    package test;

    public class LinkDll
    {
        //從指定地址讀數(shù)據(jù)
        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" );//如果執(zhí)行環(huán)境是linux這里加載的是SO文件,如果是windows環(huán)境這里加載的是dll文件
        }
    }

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

    /* 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為了實現(xiàn)和dll文件的通信,已經(jīng)按它的標(biāo)準(zhǔn)對方法名/參數(shù)類型/參數(shù)數(shù)目作了一定的處理,其中的JNIEnv*/jobjtct這兩個參數(shù)是每個JNI方法固有的參數(shù),javah命令負責(zé)按JNI標(biāo)準(zhǔn)為每個java方法加上這兩個參數(shù).JNIEnv是指向類型為JNIEnv_的一個特殊JNI數(shù)據(jù)結(jié)構(gòu)的指針,當(dāng)由C++編譯器編譯時JNIEnv_結(jié)構(gòu)其實被定義為一個類,這個類中定義了很多內(nèi)嵌函數(shù),通過使用"->"符號,可以很方便使用這些函數(shù),如:
        (env)->NewString( jchar* c, jint len )
        可以從指針c指向的地址開始讀取len個字符封裝成一個JString類型的數(shù)據(jù).
        其中的jchar對應(yīng)于c/c++中的char,jint對應(yīng)于c/c++中的len,JString對應(yīng)于java中的String,通過查看jni.h可以看到這些數(shù)據(jù)類型其實都是根據(jù)java和c/c++中的數(shù)據(jù)類型對應(yīng)關(guān)系使用typedef關(guān)鍵字重新定義的基本數(shù)據(jù)類型或結(jié)構(gòu)體.
        具體的對應(yīng)關(guān)系如下:
    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對象,或者沒有對應(yīng)java類型的對象
    Class         jclass                  Class對象
    String          jstring                  字符串對象
    Object[]      jobjectArray         任何對象的數(shù)組
    boolean[]    jbooleanArray     布爾型數(shù)組
    byte[]          jbyteArray           比特型數(shù)組
    char[]           jcharArray            字符型數(shù)組
    short[]          jshortArray           短整型數(shù)組
    int[]             jintArray                整型數(shù)組
    long[]          jlongArray             長整型數(shù)組
    float[]         jfloatArray              浮點型數(shù)組
    double[]     jdoubleArray        雙浮點型數(shù)組
        更為詳細的資料可以查閱JNI文檔.
        需要注意的問題:test_LinkDll.h文件包含了jni.h文件;

    3>使用vc++ 6.0編寫TestDll.dll文件,這個文件名是和java類中l(wèi)oadLibrary的名稱一致.
    a>使用vc++6.0 新建一個Win32 Dynamic-Link Library的工程文件,工程名指定為TestDll
    b>把源代碼文件和頭文件使用"Add Fiels to Project"菜單加載到工程中,若使用c來編碼,源碼文件后綴名為.c,若使用c++來編碼,源碼文件擴展名為.cpp,這個一定要搞清楚,因為對于不同的語言,使用JNIEnv指針的方式是不同的.
    c>在這個文件里調(diào)用設(shè)備商提供的dll文件,設(shè)備商一般提供三種文件:dll/lib/h,這里假設(shè)分別為A.dll/A.lib/A.h.
    這個地方的調(diào)用分為動態(tài)調(diào)用和靜態(tài)調(diào)用靜態(tài)調(diào)用即是只要把被調(diào)用的dll文件放到path路徑下,然后加載lib鏈接文件和.h頭文件即可直接調(diào)用A.dll中的方法:
    把設(shè)備商提供的A.h文件使用"Add Fiels to Project"菜單加載到這個工程中,同時在源代碼文件中要把這個A.h文件使用include包含進來;
    然后依次點擊"Project->settings"菜單,打開link選項卡,把A.lib添加到"Object/library modules"選項中.
    具體的代碼如下:
    //讀出數(shù)據(jù),需要注意的是如果是c程序在調(diào)用JNI函數(shù)時必須在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 )
    {
        //*************************基本數(shù)據(jù)聲明與定義******************************
         HANDLE H_icdev = (HANDLE)ji_icdev;//設(shè)備標(biāo)志符
        __int16 i16_len = (__int16)ji_len;//讀出的數(shù)據(jù)長度,值為3,即3個HEX形式的字符
        __int16 i16_result;//函數(shù)返回值
        __int16 i16_coverResult;//字符轉(zhuǎn)換函數(shù)的返回值
            int i_temp;//用于循環(huán)的中間變量
          jchar jca_result[3] = { 'e', 'r', 'r' };//當(dāng)讀數(shù)據(jù)錯誤時返回此字符串

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

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

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

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

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

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

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

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

    //從指定地址讀數(shù)據(jù)
    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類似的一種數(shù)據(jù)類型,意為Handle to an instance,常用來標(biāo)記App實例,在這個地方首先把A.dll加載到內(nèi)存空間,以一個App的形式存放,然后取

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

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

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

        if ( i_result != 0 )
        {
            printf( "讀數(shù)據(jù)失敗......\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程序調(diào)用第三方dll文件的完整過程,當(dāng)然,在整個過程的工作全部完成以后,就可以使用java類LinkDll中的public String radData( int, int, int )方法了,效果同直接使用c/c++調(diào)用這個設(shè)備商提供的A.dll文件中的readData方法幾乎一樣.

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

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


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


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 亚洲AV无码AV男人的天堂不卡| 亚洲精选在线观看| 色偷偷噜噜噜亚洲男人| 在线观看日本免费a∨视频| 亚洲人成网站在线观看播放动漫 | 中文字幕无码视频手机免费看| 337p欧洲亚洲大胆艺术| 久久成人无码国产免费播放| 亚洲电影免费在线观看| 99re免费在线视频| 亚洲午夜久久久精品电影院| 一个人免费观看视频www| 久久亚洲AV成人无码国产电影| 国产亚洲精品免费| 亚洲免费在线观看| 久久精品国产亚洲夜色AV网站| 88xx成人永久免费观看| 中国亚洲呦女专区| 亚洲中文字幕无码专区| 日韩免费在线观看视频| 亚洲特级aaaaaa毛片| 免费高清在线爱做视频| 免费看美女午夜大片| 国产亚洲精品久久久久秋霞| 午夜不卡久久精品无码免费 | 亚洲午夜国产精品无码老牛影视 | 国产成人免费爽爽爽视频| 亚洲一区二区三区高清在线观看| 浮力影院第一页小视频国产在线观看免费 | 亚洲熟伦熟女新五十路熟妇| 国内精品久久久久影院免费| 亚洲不卡1卡2卡三卡2021麻豆| 日韩精品视频免费观看| 最近免费字幕中文大全| 亚洲中文无码亚洲人成影院| 亚洲伊人久久成综合人影院| 精品一区二区三区免费毛片爱| 亚洲另类无码专区首页| 亚洲精品亚洲人成人网| 成人免费视频一区二区三区| 免费看一区二区三区四区|