// Communication.h: interface for the CCommunication class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_COMMUNICATION_H__6CA00576_F088_11D1_89BB_8311A0F2733D__INCLUDED_) #define AFX_COMMUNICATION_H__6CA00576_F088_11D1_89BB_8311A0F2733D__INCLUDED_ #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 /* _MSC_VER 定义编译器的版本 MS VC++ 10.0 _MSC_VER = 1600 MS VC++ 9.0 _MSC_VER = 1500 MS VC++ 8.0 _MSC_VER = 1400 MS VC++ 7.1 _MSC_VER = 1310 MS VC++ 7.0 _MSC_VER = 1300 MS VC++ 6.0 _MSC_VER = 1200 MS VC++ 5.0 _MSC_VER = 1100 其中MS VC++ 10.0就是Visual C++ 2010,MS VC++ 9.0就是Visual C++ 2008,MS VC++ 8.0就是Visual C++ 2005。 在程序中加入_MSC_VER宏可以根据编译器版本让编译器选择性地编译一段程序。 例如一个版本编译器产生的lib文件可能不能被另一个版本的编译器调用, 那么在开发应用程序的时候,在该程序的lib调用库中放入多个版本编译器产生的lib文件。 在程序中加入_MSC_VER宏,编译器就能够在调用的时根据其版本自动选择可以链接的lib库版本,如下所示。 #if _MSC_VER >= 1400 // for vc8, or vc9 #ifdef _DEBUG #pragma comment( lib, "SomeLib-vc8-d.lib" ) #else if #pragma comment( lib, "SomeLib-vc8-r.lib" ) #endif #else if _MSC_VER >= 1310 // for vc71 #ifdef _DEBUG #pragma comment( lib, "SomeLib-vc71-d.lib" ) #else if #pragma comment( lib, "SomeLib-vc71-r.lib" ) #endif #else if _MSC_VER >=1200 // for vc6 #ifdef _DEBUG #pragma comment( lib, "SomeLib-vc6-d.lib" ) #else if #pragma comment( lib, "SomeLib-vc6-r.lib" ) #endif #endif */ #define WM_RECEIVEPACKET WM_USER+100 class CCommunication { public: // BOOL Connect; //发送数据函数 int SendData(char *data,int len); //设定消息接收者 void SetMessageReceiver(CWnd *pWnd); //初始化函数 BOOL Initialize(char *device,DWORD BaudRate,int Bits,int DDV,int StopBit); //构造函数 CCommunication(); //析构函数 virtual ~CCommunication(); //关闭通讯接口 BOOL CloseSerialPort(); //CWnd是MFC窗口类的基类,提供了微软基础类库中所有窗口类的基本功能 CWnd *msg_receiver; //OVERLAPPED是一个包含了用于异步输入输出的信息的结构体 OVERLAPPED write_os; //定义一个句柄 HANDLE hComPort; private: }; #endif // !defined(AFX_COMMUNICATION_H__6CA00576_F088_11D1_89BB_8311A0F2733D__INCLUDED_) /* 第一种声明: typedef struct _OVERLAPPED { DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED 参数说明: Internal: 预留给操作系统使用。 它指定一个独立于系统的状态,当GetOverlappedResult函数返回时没有设置扩展错误信息ERROR_IO_PENDING时有效。 InternalHigh: 预留给操作系统使用。它指定长度的数据转移,当GetOverlappedResult函数返回TRUE时有效。 Offset: 该文件的位置是从文件起始处的字节偏移量。调用进程设置这个成员之前调用ReadFile或WriteFile函数。 当读取或写入命名管道和通信设备时这个成员被忽略设为零。 OffsetHigh: 指定文件传送的字节偏移量的高位字。当读取或写入命名管道和通信设备时这个成员被忽略设为零。 hEvent: 在转移完成时处理一个事件设置为有信号状态。 调用进程集这个成员在调用ReadFile、 WriteFile、TransactNamedPipe、 ConnectNamedPipe函数之前。 */ /* 第二种声明: typedef struct _OVERLAPPED { ULONG_PTR Internal; //操作系统保留,指出一个和系统相关的状态 ULONG_PTR InternalHigh; //指出发送或接收的数据长度 union { struct { DWORD Offset; //文件传送的字节偏移量的低位字 DWORD OffsetHigh; //文件传送的字节偏移量的高位字 }; PVOID Pointer; //指针,指向文件传送位置 }; HANDLE hEvent; //指定一个I/O操作完成后触发的事件 } OVERLAPPED, *LPOVERLAPPED; */ /* I/O设备处理必然让主程序停下来干等I/O的完成,解决这个问题,可以使用OVERLAPPED。 OVERLAPPED I/O是WIN32的一项技术, 你可以要求操作系统为你传送数据,并且在传送完毕时通知你。 这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。 事实上,操作系统内部正是以线程来I/O完成OVERLAPPED I/O。 ,而不需付出什么痛苦的代价。也就是说,OVERLAPPED主要是设置异步I/O操作, 异步I/O操作是指应用程序可以在后台读或者写数据,而在前台做其他事情。 */
// Communication.cpp: implementation of the CCommunication class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "Communication.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// BOOL CState;//定义一个全局的变量 CState。 //构造函数 用于给全局变量CState置ON CCommunication::CCommunication() { CState=TRUE; } //析构函数 用于给全局变量CState置OFF CCommunication::~CCommunication() { CState=FALSE; } //具体的函数定义 。这个CommWatchProc 这个函数 一个线程函数, 这个函数是为了处理 串口相关事件的处理。 //在后面的代码里面,有个AfxCreateThrend的函数 是用于创建线程的,其中第一个参数 就要填CommWatchProc UINT CommWatchProc(LPVOID lpData) { //Add message WM_RECEIVEPACKET handler in class caller: //(wParam=pointer to data block lParam=length of data block //Add message WM_SENDSUCCESS handler in class caller CCommunication* com=(CCommunication*)lpData; DWORD dwEvtMask ; OVERLAPPED os; COMSTAT comstat; DWORD dwErrorFlag; DWORD dwLength; // AfxMessageBox("Receiving"); memset( &os, 0, sizeof( OVERLAPPED ) ) ; // create I/O event used for overlapped read os.hEvent = CreateEvent( NULL, // no security TRUE, // explicit reset req FALSE, // initial event reset NULL ) ; // no name if (os.hEvent == NULL) { MessageBox( NULL, _T("Failed to create event for thread!"), _T("Communication Error!"), MB_ICONEXCLAMATION | MB_OK ) ; return ( FALSE ) ; } if (!SetCommMask(com->hComPort, EV_RXCHAR )) return ( FALSE ) ; DWORD dwRead; char *buf; buf=new char[MAX_PATH]; memset(buf,0,MAX_PATH); while (CState) { dwEvtMask = 0 ; WaitCommEvent(com->hComPort, &dwEvtMask, 0 );//等待串口通讯的事件的发生。 Sleep(100); //memset(buf,0,MAX_PATH);检测返回的dwEvtMask,知道发生了什么样的串口事件 if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR) {//EV_RXCHAR代表缓冲区 有数据过来了 //read code ClearCommError(com->hComPort,&dwErrorFlag,&comstat);//清除错误 dwLength=comstat.cbInQue;//输入缓冲区有多少数据? if (dwLength)//如果大于0 { if (ReadFile(com->hComPort,buf,dwLength,&dwRead,&os))//调用ReadFile函数读取缓冲区数据 { com->msg_receiver->PostMessage(WM_RECEIVEPACKET,(WPARAM)buf,(LPARAM)dwRead); //想系统消息队列放入一个消息,通知程序的主线程,串口受到了数据。 } } } } delete buf; // get rid of event handle CloseHandle( os.hEvent ) ; return( TRUE ) ; } // end of CommWatchProc() //初始化端口 定义端口的带宽 位数 奇偶校验 停止位之类的 BOOL CCommunication::Initialize(char * device,DWORD BaudRate,int Bits,int DDV,int StopBit) { // srand( (unsigned)time( NULL ) ); // open COMM device CState=TRUE; //将函数CreateFileA的的返回值赋值给hComPort,并且判定其等于(HANDLE) -1 if ( (hComPort = CreateFileA( device,//设备名称 可以是COM2 COM3 之类的名字 GENERIC_READ | GENERIC_WRITE,//允许读写 0, // exclusive access 必须是0 NULL, // no security attrs OPEN_EXISTING, //设置产生方式 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // overlapped I/O 使用异步通讯 NULL ) ) == (HANDLE) -1 ) return ( FALSE ) ; // get any early notifications SetCommMask(hComPort, EV_RXCHAR ) ;//设置事件驱动类型 // setup device buffers SetupComm(hComPort, 10240, 10240 ) ;//设置输入 输出缓冲区的大小 // purge any information in the buffer PurgeComm(hComPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;//清干净输入和输出缓冲区 // set up for overlapped I/O COMMTIMEOUTS CommTimeOuts; //定义超时结构, 并且给结构的中的值赋值 CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ; CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ; CommTimeOuts.ReadTotalTimeoutConstant = 1000 ; CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ; CommTimeOuts.WriteTotalTimeoutConstant = 10000 ; SetCommTimeouts(hComPort, &CommTimeOuts ) ; DCB dcb;//定义数据控制块结构 dcb.DCBlength=sizeof(DCB); dcb.BaudRate=BaudRate;//通讯波特率 dcb.fBinary=1; dcb.fParity=1; dcb.fOutxCtsFlow=0; dcb.fOutxDsrFlow=0; dcb.fDtrControl=0; dcb.fDsrSensitivity=0; dcb.fTXContinueOnXoff=0; dcb.fOutX=0; dcb.fInX=0; dcb.fErrorChar=0; dcb.fNull=0; dcb.fRtsControl=0; dcb.fAbortOnError=0; // dcb.wReserved=0; dcb.XonLim=0; dcb.XoffLim=0; dcb.ByteSize=Bits;//数据位长度 Bits 就是八位的意思 也可以写数字8 dcb.Parity=0; dcb.StopBits=ONESTOPBIT;//停止位 是几位 此处写的一位 //dcb.StopBits=StopBit; if (!SetCommState(hComPort,&dcb))//数据配置完毕以后,就调用SetCommState函数 给端口配置 return (FALSE);//如果配置不成功就返回 FALSE if (!AfxBeginThread(CommWatchProc,(LPVOID)this))//启动一个辅助线程,用于串口事件的处理 //此处需要对线程说明一下: /* Windows提供了两种线程,辅助线程和用户界面线程。区别在于:辅助线程 没有窗口,所以它没有自己的消息循环。但是辅助线程很容易编程,通常也 很有用。我们使用辅助线程。主要用它来监视串口状态,看有无数据到达、通 信有无错误;而主线程则可专心进行数据处理、提供友好的用户界面等重要 的工作。 辅助线程还有一个名字 就是工作线程。 */ { CloseHandle(hComPort) ;//如果线程没有启动成功,就关闭句柄,并且返回FAlSE return (FALSE); } else { // assert DTR and RTS EscapeCommFunction(hComPort,SETDTR); EscapeCommFunction(hComPort,SETRTS); } memset( &write_os, 0, sizeof( OVERLAPPED )) ; write_os.hEvent = CreateEvent( NULL, // no security TRUE, // explicit reset req FALSE, // initial event reset NULL ); // no name if (NULL == write_os.hEvent) { CloseHandle( write_os.hEvent ) ; return (FALSE) ; } return(TRUE); } //关闭串口的端口 BOOL CCommunication::CloseSerialPort() { CState=FALSE; Sleep(10); if(!CloseHandle(hComPort)) return FALSE; else return TRUE; } void CCommunication::SetMessageReceiver(CWnd * pWnd) { //Add a line in class caller: //??.SetMessageReceiver(this); msg_receiver=pWnd; } //函数: 用于发送数据 。需要提交的是发送数据的字符内容 和长度 int CCommunication::SendData(char * data, int len) { CString b; int a=1,c=1; unsigned long sendlen; a=WriteFile(hComPort, data, len, &sendlen, &write_os);//其实其内部调用的是WriteFile这个函数。 //从本质上 系统把通讯的东西看成一个文件的读写。 CString strText; strText.Format(",,‘%s",data); // ::Log(GetDirectory()+"\\Log\\Com.csv",strText.Left(31),TRUE,"Time,Receive,send"); return sendlen; } /* 使用多线程技术,在工作线程(也就是所谓的辅助线程)里面监视串口,有数据到达时依靠事件驱动, 读取数据后向主线程汇报,注意发送数据的工作在主线程里面完成,因为通常来说发送数据的 内容(也就是所谓的下行数据)数量比较少,并且WaitCommEvent,ReadFile(),WriteFile() 都 使用非阻塞通信技术。依靠重叠(Overlappend)读写操作,让串口的读写工作在后台完成。 */ /* 读写文件是每个Windows软件开发人员都需要做的工作。可见这项工作是非常重要的, 毕竟各种各样的数据都需要保存起来,以便作各种各样的分析,或者通过网络传送给别人。 像大家用BT下载的电影,在那个BT软件里,就需要不断从网络里接收到数据, 然后再把这些数据保存到文件里合适的位置,就可以生成跟发行者那里一样的文件, 这样才可以播放出来。又比如我在玩《征途》的游戏里,刚刚打开游戏时, 它就不断从服务器上下载更新的文件下来,然后保存到硬盘。WriteFile函数是用来写数据到文件, ReadFile函数是从文件里读取数据出来。但这两个函数不但可以读取写磁盘的文件, 也可以接收和发送网络的数据,还有读写串口、USB、并口等设备的数据。在读写文件里, 首先就是先打开文件,然后判断打开是否成功。在写文件时,同时要注意磁盘的空间是否满等问题。 在读取文件时,往往需要读取不同位置的文件,比如要读取一个4G的视频文件, 就不可能完全把它读取到内存里,因此就需要对文件进行定位读取。 */ /* 函数WriteFile和ReadFile声明如下: WINBASEAPI BOOL WINAPI WriteFile( __in HANDLE hFile,//是文件句柄 __in_bcount(nNumberOfBytesToWrite) LPCVOID lpBuffer,//是读写数据缓冲区 __in DWORD nNumberOfBytesToWrite,//是多少数据要写入 __out_opt LPDWORD lpNumberOfBytesWritten,//是已经写入多少数据 __inout_opt LPOVERLAPPED lpOverlapped//是异步读写的结构 ); WINBASEAPI BOOL WINAPI ReadFile( __in HANDLE hFile,//是文件句柄 __out_bcount_part(nNumberOfBytesToRead, *lpNumberOfBytesRead) LPVOID lpBuffer,//是读写数据缓冲区 __in DWORD nNumberOfBytesToRead,//是多少数据要读取 __out_opt LPDWORD lpNumberOfBytesRead,//是已经读取多少数据 __inout_opt LPOVERLAPPED lpOverlapped//是异步读写的结构 ); hFile是文件句柄。 lpBuffer是读写数据缓冲区。 nNumberOfBytesToWrite是多少数据要写入。 lpNumberOfBytesWritten是已经写入多少数据。 nNumberOfBytesToRead是多少数据要读取。 nNumberOfBytesToRead是已经读取多少数据。 lpOverlapped是异步读写的结构。 调用函数的例子如下: #001 //创建、写入、读取文件。 #002 //蔡军生 2007/10/21 QQ:9073204 深圳 #003 void CreateFileDemo(void) #004 { #005 // #006 HANDLE hFile = ::CreateFile(_T("CreateFileDemo.txt"), //创建文件的名称。 #007 GENERIC_WRITE|GENERIC_READ, // 写和读文件。 #008 0, // 不共享读写。 #009 NULL, // 缺省安全属性。 #010 CREATE_ALWAYS, // 如果文件存在,也创建。 #011 FILE_ATTRIBUTE_NORMAL, // 一般的文件。 #012 NULL); // 模板文件为空。 #013 #014 if (hFile == INVALID_HANDLE_VALUE)//根据返回文件句柄的值,判定创建是否成功。 #015 { #016 // #017 OutputDebugString(_T("CreateFile fail!/r/n")); #018 } #019 #020 //往文件里写数据。 #021 const int BUFSIZE = 4096;//定义缓冲区的大小 #022 char chBuffer[BUFSIZE]; //定义一个缓冲区 其实就是一个字符数组 #023 memcpy(chBuffer,"Test",4);//由src指向地址为起始地址的连续n个字节的数据复制到 //以destin指向地址为起始地址的空间内。 #024 DWORD dwWritenSize = 0; #025 BOOL bRet = ::WriteFile(hFile,chBuffer,4,&dwWritenSize,NULL);//将字符数组里面 //的数据写入 文件句柄对应的那块内存区域里面。 #026 if (bRet) #027 { #028 // #029 OutputDebugString(_T("WriteFile 写文件成功/r/n")); #030 } #031 #032 //先把写文件缓冲区的数据强制写入磁盘。 #033 FlushFileBuffers(hFile); #034 #035 // #036 //从文件里读取数据。 #037 LONG lDistance = 0; #038 DWORD dwPtr = SetFilePointer(hFile, lDistance, NULL, FILE_BEGIN); #039 if (dwPtr == INVALID_SET_FILE_POINTER) #040 { #041 //获取出错码。 #042 DWORD dwError = GetLastError() ; #043 //处理出错。 #044 } #045 #046 DWORD dwReadSize = 0; #047 bRet = ::ReadFile(hFile,chBuffer,4,&dwReadSize,NULL); #048 if (bRet) #049 { #050 // #051 OutputDebugString(_T("ReadFile 读文件成功/r/n")); #052 } #053 else #054 { #055 //获取出错码。 #056 DWORD dwError = GetLastError(); #057 //处理出错。 #058 TCHAR chErrorBuf[1024]; #059 wsprintf(chErrorBuf,_T("GetLastError()=%d/r/n"),dwError); #060 OutputDebugString(chErrorBuf); #061 } #062 #063 } */ /* 在软件的需求里,把有用的数据保存起来是非常重要的功能。 比如每天的股票行情数据需要保存起来,以便生成K线图。 比如游戏客户端的LOG需要保存起,以便客户端出错时可以把LOG发送回来分析它出错的原因。 比如银行每天进行交易时,也需要把所有交易的数据保存到文件备份起来,以便进行结算。 还有在数据采集领域更是需要保存更多的数据,比如从DV里读取视频和语音数据出来, 就会生成12G的巨型文件。比如读DVD光盘里,把光盘做成虚拟光驱也有9G大小。 因此,创建文件是非常普通的功能,这个肯定是掌握,并且非常会使用的。 当然这个CreateFile函数不但可以创建文件,还可以打串口、并口、网络、USB设备等功能。 函数CreateFile声明如下: WINBASEAPI __out HANDLE WINAPI CreateFileA( __in LPCSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition,//是创建属性 __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); WINBASEAPI __out HANDLE WINAPI CreateFileW( __in LPCWSTR lpFileName,//是文件或是设备的名称 __in DWORD dwDesiredAccess,//是访问属性 __in DWORD dwShareMode,//是共享模式 __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,//是安全属性 __in DWORD dwCreationDisposition,//是创建属性 __in DWORD dwFlagsAndAttributes,//是文件标志和属性 __in_opt HANDLE hTemplateFile//是文件模板 ); #ifdef UNICODE #define CreateFile CreateFileW #else #define CreateFile CreateFileA #endif // !UNICODE lpFileName是文件或设备的名称。 dwDesiredAccess是访问属性。 dwShareMode是共享属性。 lpSecurityAttributes是安全属性。 dwCreationDisposition是创建属性。 dwFlagsAndAttributes是文件标志和属性。 hTemplateFile是文件模板。 调用函数的例子如下: #001 //创建文件。 #002 //蔡军生 2007/10/18 QQ:9073204 深圳 #003 void CreateFileDemo(void) #004 { #005 // #006 HANDLE hFile = ::CreateFile(_T("CreateFileDemo.txt"), //创建文件的名称。 #007 GENERIC_WRITE, // 写文件。 #008 0, // 不共享读写。 #009 NULL, // 缺省安全属性。 #010 CREATE_ALWAYS, // 如果文件存在,也创建。 #011 FILE_ATTRIBUTE_NORMAL, // 一般的文件。 #012 NULL); // 模板文件为空。 #013 #014 if (hFile == INVALID_HANDLE_VALUE) #015 { #016 // #017 OutputDebugString(_T("CreateFile fail!/r/n")); #018 } #019 } */
时间: 2024-10-07 10:21:02