??
https://www.cnblogs.com/kadcyh/p/14389710.html
寫這個博客主要是因為自己想用java寫一個小小的后端服務器,其中要處理由51單片機傳送來的一些數(shù)據(jù)。單片機的數(shù)據(jù)由USB轉串口發(fā)送至上位機,要處理這些數(shù)據(jù),就會用到windows提供一些API( Application Programming Interface,應用程序接口 )。java在安裝了相關的包后,比如JNative.jar,可以直接用該包提供的接口來進行調用windowsAPI。但是我才接觸java。而且整個作業(yè),我僅僅只要一部分來處理這個數(shù)據(jù)。安裝一個java包,實在是大可不必,所以就用C/C++來寫一個的終端,封裝一下放在java程序里面。
??2.1 ? 工具:VC++6.0
??2.2 ? 概述:windows操作系統(tǒng)的設備無關性將所有的外設都當做文件來操作,那么我們寫串口通信就直接將串口當做文件來讀寫。那么我們打開串口后一定要記得關閉,這個很重要
??2.3 ? 串口通信程序概述
HANDLE WINAPI CreateFileW( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, //共享模式 LPSECURITY_ATTRIBUTES lpSecurityAttributes, //安全屬性 DWORD dwCreationDisposition, //指定文件的動作 DWORD dwFlagsAndAttributes, //文件屬性---不指定就默認為同步IO HANDLE hTemplateFile //模板文件 ); BOOL WINAPI CloseHandle( HANDLE hObject );
返回值:一個串口的句柄。
參數(shù)解釋:
lpFileName——串口名字。?當為COM1~COM9的時候可以直接寫入但是大于10會有另外的寫法。以COM10為例:\.\COM10。
lpFileName——打開方式。?簡單來說這個就是打開文件是讀還是寫。GENERIC_READ(讀)|GENERIC_WRITE(寫)。還有其它的兩個值,詳細請在編輯器中直接按下F12看看他們的定義。
如果我們要定義一個同步IO的話,我們的打開方式必須要有讀動作。
dwShareMode指定該端口的共享屬性。?對于不能共享的串口,它必須設置為0。這就是文件與通信設備之間的主要差異之一。如果在當前的應用程序調用CreateFile()時,另一個應用程序已經(jīng)打開了串口,該函數(shù)就會返回錯誤代碼,原因是兩個應用程序不能共享一個端口。然而,同一個應用程序的多個線程可以共享由CreateFile()返回的端口句柄,并且根據(jù)安全性屬性設置,該句柄可以被打開端口的應用程序的子程序所繼承。
dwCreationDisposition指定文件的動作。?指定如果CreateFile()正在被已有的文件調用時應采取的動作。因為串口總是存在,fdwCreate必須設置成OPEN_EXISTING。該標志告訴Windows不用企圖創(chuàng)建新端口,而是打開已經(jīng)存在的端口。
調用該函數(shù)后,如果沒有穿件成功將會返回INVALID_HANDLE_VALUE。
(1)?當我們設置同步IO通信的時候,需要設置一下通信超時。一般情況下,我們用GetCommTimeouts來獲得COMMTIMEOUTS結構體,再利用SetCommTimeouts來寫入。
BOOL WINAPI GetCommTimeouts( HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts ); BOOL WINAPI SetCommTimeouts( HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts );
COMMTIMEOUTS:
typedef struct _COMMTIMEOUTS { DWORD ReadIntervalTimeout; /* 設置兩個字符之前的最大讀取時間 */ DWORD ReadTotalTimeoutMultiplier; /* 設置每個字符的讀取時間 */ DWORD ReadTotalTimeoutConstant; /* 設置所有字符讀取的最大時間 */ DWORD WriteTotalTimeoutMultiplier; /* 設置每個字符的寫入時間 */ DWORD WriteTotalTimeoutConstant; /* 設置所有字符的寫入時間 */ } COMMTIMEOUTS,*LPCOMMTIMEOUTS;
(2)?設置波特率等相關參數(shù)
仍然先用GetCommState得到DCB結構,修改其中的某些參數(shù)后再用SetCommState寫入DCB結構。
BOOL WINAPI GetCommState( HANDLE hFile, LPDCB lpDCB ); BOOL WINAPI SetCommState( HANDLE hFile, LPDCB lpDCB );
DCB數(shù)據(jù)結構我們初級學者需要關注:波特率、校驗位、停止位、發(fā)送數(shù)據(jù)位數(shù)。
(3)?設置緩沖區(qū)大小,根據(jù)程序要接收/發(fā)送的數(shù)據(jù)大小來決定。
BOOL WINAPI SetupComm( HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue );
(4)?讀取/寫入數(shù)據(jù)
BOOL WINAPI ReadFile( HANDLE hFile, LPVOID lpBuffer, //存放數(shù)據(jù)的緩沖區(qū) DWORD nNumberOfBytesToRead, //一次想要讀入的數(shù)據(jù)長度 LPDWORD lpNumberOfBytesRead, //實際讀入的數(shù)據(jù)長度 LPOVERLAPPED lpOverlapped ); BOOL WINAPI WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped );
在程序運行的時候,應該保證設置的緩沖區(qū)是“干凈”的。所以在讀數(shù)據(jù)或者寫數(shù)據(jù)之前,可以先清空一下緩沖區(qū)。
//清空緩沖區(qū) BOOL WINAPI PurgeComm( HANDLE hFile, DWORD dwFlags ); //清除錯誤 BOOL WINAPI ClearCommError( HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat );
#include <stdio.h>` #include <windows.h> int Comm(int nBaud,int parity,int bytesize,int stopbits,int accdatalength,char rBuf[]) { //緩沖區(qū) DWORD rSize = 0; DWORD dwError; //清除錯誤 COMSTAT cs; COMMTIMEOUTS timeouts; //超時數(shù)據(jù)結構 DCB dcb; //串口通信配置文件---用LPDCB類型會報錯 HANDLE hCom; //串口的句柄(實例)| the instance of com hCom = CreateFile("COM3", //串口的名字 GENERIC_READ | GENERIC_WRITE, //串口打開方式 0, //共享方式 NULL, //安全屬性 OPEN_EXISTING, //指定文件的動作 0, //文件屬性---不指定就默認為同步IO NULL //指向模板文件的句柄 ); if(hCom == INVALID_HANDLE_VALUE) { return -1; } /////////////////////////////////////// //同步IO需要設置讀數(shù)據(jù)超時 ////////////////////////////////////// if(!(GetCommTimeouts(hCom,&timeouts))) //獲的COMMTIMEOUTS結構失?。? { CloseHandle(hCom); } timeouts.ReadIntervalTimeout = 1000; //讀取每個字符之間的超時 timeouts.ReadTotalTimeoutMultiplier = 500; //讀取一個字符的超時 timeouts.ReadTotalTimeoutConstant=5000; //固定總超時 timeouts.WriteTotalTimeoutConstant = 0; //寫入字符之間超時 timeouts.WriteTotalTimeoutMultiplier = 0; //寫入字符總超時 if(!(SetCommTimeouts(hCom,&timeouts))) //設置COMMTIMEOUTS結構失敗 { CloseHandle(hCom); } //////////////////////////////////////// //設置緩沖區(qū)大小 /////////////////////////////////////// if(!SetupComm(hCom,500,500)) //設置讀寫緩沖區(qū)失敗 { CloseHandle(hCom); } ////////////////////////////////////// //設置波特率等其它讀文件配置 ///////////////////////////////////// if(GetCommState(hCom,&dcb)==0) //獲得DCB數(shù)據(jù)失敗 { CloseHandle(hCom); } //dcb.DCBlength = sizeof(DCB); dcb.BaudRate = nBaud; //波特率為4800 dcb.Parity = parity; //校驗方式為無校驗 dcb.ByteSize = bytesize; //數(shù)據(jù)位為8位 dcb.StopBits = stopbits; //停止位為1位 if (!SetCommState(hCom,&dcb)) //設置串口通信配置項失敗 { CloseHandle(hCom); } ////////////////////////////////////// //清除緩沖區(qū) ///////////////////////////////////// PurgeComm(hCom,PURGE_RXCLEAR|PURGE_TXCLEAR); /////////////////////////////////////// //清除錯誤 /////////////////////////////////////// if(!(ClearCommError(hCom,&dwError,&cs))) { CloseHandle(hCom); } //////////////////////////////////////////// //開始讀取緩沖口的數(shù)據(jù) /////////////////////////////////////////// //讀取串口數(shù)據(jù) if(INVALID_HANDLE_VALUE != hCom) { WriteFile ReadFile(hCom,rBuf,accdatalength,&rSize,NULL); printf("%d \n",rSize); if(rSize) { CloseHandle(hCom); return 1; } else { CloseHandle(hCom); return 0; } } else { CloseHandle(hCom); return 2; } } void main() { int i; char buf[18] = {0}; int a,b,c,d,e; printf("請輸入相關參數(shù):"); scanf("%d%d%d%d%d",&a,&b,&c,&d,&e); while(1) { if(Comm(a,b,c,d,e,buf)==1) { for(i=0;i<17;i++) { printf("%c ",buf[i]); } printf("\n"); } else { printf("%d \n",Comm(a,b,c,d,e,buf)); break; } } }
??參考博客
??在寫java程序調用C程序之前,寫過C#程序調用用C#封裝好的dll程序。然后,我以為,java程序調用C程序也可以直接把C封裝好的dll程序拿過來直接用就好。結果就是一直報錯。那么接下來就是正確的調用方式。
工具:eclipse
??首先在新建一個類,類的名字隨意。最好加上main()函數(shù)方便我們進行模塊調試。在這個類里面。在這個類里面我們要自己定義將會用C/C++實現(xiàn)的函數(shù)。并且必須用到java提供的 System.loadLibrary()函數(shù)。如下:
`
public class Com { static { System.loadLibrary("CommDLL"); //靜態(tài)語句塊,保證在創(chuàng)建該類的時候,該方法必須且只會調用一次。參數(shù)就是編寫的動態(tài)庫名稱 } public native int Comm(int nBaud,int parity,int bytesize,int stopbits,int accdatalength,String ComName,char[] rBuf);//將要用C/C++實現(xiàn)的函數(shù) public static void main(String[] args) { int term = new Com().Comm(4800, 0, 8, 0, 17,"COM3", Buffer); } }
`
注:native 關鍵字表示,這個函數(shù)為本地函數(shù)。盡管沒用java語言實現(xiàn),但它有自己的實體。
??找到剛編寫的java源程序文件所在的位置。然后如下操作:
??進入控制臺:
??輸入 javac -d ./ Com.java 。即是根據(jù)將java文件經(jīng)過編譯生成二進制文件(class文件)
??輸入 javac -h ./ Com.java 生成相關的頭文件。
??注:如果報錯:'javac不是內(nèi)部或外部命令,也不是可運行的程序或批處理文件。'?請參考相關博客添加相關的環(huán)境變量。參考博客。
??在生成了相關的庫文件的編寫后。我們要用這個庫文件有兩種辦法。
1:直接將生成的dll文件加入默認的環(huán)境變量里面。( 但是不是很推薦這個辦法,因為我們編寫的庫,僅僅只是在自己的程序上用一用。沒有很大的普適性。)
2:將生成的dll文件拷貝到自己源文件下面。同時配置一下自己的java程序。
?? 右鍵src--->properties--->Native Library--->Workspace
運行代碼:
public class Com { static { System.loadLibrary("CommDLL"); //靜態(tài)語句塊,保證在創(chuàng)建該類的時候,該方法必須且只會調用一次。 } public native int Comm(int nBaud,int parity,int bytesize,int stopbits,int accdatalength,String ComName,byte[] rBuf); public static void main(String[] args) { // TODO Auto-generated method stub byte[] Buffer = new byte[18]; while(true) { int term = new Com().Comm(4800, 0, 8, 0, 17,"COM3", Buffer); if(term==1) { for(int i=0;i<17;i++) { System.out.print(Buffer[i]+" "); } System.out.println(); } } } }
運行結果
??一開始用的工具是VC++6.0,盡管工具有點老,但是它足夠小。動態(tài)庫編好了,在eclipse上面運行的時候,出現(xiàn)了錯誤,大致意思就是:“32位的動態(tài)庫,沒有辦法在64位的設備上面”。 如果,我還想繼續(xù)使用這個動態(tài)庫,已知的解決辦法就是:下載一個32位的java ? JDK包。但是,相比配置一個可以運行32位的環(huán)境,我更加傾向于編譯生成一個64位的動態(tài)運行庫。然后,我就把代碼粘到了VS上面。一般寫代碼我不太喜歡在VS上面,雖然它的功能很強太,但是我的電腦負載它真的很費力。
?? * 1:建立DLL程序:文件--->新建--->項目--->windows桌面--->動態(tài)鏈接庫
?? * 1:新建一個頭文件,把在第三節(jié)里面生成的頭文件內(nèi)容復制粘貼過來。
VS單獨運行代碼:
#include <iostream> #include<stdlib.h> #include<windows.h> using std::string; int Comm(int nBaud, int parity, int bytesize, int stopbits, int accdatalength,string comName, char rBuf[]) { DWORD rSize = 0; DWORD dwError; //清除錯誤 COMSTAT cs; COMMTIMEOUTS timeouts; //超時數(shù)據(jù)結構 DCB dcb; //串口通信配置文件---用LPDCB類型會報錯 HANDLE hCom; //串口的句柄(實例)| the instance of com ////////////////////////////////////////////////////////////////////// //將string轉換為LPCWSTR類型 ////////////////////////////////////////////////////////////////////// int len = comName.length(); WCHAR buffer[256]; ///////memset原型---extern void *memset(void *buffer, int c, int count) buffer:為指針或是數(shù)組,c:是賦給buffer的值,count:是buffer的長度.////// memset(buffer, 0, sizeof(buffer)); //作用是在一段內(nèi)存塊中填充某個給定的值,它是對較大的結構體或數(shù)組進行清零操作的一種最快方法 MultiByteToWideChar(CP_ACP, 0, comName.c_str(), (len+1), buffer, sizeof(buffer) / sizeof(buffer[0])); printf("%d\n", buffer[0]); hCom = CreateFile(buffer, //串口的名字 GENERIC_READ | GENERIC_WRITE, //串口打開方式 0, //共享方式 NULL, //安全屬性 OPEN_EXISTING, //指定文件的動作 0, //文件屬性---不指定就默認為同步IO NULL //指向模板文件的句柄 ); if (hCom == INVALID_HANDLE_VALUE) { return -1; } /////////////////////////////////////// //同步IO需要設置讀數(shù)據(jù)超時 ////////////////////////////////////// if (!(GetCommTimeouts(hCom, &timeouts))) //獲的COMMTIMEOUTS結構失??! { CloseHandle(hCom); } timeouts.ReadIntervalTimeout = 1000; //讀取每個字符之間的超時 timeouts.ReadTotalTimeoutMultiplier = 500; //讀取一個字符的超時 timeouts.ReadTotalTimeoutConstant = 5000; //固定總超時 timeouts.WriteTotalTimeoutConstant = 0; //寫入字符之間超時 timeouts.WriteTotalTimeoutMultiplier = 0; //寫入字符總超時 if (!(SetCommTimeouts(hCom, &timeouts))) //設置COMMTIMEOUTS結構失敗 { CloseHandle(hCom); } //////////////////////////////////////// //設置緩沖區(qū)大小 /////////////////////////////////////// if (!SetupComm(hCom, 500, 500)) //設置讀寫緩沖區(qū)失敗 { CloseHandle(hCom); } ////////////////////////////////////// //設置波特率等其它讀文件配置 ///////////////////////////////////// if (GetCommState(hCom, &dcb) == 0) //獲得DCB數(shù)據(jù)失敗 { CloseHandle(hCom); } //dcb.DCBlength = sizeof(DCB); dcb.BaudRate = nBaud; //波特率為4800 dcb.Parity = parity; //校驗方式為無校驗 dcb.ByteSize = bytesize; //數(shù)據(jù)位為8位 dcb.StopBits = stopbits; //停止位為1位 if (!SetCommState(hCom, &dcb)) //設置串口通信配置項失敗 { CloseHandle(hCom); } ////////////////////////////////////// //清除緩沖區(qū) ///////////////////////////////////// PurgeComm(hCom, PURGE_RXCLEAR | PURGE_TXCLEAR); /////////////////////////////////////// //清除錯誤 /////////////////////////////////////// if (!(ClearCommError(hCom, &dwError, &cs))) { CloseHandle(hCom); } //////////////////////////////////////////// //開始讀取緩沖口的數(shù)據(jù) /////////////////////////////////////////// //讀取串口數(shù)據(jù) if (INVALID_HANDLE_VALUE != hCom) { ReadFile(hCom, rBuf, accdatalength, &rSize, NULL); printf("%d \n", rSize); if (rSize) { CloseHandle(hCom); return 1; } else { CloseHandle(hCom); return 0; } } else { CloseHandle(hCom); return 2; } } void main() { int i; char buf[18] = { 0 }; int a, b, c, d, e; char ComName[6]; printf("請輸入相關參數(shù):"); std::cin >> a >> b >> c >> d >> e>>ComName; while (1) { int t = Comm(a, b, c, d, e, ComName, buf); if (t == 1) { for (i = 0; i < 17; i++) { printf("%c ", buf[i]); } printf("\n"); } else { printf("%d\n", t); break; } } }
3:JNI中基本類型的處理
??完成這個作業(yè)之前我只是知道有JNI這個知識點,但是確實沒有系統(tǒng)的學習過。但是我就自己學的一點點東西可以總結一下。java里面的基礎類型可以放到C/C++里面使用但是像數(shù)組之類的數(shù)據(jù)類型,必須有Java認同的類型稍作處理,才可以在C/C++的環(huán)境里面運行。具體請參考:參考博客這片博客好像是sun官網(wǎng)的中翻,值得參考。
4:成功的DLL源代碼:
#include "pch.h" #include "comm.h" #include <iostream> #include <stdlib.h> #include <windows.h> using std::string; ///////////////////////////////////////////////////////////////////////// // JNIEXPORT jint JNICALL Java_Com_Comm(JNIEnv* env, jobject, jint nBaud, jint parity, jint bytesize, jint stopbits, jint accdatalength,jstring comName, jbyteArray rBuf) { DWORD rSize = 0; DWORD dwError; //清除錯誤 COMSTAT cs; COMMTIMEOUTS timeouts; //超時數(shù)據(jù)結構 DCB dcb; //串口通信配置文件---用LPDCB類型會報錯 HANDLE hCom; //串口的句柄(實例)| the instance of com BYTE rBuffer[255]; ////////////////////////////////////////////////////////////////////// //將jstring轉換為LPCWSTR類型 ////////////////////////////////////////////////////////////////////// WCHAR* buffer = (WCHAR*)env->GetStringChars(comName,NULL); hCom = CreateFile(buffer, //串口的名字 GENERIC_READ | GENERIC_WRITE, //串口打開方式 0, //共享方式 NULL, //安全屬性 OPEN_EXISTING, //指定文件的動作 0, //文件屬性---不指定就默認為同步IO NULL //指向模板文件的句柄 ); if (hCom == INVALID_HANDLE_VALUE) { return -1; } /////////////////////////////////////// //同步IO需要設置讀數(shù)據(jù)超時 ////////////////////////////////////// if (!(GetCommTimeouts(hCom, &timeouts))) //獲的COMMTIMEOUTS結構失??! { CloseHandle(hCom); } timeouts.ReadIntervalTimeout = 1000; //讀取每個字符之間的超時 timeouts.ReadTotalTimeoutMultiplier = 500; //讀取一個字符的超時 timeouts.ReadTotalTimeoutConstant = 5000; //固定總超時 timeouts.WriteTotalTimeoutConstant = 0; //寫入字符之間超時 timeouts.WriteTotalTimeoutMultiplier = 0; //寫入字符總超時 if (!(SetCommTimeouts(hCom, &timeouts))) //設置COMMTIMEOUTS結構失敗 { CloseHandle(hCom); } //////////////////////////////////////// //設置緩沖區(qū)大小 /////////////////////////////////////// if (!SetupComm(hCom, 500, 500)) //設置讀寫緩沖區(qū)失敗 { CloseHandle(hCom); } ////////////////////////////////////// //設置波特率等其它讀文件配置 ///////////////////////////////////// if (GetCommState(hCom, &dcb) == 0) //獲得DCB數(shù)據(jù)失敗 { CloseHandle(hCom); } //dcb.DCBlength = sizeof(DCB); dcb.BaudRate = nBaud; //波特率為4800 dcb.Parity = parity; //校驗方式為無校驗 dcb.ByteSize = bytesize; //數(shù)據(jù)位為8位 dcb.StopBits = stopbits; //停止位為1位 if (!SetCommState(hCom, &dcb)) //設置串口通信配置項失敗 { CloseHandle(hCom); } ////////////////////////////////////// //清除緩沖區(qū) ///////////////////////////////////// PurgeComm(hCom, PURGE_RXCLEAR | PURGE_TXCLEAR); /////////////////////////////////////// //清除錯誤 /////////////////////////////////////// if (!(ClearCommError(hCom, &dwError, &cs))) { CloseHandle(hCom); } //////////////////////////////////////////// //開始讀取緩沖口的數(shù)據(jù) /////////////////////////////////////////// //讀取串口數(shù)據(jù) if (INVALID_HANDLE_VALUE != hCom) { ReadFile(hCom, &rBuffer, accdatalength, &rSize, NULL); if (rSize) { jbyte* term; //將RBuffer中的值賦給rBuf term = env->GetByteArrayElements(rBuf,FALSE); for (int i = 0; i < accdatalength; i++) { term[i] = rBuffer[i]; } env->ReleaseByteArrayElements(rBuf,term, JNI_COMMIT); CloseHandle(hCom); return 1; } } else { CloseHandle(hCom); return 1; } env->ReleaseStringChars(comName,(jchar *)buffer); //釋放空間 CloseHandle(hCom); return 2; }
注:代碼寫在自己新建的和頭文件同名的自己建立的源文件(.cpp)里面。
在生成DLL文件之前,我們還必須做一些重要的配置。
1:把java jdk里面的一些文件拷貝到DLL文件里面去。
??將框起來的三個頭文件復制到DLL源程序的文件里面。
2:修改一下DLL項目的配置:
右鍵項目 ---> 屬性--->VC++目錄--->包含目錄(把DLL程序所在的文件路徑添加進去)