// 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 }
*/