1、深入浅出 Win32 多线程程序设计之综合实例时间: 2005-12-23 来自:天极开发本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。1.串口通信在工业控制系统中,工控机(一般都基于 PC Windows 平台)经常需要与单片机通过串口进行通信。因此,操作和使用 PC 的串口成为大多数单片机、嵌入式系统领域工程师必须具备的能力。串口的使用需要通过三个步骤来完成的:(1) 打开通信端口;(2) 初始化串口,设置波特率、数据位、停止位、奇偶校验等参数。为了给读者一个
2、直观的印象,下图从 Windows 的“控制面板系统设备管理器通信端口(COM1)“打开 COM 的设置窗口:(3) 读写串口。在 WIN32 平台下,对通信端口进行操作跟基本的文件操作一样。创建/打开 COM 资源下列函数如果调用成功,则返回一个标识通信端口的句柄,否则返回-1:HADLE CreateFile(PCTSTR lpFileName, /通信端口名,如“COM1“WORD dwDesiredAccess, /对资源的访问类型WORD dwShareMode, /指定共享模式,COM 不能共享,该参数为 0PSECURITY_ATTRIBUTES lpSecurityAttrib
3、utes,/安全描述符指针,可为 NULLWORD dwCreationDisposition, /创建方式WORD dwFlagsAndAttributes, /文件属性,可为 NULLHANDLE hTemplateFile /模板文件句柄,置为 NULL);获得/设置 COM 属性下列函数可以获得 COM 口的设备控制块,从而获得相关参数:BOOL WINAPI GetCommState(HANDLE hFile, /标识通信端口的句柄LPDCB lpDCB /指向一个设备控制块(DCB 结构)的指针);如果要调整通信端口的参数,则需要重新配置设备控制块,再用 WIN32 API Set
4、CommState()函数进行设置:BOOL SetCommState(HANDLE hFile, /标识通信端口的句柄LPDCB lpDCB /指向一个设备控制块(DCB 结构)的指针);DCB 结构包含了串口的各项参数设置,如下: typedef struct _DCB/ dcbDWORD DCBlength; / sizeof(DCB)DWORD BaudRate; / current baud rateDWORD fBinary: 1; / binary mode, no EOF checkDWORD fParity: 1; / enable parity checkingDWORD
5、fOutxCtsFlow: 1; / CTS output flow controlDWORD fOutxDsrFlow: 1; / DSR output flow controlDWORD fDtrControl: 2; / DTR flow control typeDWORD fDsrSensitivity: 1; / DSR sensitivityDWORD fTXContinueOnXoff: 1; / XOFF continues TxDWORD fOutX: 1; / XON/XOFF out flow controlDWORD fInX: 1; / XON/XOFF in flo
6、w controlDWORD fErrorChar: 1; / enable error replacementDWORD fNull: 1; / enable null strippingDWORD fRtsControl: 2; / RTS flow controlDWORD fAbortOnError: 1; / abort reads/writes on errorDWORD fDummy2: 17; / reservedWORD wReserved; / not currently usedWORD XonLim; / transmit XON thresholdWORD XoffL
7、im; / transmit XOFF thresholdBYTE ByteSize; / number of bits/byte, 4-8BYTE Parity; / 0-4=no,odd,even,mark,spaceBYTE StopBits; / 0,1,2 = 1, 1.5, 2char XonChar; / Tx and Rx XON characterchar XoffChar; / Tx and Rx XOFF characterchar ErrorChar; / error replacement characterchar EofChar; / end of input c
8、haracterchar EvtChar; / received event characterWORD wReserved1; / reserved; do not use DCB;读写串口在读写串口之前,还要用 PurgeComm()函数清空缓冲区,并用 SetCommMask ()函数设置事件掩模来监视指定通信端口上的事件,其原型为:BOOL SetCommMask(HANDLE hFile, /标识通信端口的句柄DWORD dwEvtMask /能够使能的通信事件);串口上可能发生的事件如下表所示:值 事件描述EV_BREAK A break was detected on input
9、.EV_CTS The CTS (clear-to-send) signal changed state.EV_DSR The DSR(data-set-ready) signal changed state.EV_ERR A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.EV_RING A ring indicator was detected.EV_RLSD The RLSD (receive-line-signal-detect) signal change
10、d state.EV_RXCHAR A character was received and placed in the input buffer.EV_RXFLAG The event character was received and placed in the input buffer. The event character is specified in the devices DCB structure, which is applied to a serial port by using the SetCommState function.EV_TXEMPTY The last
11、 character in the output buffer was sent. 在设置好事件掩模后,我们就可以利用 WaitCommEvent()函数来等待串口上发生事件,其函数原型为:BOOL WaitCommEvent(HANDLE hFile, /标识通信端口的句柄LPDWORD lpEvtMask, /指向存放事件标识变量的指针LPOVERLAPPED lpOverlapped, / 指向 overlapped 结构);我们可以在发生事件后,根据相应的事件类型,进行串口的读写操作:BOOL ReadFile(HANDLE hFile, /标识通信端口的句柄LPVOID lpBuff
12、er, /输入数据 Buffer 指针DWORD nNumberOfBytesToRead, / 需要读取的字节数LPDWORD lpNumberOfBytesRead, /实际读取的字节数指针LPOVERLAPPED lpOverlapped /指向 overlapped 结构);BOOL WriteFile(HANDLE hFile, /标识通信端口的句柄LPCVOID lpBuffer, /输出数据 Buffer 指针DWORD nNumberOfBytesToWrite, /需要写的字节数LPDWORD lpNumberOfBytesWritten, /实际写入的字节数指针LPOVER
13、LAPPED lpOverlapped /指向 overlapped 结构);深入浅出 Win32 多线程程序设计之综合实例时间: 2005-12-23 来自:天极开发2.工程实例下面我们用第 1 节所述 API 实现一个多线程的串口通信程序。这个例子工程(工程名为 MultiThreadCom)的界面很简单,如下图所示:它是一个多线程的应用程序,包括两个工作者线程,分别处理串口 1 和串口 2。为了简化问题,我们让连接两个串口的电缆只包含 RX、TX 两根连线(即不以硬件控制 RS-232,串口上只会发生 EV_TXEMPTY、EV_RXCHAR 事件)。在工程实例的 BOOL CMulti
14、ThreadComApp:InitInstance()函数中,启动并设置 COM1 和 COM2,其源代码为:BOOL CMultiThreadComApp:InitInstance()AfxEnableControlContainer();/打开并设置 COM1hComm1=CreateFile(“COM1“, GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);if (hComm1=(HANDLE)-1)AfxMessageBox(“打开 COM1 失败“);return false;elseDCB wdcb;GetCom
15、mState (hComm1,wdcb.BaudRate=9600;SetCommState (hComm1,PurgeComm(hComm1,PURGE_TXCLEAR);/打开并设置 COM2hComm2=CreateFile(“COM2“, GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);if (hComm2=(HANDLE)-1)AfxMessageBox(“打开 COM2 失败“);return false;elseDCB wdcb; GetCommState (hComm2,wdcb.BaudRate=9600
16、;SetCommState (hComm2,PurgeComm(hComm2,PURGE_TXCLEAR);CMultiThreadComDlg dlg;m_pMainWnd = int nResponse = dlg.DoModal();if (nResponse = IDOK)/ TODO: Place code here to handle when the dialog is/ dismissed with OKelse if (nResponse = IDCANCEL)/ TODO: Place code here to handle when the dialog is/ dism
17、issed with Cancelreturn FALSE;此后我们在对话框 CMultiThreadComDlg 的初始化函数 OnInitDialog 中启动两个分别处理 COM1 和 COM2 的线程:BOOL CMultiThreadComDlg:OnInitDialog()CDialog:OnInitDialog(); / Add “About.“ menu item to system menu./ IDM_ABOUTBOX must be in the system command range.ASSERT(IDM_ABOUTBOX ASSERT(IDM_ABOUTBOX App
18、endMenu(MF_SEPARATOR);pSysMenu-AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);/ Set the icon for this dialog. The framework does this automatically/ when the applications main window is not a dialogSetIcon(m_hIcon, TRUE); / Set big iconSetIcon(m_hIcon, FALSE); / Set small icon/ TODO: Add extra in
19、itialization here/启动串口 1 处理线程 DWORD nThreadId1;hCommThread1 = :CreateThread(LPSECURITY_ATTRIBUTES)NULL, 0,(LPTHREAD_START_ROUTINE)Com1ThreadProcess, AfxGetMainWnd()-m_hWnd, 0, if (hCommThread1 = NULL)AfxMessageBox(“创建串口 1 处理线程失败“);return false;/启动串口 2 处理线程DWORD nThreadId2;hCommThread2 = :CreateThrea
20、d(LPSECURITY_ATTRIBUTES)NULL, 0,(LPTHREAD_START_ROUTINE)Com2ThreadProcess, AfxGetMainWnd()-m_hWnd, 0, if (hCommThread2 = NULL)AfxMessageBox(“创建串口 2 处理线程失败“);return false;return TRUE; / return TRUE unless you set the focus to a control两个串口 COM1 和 COM2 对应的线程处理函数等待串口上发生事件,并根据事件类型和自身缓冲区是否有数据要发送进行相应的处理,其
21、源代码为:DWORD WINAPI Com1ThreadProcess(HWND hWnd/主窗口句柄)DWORD wEven;char str10; /读入数据SetCommMask(hComm1, EV_RXCHAR | EV_TXEMPTY);while (TRUE)WaitCommEvent(hComm1, if(wEven = 0)CloseHandle(hCommThread1);hCommThread1 = NULL;ExitThread(0);elseswitch (wEven)case EV_TXEMPTY:if (wTxPos wTxLen)/在串口 1 写入数据DWORD
22、 wCount; /写入的字节数WriteFile(hComm1, com1Data.TxBufwTxPos, 1, com1Data.wTxPos+;break;case EV_RXCHAR:if (com1Data.wRxPos com1Data.wRxLen)/读取串口数据, 处理收到的数据DWORD wCount; /读取的字节数ReadFile(hComm1, com1Data.RxBufwRxPos, 1, com1Data.wRxPos+;if(com1Data.wRxPos= com1Data.wRxLen);:PostMessage(hWnd, COM_SENDCHAR, 0
23、, 1);break;return TRUE;DWORD WINAPI Com2ThreadProcess(HWND hWnd /主窗口句柄)DWORD wEven;char str10; /读入数据SetCommMask(hComm2, EV_RXCHAR | EV_TXEMPTY);while (TRUE)WaitCommEvent(hComm2, if (wEven = 0)CloseHandle(hCommThread2);hCommThread2 = NULL;ExitThread(0);elseswitch (wEven)case EV_TXEMPTY:if (wTxPos wTx
24、Len)/在串口 2 写入数据DWORD wCount; /写入的字节数WriteFile(hComm2, com2Data.TxBufwTxPos, 1, com2Data.wTxPos+;break;case EV_RXCHAR:if (com2Data.wRxPos com2Data.wRxLen)/读取串口数据, 处理收到的数据DWORD wCount; /读取的字节数ReadFile(hComm2, com2Data.RxBufwRxPos, 1, com2Data.wRxPos+;if(com2Data.wRxPos= com2Data.wRxLen);:PostMessage(h
25、Wnd, COM_SENDCHAR, 0, 1);break;return TRUE;线程控制函数中所操作的 com1Data 和 com2Data 是与串口对应的数据结构struct tagSerialPort 的实例,这个数据结构是: typedef struct tagSerialPortBYTE RxBufSPRX_BUFLEN;/接收 BufferWORD wRxPos; /当前接收字节位置WORD wRxLen; /要接收的字节数BYTE TxBufSPTX_BUFLEN;/发送 BufferWORD wTxPos; /当前发送字节位置WORD wTxLen; /要发送的字节数Se
26、rialPort, * LPSerialPort;深入浅出 Win32 多线程程序设计之综合实例时间: 2005-12-23 来自:天极开发3.多线程串口类使用多线程串口通信更方便的途径是编写一个多线程的串口类,例如Remon Spekreijse 编写了一个 CSerialPort 串口类。仔细分析这个类的源代码,将十分有助于我们对先前所学多线程及同步知识的理解。3.1 类的定义#ifndef _SERIALPORT_H_#define _SERIALPORT_H_#define WM_COMM_BREAK_DETECTED WM_USER+1 / A break was detected
27、on input.#define WM_COMM_CTS_DETECTED WM_USER+2 / The CTS (clear-to-send) signal changed state. #define WM_COMM_DSR_DETECTED WM_USER+3 / The DSR (data-set-ready) signal changed state. #define WM_COMM_ERR_DETECTED WM_USER+4 / A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.