目录1.前言2.windows 串口通信API3.JAVA-JNI java程序调用C++程序4.C/C++封装 动态运行库一、前言
https://www.cnblogs.com/kadcyh/p/14389710.html 写这个博客主要是因为自己想用java写一个小小的后端服务器,其中要处理由51单片机传送来的一些数据。单片机的数据由USB转串口发送至上位机,要处理这些数据,就会用到windows提供一些API( Application Programming Interface,应用程序接口 )。java在安装了相关的包后,比如JNative.jar,可以直接用该包提供的接口来进行调用windowsAPI。但是我才接触java。而且整个作业,我仅仅只要一部分来处理这个数据。安装一个java包,实在是大可不必,所以就用C/C++来写一个的终端,封装一下放在java程序里面。 二、windows串口通信API2.1 工具:VC++6.0 2.2 概述:windows操作系统的设备无关性将所有的外设都当做文件来操作,那么我们写串口通信就直接将串口当做文件来读写。那么我们打开串口后一定要记得关闭,这个很重要 2.3 串口通信程序概述 2.3.1 打开/关闭串口HANDLE WINAPI CreateFileW( LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, //共享模式 LPSECURITY_ATTRIBUTES lpSecurityAttributes, //安全属性 DWORD dwCreationDisposition, //指定文件的动作 DWORD dwFlagsAndAttributes, //文件属性---不指定就默认为同步IO HANDLE hTemplateFile //模板文件 ); BOOL WINAPI CloseHandle( HANDLE hObject ); 返回值:一个串口的句柄。 2.3.2 配置串口通信
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;
BOOL WINAPI GetCommState( HANDLE hFile, LPDCB lpDCB ); BOOL WINAPI SetCommState( HANDLE hFile, LPDCB lpDCB ); DCB数据结构我们初级学者需要关注:波特率、校验位、停止位、发送数据位数。
BOOL WINAPI SetupComm( HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue );
BOOL WINAPI ReadFile( HANDLE hFile, LPVOID lpBuffer, //存放数据的缓冲区 DWORD nNumberOfBytesToRead, //一次想要读入的数据长度 LPDWORD lpNumberOfBytesRead, //实际读入的数据长度 LPOVERLAPPED lpOverlapped ); BOOL WINAPI WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ); 2.3.3 串口缓冲区配置在程序运行的时候,应该保证设置的缓冲区是“干净”的。所以在读数据或者写数据之前,可以先清空一下缓冲区。 //清空缓冲区 BOOL WINAPI PurgeComm( HANDLE hFile, DWORD dwFlags ); //清除错误 BOOL WINAPI ClearCommError( HANDLE hFile, LPDWORD lpErrors, LPCOMSTAT lpStat ); 2.4完整的读串口代码#include <stdio.h>` #include <windows.h> int Comm(int nBaud,int parity,int bytesize,int stopbits,int accdatalength,char rBuf[]) { //缓冲区 DWORD rSize = 0; DWORD dwError; //清除错误 COMSTAT cs; COMMTIMEOUTS timeouts; //超时数据结构 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需要设置读数据超时 ////////////////////////////////////// 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); } //////////////////////////////////////// //设置缓冲区大小 /////////////////////////////////////// if(!SetupComm(hCom,500,500)) //设置读写缓冲区失败 { CloseHandle(hCom); } ////////////////////////////////////// //设置波特率等其它读文件配置 ///////////////////////////////////// if(GetCommState(hCom,&dcb)==0) //获得DCB数据失败 { CloseHandle(hCom); } //dcb.DCBlength = sizeof(DCB); dcb.BaudRate = nBaud; //波特率为4800 dcb.Parity = parity; //校验方式为无校验 dcb.ByteSize = bytesize; //数据位为8位 dcb.StopBits = stopbits; //停止位为1位 if (!SetCommState(hCom,&dcb)) //设置串口通信配置项失败 { CloseHandle(hCom); } ////////////////////////////////////// //清除缓冲区 ///////////////////////////////////// PurgeComm(hCom,PURGE_RXCLEAR|PURGE_TXCLEAR); /////////////////////////////////////// //清除错误 /////////////////////////////////////// if(!(ClearCommError(hCom,&dwError,&cs))) { CloseHandle(hCom); } //////////////////////////////////////////// //开始读取缓冲口的数据 /////////////////////////////////////////// //读取串口数据 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("请输入相关参数:"); 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-JNI java程序调用C++程序 参考博客 工具:eclipse 3.1 首先在新建一个类,类的名字随意。最好加上main()函数方便我们进行模块调试。在这个类里面。在这个类里面我们要自己定义将会用C/C++实现的函数。并且必须用到java提供的 System.loadLibrary()函数。如下: public class Com { static { System.loadLibrary("CommDLL"); //静态语句块,保证在创建该类的时候,该方法必须且只会调用一次。参数就是编写的动态库名称 } public native int Comm(int nBaud,int parity,int bytesize,int stopbits,int accdatalength,String ComName,char[] rBuf);//将要用C/C++实现的函数 public static void main(String[] args) { int term = new Com().Comm(4800, 0, 8, 0, 17,"COM3", Buffer); } } ` 3.2 找到刚编写的java源程序文件所在的位置。然后如下操作: 进入控制台: 输入 javac -d ./ Com.java 。即是根据将java文件经过编译生成二进制文件(class文件) 输入 javac -h ./ Com.java 生成相关的头文件。 注:如果报错:'javac不是内部或外部命令,也不是可运行的程序或批处理文件。' 请参考相关博客添加相关的环境变量。参考博客。 3.3在生成了相关的库文件的编写后。我们要用这个库文件有两种办法。
3.4运行代码: public class Com { static { System.loadLibrary("CommDLL"); //静态语句块,保证在创建该类的时候,该方法必须且只会调用一次。 } 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(); } } } } 运行结果 四、C/C++封装动态运行库4.1:关于工具一开始用的工具是VC++6.0,尽管工具有点老,但是它足够小。动态库编好了,在eclipse上面运行的时候,出现了错误,大致意思就是:“32位的动态库,没有办法在64位的设备上面”。 如果,我还想继续使用这个动态库,已知的解决办法就是:下载一个32位的java JDK包。但是,相比配置一个可以运行32位的环境,我更加倾向于编译生成一个64位的动态运行库。然后,我就把代码粘到了VS上面。一般写代码我不太喜欢在VS上面,虽然它的功能很强太,但是我的电脑负载它真的很费力。 4.2 用JNI编写本地函具体步骤* 1:建立DLL程序:文件--->新建--->项目--->windows桌面--->动态链接库 4.2 代码细节 * 1:新建一个头文件,把在第三节里面生成的头文件内容复制粘贴过来。 #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; //超时数据结构 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:为指针或是数组,c:是赋给buffer的值,count:是buffer的长度.////// memset(buffer, 0, sizeof(buffer)); //作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 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需要设置读数据超时 ////////////////////////////////////// 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); } //////////////////////////////////////// //设置缓冲区大小 /////////////////////////////////////// if (!SetupComm(hCom, 500, 500)) //设置读写缓冲区失败 { CloseHandle(hCom); } ////////////////////////////////////// //设置波特率等其它读文件配置 ///////////////////////////////////// if (GetCommState(hCom, &dcb) == 0) //获得DCB数据失败 { CloseHandle(hCom); } //dcb.DCBlength = sizeof(DCB); dcb.BaudRate = nBaud; //波特率为4800 dcb.Parity = parity; //校验方式为无校验 dcb.ByteSize = bytesize; //数据位为8位 dcb.StopBits = stopbits; //停止位为1位 if (!SetCommState(hCom, &dcb)) //设置串口通信配置项失败 { CloseHandle(hCom); } ////////////////////////////////////// //清除缓冲区 ///////////////////////////////////// PurgeComm(hCom, PURGE_RXCLEAR | PURGE_TXCLEAR); /////////////////////////////////////// //清除错误 /////////////////////////////////////// if (!(ClearCommError(hCom, &dwError, &cs))) { CloseHandle(hCom); } //////////////////////////////////////////// //开始读取缓冲口的数据 /////////////////////////////////////////// //读取串口数据 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("请输入相关参数:"); 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; } } }
#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; //超时数据结构 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需要设置读数据超时 ////////////////////////////////////// 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); } //////////////////////////////////////// //设置缓冲区大小 /////////////////////////////////////// if (!SetupComm(hCom, 500, 500)) //设置读写缓冲区失败 { CloseHandle(hCom); } ////////////////////////////////////// //设置波特率等其它读文件配置 ///////////////////////////////////// if (GetCommState(hCom, &dcb) == 0) //获得DCB数据失败 { CloseHandle(hCom); } //dcb.DCBlength = sizeof(DCB); dcb.BaudRate = nBaud; //波特率为4800 dcb.Parity = parity; //校验方式为无校验 dcb.ByteSize = bytesize; //数据位为8位 dcb.StopBits = stopbits; //停止位为1位 if (!SetCommState(hCom, &dcb)) //设置串口通信配置项失败 { CloseHandle(hCom); } ////////////////////////////////////// //清除缓冲区 ///////////////////////////////////// PurgeComm(hCom, PURGE_RXCLEAR | PURGE_TXCLEAR); /////////////////////////////////////// //清除错误 /////////////////////////////////////// if (!(ClearCommError(hCom, &dwError, &cs))) { CloseHandle(hCom); } //////////////////////////////////////////// //开始读取缓冲口的数据 /////////////////////////////////////////// //读取串口数据 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)里面。 4.3 配置在生成DLL文件之前,我们还必须做一些重要的配置。
将框起来的三个头文件复制到DLL源程序的文件里面。
右键项目 ---> 属性--->VC++目录--->包含目录(把DLL程序所在的文件路径添加进去) |
|
来自: 山峰云绕 > 《c加加c井号面向对象》