羅尹奇+劉力銀
摘要:為解決在不同平臺(tái)下Java串口通信問(wèn)題,設(shè)計(jì)了一種通用的Java串口通信系統(tǒng)。該系統(tǒng)采用三層體系架構(gòu),包含了Java接口調(diào)用層、本地接口調(diào)用層和本地實(shí)現(xiàn)層;Java接口調(diào)用層負(fù)責(zé)定義串口通信的功能函數(shù),本地接口調(diào)用層則由JNI(Java Native Interface)技術(shù)生成,定義本地代碼的函數(shù)調(diào)用接口,本地實(shí)現(xiàn)層利用操作系統(tǒng)API實(shí)現(xiàn)串口通信功能。根據(jù)該系統(tǒng)設(shè)計(jì),以Windows平臺(tái)為例,實(shí)現(xiàn)了在Windows平臺(tái)下的Java串口通信。通過(guò)功能測(cè)試實(shí)驗(yàn)表明該系統(tǒng)設(shè)計(jì)能正確地完成串口通信功能。
關(guān)鍵詞:JNI(Java Native Interface);本地方法;串口通信;Windows
中圖分類(lèi)號(hào):TP311 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2017)34-0051-06
Abstract: In order to solve the problem of Java serial communication in different platforms, a general Java serial communication system is designed in this paper. The system uses three layer architecture, including Java interface layer, native interface layer and a local implementation layer; Java interface layer is responsible for the function definition of serial communication, native interface layer is formed by JNI (Java Native Interface) technique and the function interface to define the local code, local implementation layer using operation API realize the serial communication function. According to the design of the system, this paper takes the Windows platform as an example to realize the Java serial communication under the Windows platform. The function test shows that the design of the system can correctly complete the serial communication function.
Key words: JNI(Java Native Interface); native method;serial communication; Windows
隨著物聯(lián)網(wǎng)技術(shù)的不斷發(fā)展,串口通信技術(shù)作為一項(xiàng)十分重要的數(shù)據(jù)傳輸手段正得到越來(lái)越廣泛的應(yīng)用[1]。在傳統(tǒng)的應(yīng)用領(lǐng)域里,串口設(shè)備的訪問(wèn)均是基于C/C++本地代碼實(shí)現(xiàn)的,雖然具備較高的訪問(wèn)性能,但其跨平臺(tái)性則相對(duì)受限;而Java程序在跨平臺(tái)方面具備得天獨(dú)厚的優(yōu)勢(shì),其體系結(jié)構(gòu)無(wú)關(guān)性正受到越來(lái)越多的企業(yè)級(jí)服務(wù)的青睞[2-3]。然而Java的跨平臺(tái)特性也為其帶來(lái)了一定的局限性,部分與平臺(tái)相關(guān)的功能無(wú)法得到良好支持[4],在對(duì)本地硬件設(shè)備訪問(wèn)方面(諸如串口設(shè)備等)就是受限情況之一。
JNI技術(shù)(Java Native Interface)作為Java訪問(wèn)C/C++本地代碼的接口,可以實(shí)現(xiàn)對(duì)本地動(dòng)態(tài)庫(kù)的調(diào)用,既彌補(bǔ)了Java的不足,同時(shí)也兼具了跨平臺(tái)的優(yōu)勢(shì)[3]。通過(guò)該技術(shù)可以將與平臺(tái)相關(guān)的串口通信同跨平臺(tái)的Java應(yīng)用結(jié)合起來(lái),實(shí)現(xiàn)Java程序?qū)Υ谠O(shè)備的訪問(wèn)。
1 關(guān)鍵技術(shù)簡(jiǎn)介
1.1 JNI(Java Native Interface)
JNI(Java Native Interface)是Java本地程序接口,屬于JDK的一部分[3]。JNI允許運(yùn)行在Java 虛擬機(jī)(JavaVirtual Machine,JVM)上的Java代碼操作其他語(yǔ)言(例如C/C++)編寫(xiě)的應(yīng)用程序和庫(kù)[5]。同時(shí)本地應(yīng)用程序和庫(kù)也可以通過(guò)JNI來(lái)操作JVM內(nèi)存中的Java對(duì)象,實(shí)現(xiàn)與Java應(yīng)用程序共享這些對(duì)象[5]。
本地代碼在平臺(tái)屬性使用和高性能計(jì)算方面具備明顯的優(yōu)勢(shì),而JNI技術(shù)則將這種優(yōu)勢(shì)集成到了Java之中,極大擴(kuò)展了Java的功能范疇,特別是在處理Java本身不具備的平臺(tái)屬性和提升Java應(yīng)用程序性能方面??梢哉f(shuō)JNI技術(shù)充當(dāng)了跨平臺(tái)的Java代碼和平臺(tái)相關(guān)的本地代碼的通信“橋梁”,實(shí)現(xiàn)了兩者之間的互操作性。
1.2 串口通信
串口是計(jì)算機(jī)與外圍設(shè)備之間的數(shù)據(jù)傳輸通道[6],數(shù)據(jù)通過(guò)串口以一位一位按順序的方式進(jìn)行傳輸,其優(yōu)點(diǎn)是只需一對(duì)傳輸線,大大降低了數(shù)據(jù)傳輸?shù)某杀荆貏e適合遠(yuǎn)距離通信[7]。當(dāng)前主流的串口標(biāo)準(zhǔn)包含了RS-232、RS-485、RS-422等,這些標(biāo)準(zhǔn)只對(duì)接口的電氣特性做出規(guī)定,而不涉及接插件、電纜或協(xié)議,在此基礎(chǔ)上用戶(hù)可以建立自己的高層通信協(xié)議。
在當(dāng)前主流操作系統(tǒng)平臺(tái)上,串口設(shè)備是作為一種設(shè)備資源存在的[6],不同的操作系統(tǒng)對(duì)設(shè)備資源的管理方式各不相同,對(duì)串口設(shè)備的編程訪問(wèn)也需要依平臺(tái)而定。在Windows平臺(tái)下,實(shí)現(xiàn)串口通信的方式主要包括平臺(tái)API函數(shù)、MScomm通信組件以及VS2008專(zhuān)門(mén)提供的串口通信類(lèi)SerialPort[6]。
2 系統(tǒng)設(shè)計(jì)
2.1 整體架構(gòu)
為了實(shí)現(xiàn)系統(tǒng)模塊化設(shè)計(jì),保證各個(gè)模塊之間邏輯相對(duì)獨(dú)立以方便后續(xù)改進(jìn),該系統(tǒng)采用了三層體系架構(gòu),即Java接口調(diào)用層、本地接口調(diào)用層和本地實(shí)現(xiàn)層。各層的關(guān)系如下:
1) Java接口調(diào)用層:定義串口通信的Java接口,該接口一方面提供給應(yīng)用層代碼調(diào)用,實(shí)現(xiàn)Java程序與串口設(shè)備通信;另一方面該接口通過(guò)JNI技術(shù)產(chǎn)生本地調(diào)用接口,實(shí)現(xiàn)Java調(diào)用接口與本地調(diào)用接口一一對(duì)應(yīng)關(guān)系。
2) 本地接口調(diào)用層:由JNI技術(shù)產(chǎn)生與Java接口對(duì)應(yīng)的本地代碼(C/C++)接口,該接口規(guī)定了本地實(shí)現(xiàn)串口通信功能時(shí)所必須遵照的調(diào)用協(xié)議[2]。
3) 本地實(shí)現(xiàn)層:利用操作系統(tǒng)API實(shí)現(xiàn)串口號(hào)獲取、打開(kāi)串口、配置參數(shù)、讀寫(xiě)串口、關(guān)閉串口等通信功能,并對(duì)本地調(diào)用接口進(jìn)行封裝。
根據(jù)各層的關(guān)系,該系統(tǒng)的整體架構(gòu)設(shè)計(jì)如下:
2.2 Java接口調(diào)用層
Java接口調(diào)用層實(shí)現(xiàn)對(duì)串口對(duì)象的抽象,將實(shí)際物理串口設(shè)備抽象成邏輯串口對(duì)象,并提供給應(yīng)用層代碼調(diào)用;同時(shí)為了和本地代碼通信,該層定義了調(diào)用接口,描述了本地接口應(yīng)該遵照的調(diào)用協(xié)議形式。由于Java語(yǔ)言本身是面向?qū)ο蟮?,而本地方法(例如C語(yǔ)言)是面向過(guò)程的,所以調(diào)用接口是按照基本數(shù)據(jù)傳遞方式進(jìn)行設(shè)計(jì)。Java接口調(diào)用層設(shè)計(jì)如圖2所示。
1) SerialPort接口描述了串口對(duì)象的基本功能,屏蔽了底層串口通信細(xì)節(jié),應(yīng)用層可以調(diào)用該接口實(shí)現(xiàn)與串口通信通信;在該接口中定義了串口的打開(kāi)和關(guān)閉、配置串口以及讀寫(xiě)串口功能;
2) SerialPortImpl類(lèi)為SerialPort接口的具體實(shí)現(xiàn),該類(lèi)中包含了串口對(duì)象的波特率、數(shù)據(jù)位、停止位和校驗(yàn)位屬性,同時(shí)由于串口設(shè)備只能采用獨(dú)占式打開(kāi),因此該類(lèi)采用了單例設(shè)計(jì)模式,保證邏輯串口對(duì)象和物理串口設(shè)備的統(tǒng)一;
3) PortHelper類(lèi)為調(diào)用接口,可以通過(guò)JNI技術(shù)生成本地調(diào)用接口,該接口中的方法均為靜態(tài)native方法,以便和本地代碼(例如C語(yǔ)言)匹配。
2.3 本地接口調(diào)用層
本地接口調(diào)用層由JNI技術(shù)自動(dòng)生成,該層的函數(shù)與上述PortHelper類(lèi)中的方法形成一一對(duì)應(yīng)關(guān)系,從而實(shí)現(xiàn)Java虛擬機(jī)通過(guò)裝載動(dòng)態(tài)連接庫(kù)[4],經(jīng)JNI間接完成本地代碼調(diào)用的過(guò)程。由于該層接口是對(duì)Java調(diào)用接口的本地描述,因此當(dāng)Java調(diào)用接口發(fā)生改變時(shí),該層需重新生成以保證調(diào)用邏輯正確對(duì)應(yīng)。
本地調(diào)用層在實(shí)現(xiàn)與Java接口調(diào)用層通信的同時(shí),還需要實(shí)現(xiàn)同本地實(shí)現(xiàn)代碼之間的調(diào)用。為了保持模塊的相對(duì)獨(dú)立性,本地調(diào)用層不包含具體的串口通信代碼,其主要功能是實(shí)現(xiàn)基本的數(shù)據(jù)提取和安全檢查,并將處理后的數(shù)據(jù)傳遞給本地實(shí)現(xiàn)層函數(shù)。本地接口調(diào)用層設(shè)計(jì)如下:
1) 本地接口依賴(lài)于JNI技術(shù)自動(dòng)生成,是一組本地代碼的函數(shù)聲明,不包含具體的邏輯功能,與PortHelper類(lèi)中的方法對(duì)應(yīng)。由于采用了基本數(shù)據(jù)類(lèi)型傳遞數(shù)據(jù),因此生成的本地接口中的數(shù)據(jù)類(lèi)型可以和C語(yǔ)言中的基本數(shù)據(jù)類(lèi)型進(jìn)行關(guān)聯(lián),并可以通過(guò)相應(yīng)的方法完成數(shù)據(jù)轉(zhuǎn)換;
2) Java中的數(shù)據(jù)類(lèi)型與C語(yǔ)言中的數(shù)據(jù)類(lèi)型存在差異,無(wú)法在本地代碼中直接使用Java數(shù)據(jù)類(lèi)型,因此需要對(duì)JNI傳遞而來(lái)的數(shù)據(jù)做適當(dāng)?shù)奶崛∨c格式轉(zhuǎn)化之后才能在本地代碼中使用;
3) 在JVM內(nèi)存模型中,JVM能夠?qū)崿F(xiàn)對(duì)堆內(nèi)存進(jìn)行自動(dòng)垃圾回收,但在本地內(nèi)存上則無(wú)法實(shí)現(xiàn),因此在處理數(shù)據(jù)的過(guò)程中,還需對(duì)數(shù)據(jù)的內(nèi)存進(jìn)行管理,以保證不會(huì)發(fā)生JVM崩潰問(wèn)題。
2.4 本地實(shí)現(xiàn)層
本地實(shí)現(xiàn)層負(fù)責(zé)處理具體的串口邏輯功能。由于串口設(shè)備的驅(qū)動(dòng)方式和通信方式與具體的操作系統(tǒng)平臺(tái)相關(guān),因此本地實(shí)現(xiàn)層需要借助具體的操作系統(tǒng)平臺(tái)API來(lái)實(shí)現(xiàn)對(duì)串口設(shè)備的操作。串口設(shè)備作為一種獨(dú)占式訪問(wèn)設(shè)備,要求用戶(hù)程序有且僅有一個(gè)實(shí)例來(lái)獲取串口句柄,因此在本地實(shí)現(xiàn)層里提供了一個(gè)全局串口變量來(lái)抽象該設(shè)備,通過(guò)該變量的使用狀態(tài)來(lái)達(dá)到訪問(wèn)互斥的效果。本地實(shí)現(xiàn)層設(shè)計(jì)如下:
在現(xiàn)代操作系統(tǒng)中,物理設(shè)備被抽象成各種文件,因此對(duì)設(shè)備的訪問(wèn)也相應(yīng)的抽象成了文件訪問(wèn)。而在不同操作系統(tǒng)中,文件模型以及讀寫(xiě)文件的IO模型各不一樣,故本地實(shí)現(xiàn)層的代碼需視具體的操作系統(tǒng)平臺(tái)而定。全局串口變量存在于本地內(nèi)存中,為了保證其內(nèi)存運(yùn)行正確,不會(huì)出現(xiàn)溢出崩潰現(xiàn)象,故設(shè)計(jì)了一組內(nèi)存管理函數(shù)New_Port和Free_Port,用于對(duì)全局串口變量的內(nèi)存空間開(kāi)辟和釋放的管理。
3 代碼實(shí)現(xiàn)
3.1 Java接口實(shí)現(xiàn)
在Java接口調(diào)用層中,PortHelper類(lèi)為調(diào)用接口,也是通過(guò)JNI技術(shù)生成本地調(diào)用接口的模板,因此對(duì)該接口的實(shí)現(xiàn)成為了系統(tǒng)調(diào)用的關(guān)鍵。根據(jù)Java調(diào)用接口層設(shè)計(jì),接口中的方法均為靜態(tài)native方法,并且傳遞的參數(shù)均為基本數(shù)據(jù)類(lèi)型,不涉及復(fù)雜對(duì)象的傳遞。同時(shí)Java語(yǔ)言本身具備良好的跨平臺(tái)性,該接口的定義在任意平臺(tái)下均適用。調(diào)用接口的代碼如下:
各方法的功能及參數(shù)的說(shuō)明如表2所示。
3.2 本地接口生成
本地接口是基于JNI技術(shù)自動(dòng)生成的,在生成時(shí)需要使用不同平臺(tái)下的JNI[3]。本文選擇了Windows平臺(tái)作為實(shí)驗(yàn)平臺(tái),因此需使用該平臺(tái)下的JNI(%JAVA_HOME%\bin\javah.exe)。通過(guò)調(diào)用javah命令將上述PortHelper類(lèi)生成對(duì)應(yīng)的本地接口文件test_tools_PortHelper.h[2]。該頭文件中定義的本地接口與PortHelper類(lèi)中的靜態(tài)方法形成了一一對(duì)應(yīng)關(guān)系,具體代碼如下(省略條件編譯代碼):
其中jni.h頭文件(以及內(nèi)部包含的jni_md.h)由本地JDK提供,定義了一系列Java與本地代碼通信的庫(kù)函數(shù)和數(shù)據(jù)結(jié)構(gòu),通過(guò)引入該頭文件可以實(shí)現(xiàn)Java數(shù)據(jù)類(lèi)型與本地?cái)?shù)據(jù)類(lèi)型之間相互轉(zhuǎn)化,以及JVM的本地內(nèi)存管理。
3.3 本地接口調(diào)用實(shí)現(xiàn)
生成的本地接口不包含任何邏輯,為了實(shí)現(xiàn)本地接口對(duì)本地實(shí)現(xiàn)層的代碼調(diào)用,還需對(duì)本地接口的調(diào)用過(guò)程進(jìn)行實(shí)現(xiàn)。為了保證調(diào)用過(guò)程的相對(duì)獨(dú)立,降低各個(gè)接口之間的耦合度,本地接口與本地實(shí)現(xiàn)層的調(diào)用關(guān)系也為一一對(duì)應(yīng)的,具體對(duì)應(yīng)關(guān)系如下:
在調(diào)用過(guò)程中,數(shù)據(jù)的傳遞是雙向的,一方面Java代碼中的數(shù)據(jù)通過(guò)JNI傳遞到本地代碼中,這需要保證在本地代碼中能夠正確的提取出所傳遞的數(shù)據(jù);另一方面本地代碼產(chǎn)生的結(jié)果也需要通過(guò)JNI傳遞到Java代碼中,這需要保證JVM本地內(nèi)存和本地內(nèi)存正確,避免潛在的內(nèi)存泄漏引發(fā)程序崩潰。因此在本地調(diào)用時(shí),需要做以下兩點(diǎn)處理:
1) 利用虛擬機(jī)內(nèi)存指針對(duì)傳遞的數(shù)據(jù)進(jìn)行處理,將其轉(zhuǎn)化為本地代碼能夠識(shí)別的數(shù)據(jù)類(lèi)型;
2) 利用虛擬機(jī)內(nèi)存指針和本地釋放函數(shù)對(duì)數(shù)據(jù)進(jìn)行回收,避免內(nèi)存泄漏。
由于在調(diào)用過(guò)程中所有的模塊均需要遵循上述處理要求,因此現(xiàn)以讀取串口為例,展示從JNI中利用虛擬機(jī)內(nèi)存指針獲取數(shù)據(jù)、處理數(shù)據(jù)、調(diào)用本地代碼和回收數(shù)據(jù)整個(gè)過(guò)程(其他模塊處理過(guò)程類(lèi)似,在這里不再?gòu)?fù)述)。
根據(jù)Java調(diào)用接口定義,讀取串口方法中包含了緩沖區(qū)參數(shù)以及返回實(shí)際讀取的字節(jié)數(shù),由于Java數(shù)據(jù)類(lèi)型與本地代碼數(shù)據(jù)類(lèi)型存在差異,因此首先需要在本地代碼中利用虛擬機(jī)內(nèi)存指針獲取JNI層傳遞的數(shù)組對(duì)象;其次通過(guò)調(diào)用本地實(shí)現(xiàn)層將數(shù)據(jù)從串口設(shè)備中讀取出來(lái);最后對(duì)讀取結(jié)果進(jìn)行封裝并回收內(nèi)存。具體的調(diào)用代碼如下:
其中需要注意以下關(guān)鍵步驟:
1) 在步驟①中,由于Java數(shù)組對(duì)象與本地代碼數(shù)組存在著差異,無(wú)法直接對(duì)該數(shù)組對(duì)象直接操作,因此通過(guò)虛擬機(jī)內(nèi)存指針以拷貝的形式將JNI傳遞的數(shù)組對(duì)象獲取出來(lái)。需要注意的是該獲取過(guò)程采用了復(fù)制數(shù)組的形式,因此必須要有對(duì)應(yīng)的回收數(shù)據(jù)的過(guò)程,避免虛擬機(jī)內(nèi)存溢出;
2) 在步驟②中,通過(guò)直接調(diào)用本地實(shí)現(xiàn)層代碼,完成了從串口設(shè)備中讀取數(shù)據(jù)的功能,其讀取的字節(jié)保存在緩沖區(qū)中;
3) 在步驟③中,將緩沖區(qū)中的字節(jié)數(shù)據(jù)復(fù)制到數(shù)組對(duì)象中,但需要注意的是,因?yàn)樵摂?shù)組對(duì)象是復(fù)制產(chǎn)生的(步驟①),并未對(duì)實(shí)際數(shù)組對(duì)象發(fā)生任何修改,所以還需通過(guò)虛擬機(jī)內(nèi)存指針對(duì)原數(shù)組對(duì)象進(jìn)行覆蓋;
4) 步驟④實(shí)現(xiàn)了虛擬機(jī)內(nèi)存指針對(duì)原數(shù)組對(duì)象進(jìn)行覆蓋的功能,從而將本地?cái)?shù)據(jù)通過(guò)JNI傳遞到了Java代碼中;
5) 步驟⑤與步驟①對(duì)應(yīng),將復(fù)制的數(shù)組對(duì)象進(jìn)行釋放,回收內(nèi)存空間,防止虛擬機(jī)內(nèi)存溢出。
3.4 本地實(shí)現(xiàn)層
3.4.1 基本數(shù)據(jù)結(jié)構(gòu)
本地實(shí)現(xiàn)層負(fù)責(zé)利用平臺(tái)API完成實(shí)際的串口通信。在Windows平臺(tái)下串口設(shè)備被抽象成了文件,因此對(duì)串口設(shè)備的訪問(wèn)轉(zhuǎn)化為對(duì)文件的訪問(wèn),而文件的訪問(wèn)均需要通過(guò)對(duì)應(yīng)的句柄才能完成,故為了方便數(shù)據(jù)管理,進(jìn)一步提高抽象層次,基本數(shù)據(jù)結(jié)構(gòu)中封裝了串口句柄和相應(yīng)參數(shù),并定義了一組常量和內(nèi)存管理函數(shù)?;緮?shù)據(jù)結(jié)構(gòu)核心代碼如下:
其中Port結(jié)構(gòu)體封裝了與串口有關(guān)的數(shù)據(jù),包括串口句柄、波特率、數(shù)據(jù)位、停止位和校驗(yàn)位。本地實(shí)現(xiàn)層定義了全局變量g_port,能夠保存串口對(duì)象以便在后續(xù)訪問(wèn)中實(shí)現(xiàn)對(duì)串口設(shè)備的讀寫(xiě)功能。New_Port函數(shù)負(fù)責(zé)從內(nèi)存中創(chuàng)建全局變量并完成初始化,free_Port函數(shù)則負(fù)責(zé)關(guān)閉串口句柄并回收內(nèi)存,確保本地內(nèi)存不會(huì)泄漏。
3.4.2 串口號(hào)獲取
在Windows平臺(tái)下,為了獲取主機(jī)安裝的所有串口設(shè)備,需要借助Windows API對(duì)注冊(cè)表進(jìn)行訪問(wèn)。在獲取所有串口過(guò)程中沒(méi)有打開(kāi)串口的操作,故不需要使用全局變量g_port。串口號(hào)獲取核心代碼如下:
其中核心步驟主要有以下四步:
1) 步驟①利用了Windows平臺(tái)提供的操作注冊(cè)表函數(shù)實(shí)現(xiàn)打開(kāi)注冊(cè)表,將注冊(cè)表句柄賦值給hKey變量,并從參數(shù)中返回;
2) 在步驟②中,循環(huán)的從注冊(cè)表句柄里不斷的獲取串口名稱(chēng),當(dāng)沒(méi)有串口名時(shí)才從循環(huán)中退出。獲取到的串口名被封裝進(jìn)commName變量里;
3) 步驟③則將本輪循環(huán)得到的commName變量(即串口名)賦值給結(jié)果變量;
4) 步驟④則與步驟①相對(duì),將打開(kāi)的注冊(cè)表關(guān)閉并釋放句柄,保證操作注冊(cè)表的邏輯正確。
3.4.3 打開(kāi)串口
在Windows平臺(tái)下串口設(shè)備被抽象成了文件,可以借助文件操作函數(shù)CreateFile實(shí)現(xiàn)串口打開(kāi)。但需要注意的是,在Windows平臺(tái)上文件的訪問(wèn)有同步訪問(wèn)和重疊訪問(wèn),而串口設(shè)備不允許采用重疊訪問(wèn)的方式打開(kāi),因此在串口設(shè)備打開(kāi)時(shí)需要選擇同步模式。打開(kāi)串口的核心代碼如下:
由于串口設(shè)備必須是以獨(dú)占的形式訪問(wèn),因此全局變量g_port必須是在為NULL的時(shí)候才能進(jìn)行串口打開(kāi)操作,而一旦打開(kāi)成功時(shí)全局變量g_port非空,這樣防止了重復(fù)打開(kāi)串口引發(fā)錯(cuò)誤,同時(shí)也提高了代碼訪問(wèn)效率,降低了對(duì)系統(tǒng)函數(shù)訪問(wèn)的頻率。
3.4.4 配置串口
在打開(kāi)串口成功之后,此時(shí)的串口運(yùn)行參數(shù)是系統(tǒng)默認(rèn)值,需進(jìn)一步對(duì)串口的通信參數(shù)進(jìn)行配置。串口的通信參數(shù)主要包括了串口的波特率、數(shù)據(jù)位、停止位和校驗(yàn)位。在一般情況下,配置串口過(guò)程是通過(guò)從系統(tǒng)中獲取默認(rèn)的串口參數(shù)信息,修改該參數(shù)信息并重新設(shè)置來(lái)實(shí)現(xiàn)的。配置串口的核心代碼如下:
步驟①實(shí)現(xiàn)了獲取系統(tǒng)默認(rèn)的參數(shù)信息,而通過(guò)步驟②對(duì)默認(rèn)的參數(shù)信息進(jìn)行修改,使用實(shí)際參數(shù)值進(jìn)行覆蓋,在步驟③中則用修改后的參數(shù)信息對(duì)系統(tǒng)信息重新設(shè)置,并刷新串口緩沖區(qū)。
3.4.5 串口讀寫(xiě)
串口讀寫(xiě)的過(guò)程與一般文件讀寫(xiě)過(guò)程類(lèi)似。在成功獲取了串口句柄和配置串口的基礎(chǔ)上,通過(guò)平臺(tái)提供的文件操作函數(shù)ReadFile和WriteFile實(shí)現(xiàn)對(duì)串口的讀寫(xiě)。在讀取的過(guò)程中,從串口中獲取的字節(jié)信息將會(huì)保存到緩沖區(qū)中,并返回給本地調(diào)用層;而向串口寫(xiě)入數(shù)據(jù)時(shí)則直接將緩沖區(qū)中的字節(jié)數(shù)據(jù)寫(xiě)出。串口讀寫(xiě)核心代碼如下:
3.4.6 關(guān)閉串口
在操作結(jié)束時(shí)需要將串口設(shè)備關(guān)閉。關(guān)閉的過(guò)程一方面需要使用平臺(tái)提供的句柄操作函數(shù)CloseHandle函數(shù)來(lái)關(guān)閉串口句柄,另一方面需要回收全局變量g_port,并將其恢復(fù)成初始化狀態(tài),以便下一次訪問(wèn)串口設(shè)備。關(guān)閉串口核心代碼如下:
4 實(shí)驗(yàn)與結(jié)果
為了測(cè)試Java代碼能否通過(guò)JNI技術(shù)實(shí)現(xiàn)本地串口通信,本文設(shè)計(jì)了一組功能實(shí)驗(yàn),具體的運(yùn)行環(huán)境如下:
基于上述測(cè)試環(huán)境,實(shí)驗(yàn)得到的結(jié)果如圖5所示。
根據(jù)上圖實(shí)驗(yàn)結(jié)果,圖(a)實(shí)驗(yàn)軟件顯示了獲取平臺(tái)的全部串口信息,同時(shí)對(duì)串口COM1實(shí)現(xiàn)了打開(kāi)并配置。在配置成功的基礎(chǔ)上,實(shí)驗(yàn)軟件向串口COM1寫(xiě)入了測(cè)試數(shù)據(jù),并開(kāi)啟了循環(huán)讀取串口數(shù)據(jù)功能,結(jié)果顯示實(shí)驗(yàn)軟件能夠持續(xù)的從串口中讀出數(shù)據(jù)。圖(b)驗(yàn)證軟件打開(kāi)了串口COM2,由于串口COM1與串口COM2實(shí)現(xiàn)了關(guān)聯(lián),因此可以將串口COM1的數(shù)據(jù)從串口COM2中讀出,或向串口COM2寫(xiě)入數(shù)據(jù)由串口COM1讀出。驗(yàn)證軟件運(yùn)行結(jié)果顯示,其能夠正確接收到實(shí)驗(yàn)軟件發(fā)送的測(cè)試數(shù)據(jù),并且其周期性寫(xiě)入的數(shù)據(jù)在實(shí)驗(yàn)軟件中也實(shí)現(xiàn)了正確讀出。
5 結(jié)束語(yǔ)
實(shí)驗(yàn)結(jié)果表明,在Windows平臺(tái)下Java代碼能夠通過(guò)JNI技術(shù)實(shí)現(xiàn)對(duì)本地串口設(shè)備的訪問(wèn),運(yùn)行結(jié)果符合預(yù)期,顯示了該串口通信系統(tǒng)的設(shè)計(jì)方案是較為合理的,可以基于該設(shè)計(jì)方案在其他操作系統(tǒng)平臺(tái)下做進(jìn)一步驗(yàn)證。
然而該系統(tǒng)設(shè)計(jì)并未考慮性能因素,未將其與同類(lèi)型軟件在性能上做出橫向?qū)Ρ?,在后續(xù)的研究工作中還需深入對(duì)該設(shè)計(jì)方案的性能做出評(píng)估。
參考文獻(xiàn):
[1] 陳傳波, 杜娟, 張智杰. WIN32下基于RS232C協(xié)議的串口通信方法及應(yīng)用研究[J]. 南昌大學(xué)學(xué)報(bào):工科版, 2005(3):71-75.
[2] 李亞?wèn)|, 夏雨佳, 席裕庚. 基于JNI的跨平臺(tái)軟件設(shè)計(jì)[J]. 計(jì)算機(jī)工程, 2000(9):87-88,154.
[3] 任俊偉, 林東岱. JNI技術(shù)實(shí)現(xiàn)跨平臺(tái)開(kāi)發(fā)的研究[J]. 計(jì)算機(jī)應(yīng)用研究, 2005(7):180-184.
[4] 張華平, 玄光哲, 于貴平, 等. 基于JNI技術(shù)應(yīng)用框架的分析和實(shí)現(xiàn)[J].吉林大學(xué)學(xué)報(bào):信息科版, 2003(2):188-191.
[5] 沙嘉祥, 寧書(shū)年, 林捷. 利用JNI實(shí)現(xiàn)企業(yè)Java程序與傳統(tǒng)應(yīng)用程序的集成[J]. 計(jì)算機(jī)與現(xiàn)代化, 2004(2):20-25.
[6] 龔新文. 串口通信在VS2008中的實(shí)現(xiàn)與應(yīng)用[J]. 電腦與電信, 2011(3):47-48,51.
[7] 黃暉, 柴劍勇, 嚴(yán)興, 等. 串口通信技術(shù)[J]. 科技創(chuàng)新導(dǎo)報(bào), 2010(27):20-21.