翻译:Serial Communications in Win32

艾伦很少吃早餐,但是如果他必须挑一个喜欢的事物,Win32 串口通讯将会是最好的选择。

摘要:
微软Win32下的串口通讯完全不同于Windows下16位的串口通讯,那些熟悉16位下串口通讯功能的将不得不去适当的重新学习许多Win32下串口通讯的系统部分,这篇文章将会帮助你完成这些。对串口通讯不熟悉的那些人通过这篇文章的学习将会为自己以后的研究发展奠定坚实的基础。
这篇文章主要以读者熟悉的多线程和Win32下的并行操作为例。除此之外,基于对Win 32堆栈功能的熟悉了解在理解内存管理机制中是非常有用的,比如这篇文章所提到的MTTTY。对于较多关于这些功能的信息,商议平台 SDK 文件的编写,微软公司Win32 SDK 知识库 , 或微软公司网络开发者程序馆。那些控制用户接口和会话视窗的功能界面(APIs)尽管在这里并不做讨论,但是对于完全了解此篇文章提供的例子是非常有用的。对于不熟悉一般微软视窗操作系统的应该在学习串口通讯之前学习一些基本的微软程序设计。换句话说,在潜水之前先沾湿你的脚。

绪论:
这篇文章的焦点主要是应用程序设计界面(API)和微软公司兼容的方法上,Windows NT 和 Windows 95;因此,API在两个平台上的支持是唯一的探讨。Windows 95 支持 Win32,电话API(TAPI)和Windows NT 3.x不支持;因此,这里我们不讨论TAPI 。然而,TAPI 确实值得一提,它在调制解调器接口和呼叫控制中的应用是非常有用的。一个用调制解调器工作和电话程序应该实现这些TAPI 功能界面。这将允许用另一个TAPI应用程序接口与之紧密结合。此外,这篇文章不讨论Win32下象GetCommProperties的一些配置功能。
这篇文章从如下几段入手:打开串口、读写操作(非重叠和重叠)、串口状态(事件和错误)和串口设置(DCB,流量控制和通信超时)。
这篇文章所包含的例子MTTTY:多线程TTY诸多功能实现的讨论。它使用了三个线程来实现:一个内存管理用户界面的线程,一个控制所有写操作的线程,一个控制读和改变串口状态的线程。这个例子中的内存管理使用了一些不同的数据堆。它也广泛的利用同步方法促进线程间的通信。

打开串口:
用CreateFile功能打开一个通信端口。有两种方法调用CreateFile打开端口:重叠的和非重叠。下面是用重叠的方法打开通信资源操作的一个例子:
HANDLE hComm;
hComm = CreateFile( gszPort,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hComm == INVALID_HANDLE_VALUE)
// 打开错误;中断
移除标记FILE_FLAG_OVERLAPPED在调用CreateFile非重叠运算中的应用,下一区段将讨论重叠和非重叠运算。
当用SDK文件陈述平台打开一个通信端口的时候,CreateFile 的调用有下列各项需求:
l fdw共享模式必须为0,通信端口与共享文件用相同的共享方式。可以使用TAPI的功能去共享应用程序间的资源。由于Win32应用程序不能使用TAPI,句柄遗传或者副本对共享通信来说是必需的。副本处理在这篇文章讨论到了;关于更多的信息请先参阅SDK文件平台。

l fdwCreate必须指定OPEN_EXISTING标志。
l hTemplateFile参数必须为0。
传统意义上关于端口的命名是COM1,COM2,COM3,或者COM4。Win32 API不给系统上的端口提供任何确定的机制。Windows NT 和 Windows 95保留了不同系统间的端口共性,所以任何一种方法应用在Win32平台上都比较方便。一些系统甚至比传统的最大4个端口还要多。硬件厂商和驱动程序开发者可以他们喜欢的方式自由命名端口。正是由于这一原因,它可以让使用者更好的去指定他们所需端口的名字。如果一个端口不存在,一个错误将会在尝试打开端口厚出错,并且使用者应该标明此端口不可利用。

串口的读写:
利用Win32进行串口间通讯的读写操作和Win32下文件的输入输出(I/O)操作是非常相似的。实际上,完成文件输入输出和串口间的输入输出功能是相同的。Win32下的I/O操作可以用两种方法实现:重叠或者非重叠。SDk文件编写平台采用异步和同步方式来完成这些I/O间的操作。在这篇文章中,经常提到重叠和非重叠。
非重叠方式的I/O对大多数开发者来说是比较熟悉的,因为它是传统的I/O方式,当功能返回的时候被请求的操作完成。在重叠I/O情况下,当一个操作没有完成和操作完成信号出现时系统将立即调用。程序将利用I/O请求和完成之间的时间去执行后台作业。
Win32下串口通讯的读写操作明显不同与16位,16位Windows操作系统仅仅具有ReadComm 和 WriteComm功能。Win32下的读写包括更多的功能和选择。我们将在下面讨论这些问题。
非重叠I/O:
非重叠I/O尽管有些限制但是是非常简单的。当调用线程阻塞的时候一个操作发生。一旦操作完成,功能返回并当前且线程可以继续以前的作业。这种类型I/O在多线程应用中是非常有用的,因为当一个线程阻塞时其他的线程仍旧可以完成当前作业。它担负着恰当使用端口应用的职责,如果一个线程为了等待她的I/O操作完成而阻塞,随后所有其它的线程调用API将被阻塞直到最初的操作完成。举例来说,如果一个线程正在等待ReadFile的返回,任何其它执行WriteFile功能的线程将被阻塞。
便携与否是是否选用重叠和非重叠众多原因中主要的一个。因为大多数操作系统不支持重叠的,而大多数操作系统支持多线程,因而多线程非重叠I/O是最好的选择。

重叠I/O:
重叠I/O不像非重叠I/O那样简单但是有更多的适应性和有效性。一个为重叠操作打开的端口允许在同一时刻使用多线程操作并且当操作没有确定的时候仍然可以执行其它作业。此外,重叠操作在操作不可确定的情况下允许单一线程在后台响应不同的请求和完成作业。
在单线程和多线程应用中,一些同步操作在响应请求和处理结果的时候一定发生。一个线程一直阻塞直到得到可用的操作结果。重叠I/O的优势就是在请求和它完成这段时间之间允许一个线程完成一些作业。如果没有可能的工作被做,那么唯一的情况就是重叠I/O允许更好的用户响应。
MTTTY就是使用重叠I/O操作的一个简单例子。它创造一个线程来负责端口数据的写状态,它同时周期性执行后台工作。程序创造另外一个专有的线程来从端口写数据。
注意:应用软件有时候会滥用多线程系统创造太多的线程以至降低软件效率,但是使用多线程可以解决许多比较难的问题。线程的减少可以缓解系统资源使用可是仍然需要诸如CPU时钟和内存的资源使用。一个可以产生多余线程的应用软件可能影响整个操作系统的运行。一个较好利用线程的方法就是为不同的作业创建不同的请求队列和不同的作业流来适应I/O操作,这种方法在MTTTY样本中的利用在这篇文章中提及。
一个重叠的I/O操作有两个部分:操作的创建和检测的完成。操作的创建必须限定使用OVERLAPPED结构,为同步操作创建一个手动操作并且调用适当的功能(读文件或者写文件)。I/O操作肯定被立刻完成或者不可能。假定一个重叠的操作总是依赖于另外一个重叠操作应用程序将会出错。如果一个操作被立刻完成,那么这个操作需要继续正常执行。重叠操作的第二部分直接探测它的完成。检波探测的完成包括等待事件处理,检查重叠结果和处理数据。重叠操作将会产生更多作业的原因就是产生许多错误,如果一个非重叠操作发生错误,仅仅返回一个错误结果。但是如果一个重叠操作错误发生,它会在操作创建中失败或者使程序的执行不可预见,此时你必须设定一个操作时间段或者等待操作完成的信号。
串口的读操作:
ReadFile的功能就是发出一个读操作。ReadFileEx也发出一个读操作,但是它在Windows 95上不可用,在这篇文章中我们不讨论它。这儿有详细的代码描述怎样去发出一个读请求,当ReadFile返回值为真的时候将调用此功能去处理数据,在重叠操作下有同样的调用功能。我们用fWaitingOnRead来定义代码段,用它来指明读操作是否是重叠方式,它通常用来避免产生一个新的读操作。
DWORD dwRead;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};

// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

if (osReader.hEvent == NULL)
// Error creating overlapped event; abort.

if (!fWaitingOnRead) {
// Issue read operation.
if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader)) {
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
// Error in communications; report it.
else
fWaitingOnRead = TRUE;
}
else {
// read completed immediately
HandleASuccessfulRead(lpBuf, dwRead);
}
}
重叠操作的第二步分是完成检波。在OVERLAPPED结构中事件的处理被传送到WaitForSingl eObject功能模块中,它将一直等到对象发出执行信号。一旦操作完成,事件发出信号。这并不意味着它被成功执行,仅仅表明执行完成。GetOverlappedResult功能报告操作结果,如果发生一个错误,GetOverlappedResult返回出错并且GetLastError返回错误码。如果操作成功完成,GetOverlappedResult返回值为真。
注意:ERROR_IO_INCOMPLETE能检测操作是否完成同时可以返回操作失败的状态。当操作没有完成时GetOverlappedResult返回出错并且GetLastError返回ERROR_IO_INCOMPLETE。除此之外,ERROR_IO_INCOMPLETE可能被阻塞直到操作完成。这就有效的将重叠操作转变成非重叠操作并且当完成时通过bWait参数来传递真值。 这里有一段演示重叠方式下完成读操作的代码。当操作立刻完成的时候代码调用相同的功能来处理数据。请注意fWaitingOnRead标志的使用,当一操作正在运行的时候它被调用来控制检测代码。
#define READ_TIMEOUT 500 // milliseconds

DWORD dwRes;

if (fWaitingOnRead) {
dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);
switch(dwRes)
{
// Read completed.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osReader, &dwRead, FALSE))
// Error in communications; report it.
else
// Read completed successfully.
HandleASuccessfulRead(lpBuf, dwRead);

// Reset flag so that another opertion can be issued.
fWaitingOnRead = FALSE;
break;

case WAIT_TIMEOUT:
// Operation isn’t complete yet. fWaitingOnRead flag isn’t
// changed since I’ll loop back around, and I don’t want
// to issue another read until the first one finishes.
//
// This is a good time to do some background work.
break;

default:
// Error in the WaitForSingleObject; abort.
// This indicates a problem with the OVERLAPPED structure’s
// event handle.
break;
}
}
串口的写操作:
在串口通讯中数据的传送和API下数据的读出有很多相似之处。下面摘录的代码显示了如何执行和等待写操作的完成。
BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)
{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
DWORD dwRes;
BOOL fRes;

// Create this write operation’s OVERLAPPED structure’s hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// error creating overlapped event handle
return FALSE;

// Issue write.
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but isn’t delayed. Report error and abort.
fRes = FALSE;
}
else
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
// OVERLAPPED structure’s event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE))

fRes = FALSE;
else
// Write operation completed successfully.
fRes = TRUE;
break;

default:
// An error has occurred in WaitForSingleObject.
// This usually indicates a problem with the
// OVERLAPPED structure’s event handle.
fRes = FALSE;
break;
}
}
}
else
// WriteFile completed immediately.
fRes = TRUE;

CloseHandle(osWrite.hEvent);
return fRes;
}
我们注意到上面的代码使用了WaitForSingleObject功能和INFINITE定时值。这将引起WaitForSingleObject功能一直等待到操作完成;这还可能使线程或者程序刮起。事实上,写控制的执行只不过花费了较长的一段时间去完成或者使流控制阻塞了传输。稍后讨论的状态检测可以检测到这种状态,但是不能使WaitForSingleObject返回,三个事件可以缓解这种状态:
l 在单一线程中放置这些代码,可以使其它线程去执行任何我们所希望的功能而不是等待其它写线程执行完成,这正是MTTTY所具有的。
l 使用COMMTIMEOUTS在一个暂停周期后来完成写的操作。在这篇文章的后一段完全讨论
了“通信暂停”,这也是MTTTY所允许的。
l 改变WaitForSingleObject的调用包括一个时时暂停值。由于当旧的操作仍不能预见时这个程序执行另外一个操作,那么行的重叠结构和重叠时间需要被分配,这将导致许多问题。在操作设计方面同使用“作业队列存储”相比这种记录保存就困难多了。“作业队列存储”的方法被使用在MTTTY中。
注意:同步功能中的暂停数值不是暂停通信,同步暂停引起WaitForSingleObject或WaitF
orMultipleObjects返回WAIT_TIMEOUT。这不同与读写操作的暂停,这篇文章后面描述了通信暂停。
由于在上段摘录的代码中WaitForSingleObject功能使用了一个永久暂停,它相当于为fWait参数使用GetOverlappedResult返回真。在下面这个简单的表格中使用了相同的代码。
BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)
{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
BOOL fRes;

// Create this writes OVERLAPPED structure hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// Error creating overlapped event handle.
return FALSE;

// Issue write.
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but it isn’t delayed. Report error and abort.
fRes = FALSE;
}
else {
// Write is pending.
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, TRUE))
fRes = FALSE;
else
// Write operation completed successfully.
fRes = TRUE;
}
}
else
// WriteFile completed immediately.
fRes = TRUE;

CloseHandle(osWrite.hEvent);
return fRes;
}
GetOverlappedResult在等待一个重叠操作时并不总是最好的选择,举例来说,如果一个应用程序需要依附于另外一个事件的处理,那么第一个代码段币第二个要好多了。在等待其它事件处理时WaitForSingle的调用是很容易转换成WaitForMultipleObjects。这正是MTTTY程序本身所具有的。
在以前的重叠操作完成前,重叠I/O会产生一个普通错误。如果一个重叠操作在先前的一个操作完成前执行,那么必须给他分配一个新的重叠结构,同时也为重叠结构的hEvent成员创建一个手动事件。一旦重叠操作完成,重叠结构和它的事件变可以重新自由使用。
在串口通讯中唯一一个要被修改的重叠结构的成员就是hEvent,其它的重叠结构成员
应该初始化为0或者单独使用。我们不需要为串口通讯设备更改其它重叠结构成员。重叠结构中ReadFile和WriteFile的文件状态Offset和OffsetHigh必须随着程序更新,否则结果将不可预见。这一规则在重叠结构的其它资源类型中应用,就像文件结构。
串口状态:
有两种方法取回端口状态,第一种是设置一个事件标志当期望事件发生时通知程序,
用SetCommMask设置事件标志,WaitCommEvent标明期望事件的发生。这些功能与16位下SetCommEventMask 和 EnableCommNotification功能类似,区别在于Win32不发送WM_COMMNO TIFY信息。事实上,WM_COMMNOTIFY信息甚至不是Win32下API的部分。第二种方法就是周期性地调用一些不同的状态功能,轮流检测既非有效的或者不被推荐的。
通讯事件:
在串口通讯中通讯事件可能发生在任何时刻,在接收到通讯事件通知会按照以下两个
步骤:
l 用SetCommMask设定预期事件的通知。
l 用WaitCommEvent发出状态检查,状态检查可能是重叠或者非重叠的,正如读和写操作
注意:上下文所提及的事件仅仅指通讯事件,它不作为任何一个同步的事务事件。
这儿有一个SetCommMask功能的例子:
DWORD dwStoredFlags;

dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING |\
EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY ;
if (!SetCommMask(hComm, dwStoredFlags))
// error setting communications mask
在详细描述这一事件后,来看WaitCommEvent功能对发生事件的探测。如果打开了非重叠操作端口,那么WaitCommEvent功能不包含重叠结构。在一个事件发生前阻塞功能将组织调用线程,如果一个事件发生了,线程可能不确定的阻塞。
这儿有一个显示当一个端口为非重叠操作打开时如何去等待EV_RING事件:
DWORD dwCommEvent;

if (!SetCommMask(hComm, EV_RING))
// Error setting communications mask
return FALSE;

if (!WaitCommEvent(hComm, &dwCommEvent, NULL))
// An error occurred waiting for the event.
return FALSE;
else
// Event has occurred.
return TRUE;
注意:微软Win32 SDK基础文件和Windows95在EV_RING标志有相同的问题。上面的代码在Windows95下不会返回结果因为EV_RING事件不对Windows95系统进行检测。Windows NT则完全保留EV_RING事件,请通过Win32 SDK只是来获取更多的这方面的信息。
正如所说的,如果一个事件发生上面的代码将被永久阻塞。更好的解决办法就是为重叠操作打开一个端口并且用下面的方式来等待事件状态:
#define STATUS_CHECK_TIMEOUT 500 // Milliseconds

DWORD dwRes;
DWORD dwCommEvent;
DWORD dwStoredFlags;
BOOL fWaitingOnStat = FALSE;
OVERLAPPED osStatus = {0};

dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING |\
EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY ;
if (!SetCommMask(comHandle, dwStoredFlags))
// error setting communications mask; abort
return 0;

osStatus.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osStatus.hEvent == NULL)
// error creating event; abort
return 0;

for ( ; ; ) {
// Issue a status event check if one hasn’t been issued already.
if (!fWaitingOnStat) {
if (!WaitCommEvent(hComm, &dwCommEvent, &osStatus)) {
if (GetLastError() == ERROR_IO_PENDING)
bWaitingOnStatusHandle = TRUE;
else
// error in WaitCommEvent; abort
break;
}
else
// WaitCommEvent returned immediately.
// Deal with status event as appropriate.
ReportStatusEvent(dwCommEvent);
}

// Check on overlapped operation.
if (fWaitingOnStat) {
// Wait a little while for an event to occur.
dwRes = WaitForSingleObject(osStatus.hEvent, STATUS_CHECK_TIMEOUT);
switch(dwRes)
{
// Event occurred.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osStatus, &dwOvRes, FALSE))

// An error occurred in the overlapped operation;
// call GetLastError to find out what it was
// and abort if it is fatal.
else
// Status event is stored in the event flag
// specified in the original WaitCommEvent call.
// Deal with the status event as appropriate.
ReportStatusEvent(dwCommEvent);

// Set fWaitingOnStat flag to indicate that a new
// WaitCommEvent is to be issued.
fWaitingOnStat = FALSE;
break;

case WAIT_TIMEOUT:
// Operation isn’t complete yet. fWaitingOnStatusHandle flag

// isn’t changed since I’ll loop back around and I don’t want

// to issue another WaitCommEvent until the first one finishe
s.
//
// This is a good time to do some background work.
DoBackgroundWork();
break;

default:
// Error in the WaitForSingleObject; abort
// This indicates a problem with the OVERLAPPED structure’s
// event handle.
CloseHandle(osStatus.hEvent);
return 0;
}
}
}
CloseHandle(osStatus.hEvent);
上面的代码和重叠方式下的读操作代码非常相似,事实上MTTTY通过使用WaitForMult
ipleObjects在等待读事件或者状态事件发出信号的时候来完成它在同一线程中的读和状态检测。
在程序执行时有两个副作用SetCommMask 和 WaitCommEvent。第一,如果通讯端口打
开方式为非重叠操作,WaitCommEvent将被阻塞直到某一事件发生。如果另外一个线程调用 SetCommMask创立一个新的事件,此线程将在调用SetCommMask的时候被阻塞。造成这个的原因是第一个线程的最初调用WaitCommEvent仍正在运行。SetCommMas的调用阻塞了这个线程直到WaitCommEvent功能在第一个线程返回。这个副作用对于非重叠I/O下端口的打开时很普遍的。如果一个线程在任一通讯功能上被阻塞并且另外一个线程在调用通讯功能,第二个线程将被阻塞知道第一个线程返回通讯功能。引起这些功能第二个因素就是他们所使用的重叠操作下端口的打开。如果SetCommMask创建了一个新的事件,任何不可预料的WaitCommEvent将成功完成,并且由这种操作所引起的事件为空。
警告:
使用EV_RXCHAR标志在一个字节到达端口的时候来通知线程。这种事件同ReadFile功能联合起来可以使一个程序去在接收缓冲区去读数据,而不是在等待数据到达时去读。这对非重叠操作是非常有用的因为程序不需要去选取进入的数据;当EV_RXCHAR事件发生时程序被通知有进入数据。最初试图去解决这些代码的通常产生如下的伪代码,包括在这一段中的涉及的一个疏忽:
DWORD dwCommEvent;
DWORD dwRead;
char chRead;

if (!SetCommMask(hComm, EV_RXCHAR))
// Error setting communications event mask.

for ( ; ; ) {
if (WaitCommEvent(hComm, &dwCommEvent, NULL)) {
if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))
// A byte has been read; process it.
else
// An error occurred in the ReadFile call.
break;
}
else
// Error in WaitCommEvent.
break;
}
上述代码是等待一个EV_RXCHAR事件的发生。当发生的时候,代码调用ReadFile来读一个接收到的自己。再次循环,代码等待另一个EV_RXCHAR事件。当1个或者2个字节迅速到达时这段代码很适用。字节的接收引起EV_RXCHAR事件的发生,代码读出字节,如果在代码再次调用WaitCommEvent以前没有其它的字节到达,那么所有的都可以很好的执行。下一个字节的到达将引起WaitCommEvent功能来引发EV_RXCHAR事件发生。如果在代码有机会执行WaitCommEvent功能前有另外一个字节到达的信号,那么所有代码也都可以很好执行。第一个字节以以前的方式读入,到达的第二个字节在其内创建EV_RXCHAR标志。当代码返回WaitCommEvent功能时,他指出EV_RXCHAR事件的发生和第二个字节通过调用ReadFile从端口读出。
当有3个或者更多字节快速到达时,上述代码将有错误发生。第一个字节引起EV_RXCHAR事件发生,第二个字节在其内创建EV_RXCHAR标志。下一次的时候代码调用WaitCommEvent,它需要EV_RXCHAR事件。现在,如果第三个字节到达通讯端口,它会引起系统试图在其内创建EV_RXCHAR标志,因为在第二个字节到达时这已经发生,所以这次操作将被忽视。代码将顺利读取第一个字节,以后代码将调用WaitCommEvent并且它也需要EV_RXCHAR事件的发生(从第二个字节到达时)。第二个字节被读取,代码返回WaitCommEvent功能,第三个字节在系统的接收缓冲区等待。代码和系统现在超出了同步,当第四个字节最后到达时, EV_RXCHAR事件发生,并且代码读取一个单一字节。它读取第三个字节,这将是不确定的。
解决这一问题的办法看起来和在读操作的时候增加字节请求的数量一样简单,并非只能请求一个字节,代码可以请求两个,十个或者其它的一些数目。这一方法的问题时在2个或者更多的如上大小字节快速到达发在端口出读请求的时候仍将失败。所以,如果2个字节被读取,那么快速到达的4个字节将出现问题,当12个字节快速到达时10个字节请求仍将失败。 真正能解决这一问题的方法就是从端口读取数据直到没有字节剩余。如下伪代码就是通过循环读取知道字节为0。另外一种可行的方法就是调用ClearCommError来确定缓冲区的字节数目,通过一次读操作完成。这种方法需要更多的缓冲器,但是当一次到达许多数据时它可以较少读的次数。
DWORD dwCommEvent;
DWORD dwRead;
char chRead;

if (!SetCommMask(hComm, EV_RXCHAR))
// Error setting communications event mask

for ( ; ; ) {
if (WaitCommEvent(hComm, &dwCommEvent, NULL)) {
do {
if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))
// A byte has been read; process it.
else
// An error occurred in the ReadFile call.
break;
} while (dwRead);
}
else
// Error in WaitCommEvent
break;
}
上述代码在没有设定适当的暂停时间时将不能正确执行。稍后再讨论通讯暂停影响ReadFile操作在不等待字节到达时的返回的行为。这篇文章的后面“通讯暂停”讨论了这一主题。上述关于EV_RXCHAR的警告也适用于EV_RXFLAG。如果标志字节快速到达,EV_RXFLAG事件可能为它们全部发生。再一次,最好的解决方法还是读完所有的字节。上述警告也适用于其它与字节接受无关的事件。如果一些事件快速连续发生,一些通知就会被遗失。举例来说,如果表明一个CTS起始高度,然后低下去,高出,再低下去,那么EV_CTS事件就会发生。如果CTS改变非常迅速,我们就不能确定有有多少EV_CTS事件发生被WaitCommEvent检测到。由于这个原因,WaitCommEvent不能被用来保持这一线性状态。线
性状态在这篇文章后面的“调制解调器状态”有所涉及。
出错处理和通讯状态:
在调用SetCommMask时被指定的其中之一的通讯事件标志很可能就是EV_ERR。EV_ERR事件的发生表明在通讯端口存在错误。其它的错误可能在端口发生而不引起EV_ERR事件发生。在任一情况下,通讯端口的错误引起所有的I/O操作暂停直到错误状态被移除。ClearCommError有发现错误和清除错误状态的功能。 ClearCommError也可以提供通讯状况表明为什么传输被停止,它还可以指出在等待传输和缓冲区接收的字节数目。传输可能停止的原因时由于出错或者流控制出错。文章稍后将对流控制的发生进行讨论。
这里示范怎样调用ClearCommError的一些代码:
COMSTAT comStat;
DWORD dwErrors;
BOOL fOOP, fOVERRUN, fPTO, fRXOVER, fRXPARITY, fTXFULL;
BOOL fBREAK, fDNS, fFRAME, fIOE, fMODE;

// Get and clear current errors on the port.
if (!ClearCommError(hComm, &dwErrors, &comStat))
// Report error in ClearCommError.
return;

// Get error flags.
fDNS = dwErrors & CE_DNS;
fIOE = dwErrors & CE_IOE;
fOOP = dwErrors & CE_OOP;
fPTO = dwErrors & CE_PTO;
fMODE = dwErrors & CE_MODE;
fBREAK = dwErrors & CE_BREAK;
fFRAME = dwErrors & CE_FRAME;
fRXOVER = dwErrors & CE_RXOVER;
fTXFULL = dwErrors & CE_TXFULL;
fOVERRUN = dwErrors & CE_OVERRUN;
fRXPARITY = dwErrors & CE_RXPARITY;

// COMSTAT structure contains information regarding
// communications status.
if (comStat.fCtsHold)
// Tx waiting for CTS signal

if (comStat.fDsrHold)
// Tx waiting for DSR signal

if (comStat.fRlsdHold)
// Tx waiting for RLSD signal

if (comStat.fXoffHold)
// Tx waiting, XOFF char rec’d

if (comStat.fXoffSent)
// Tx waiting, XOFF char sent

if (comStat.fEof)
// EOF character received

if (comStat.fTxim)
// Character waiting for Tx; char queued with TransmitCommChar

if (comStat.cbInQue)
// comStat.cbInQue bytes have been received, but not read

if (comStat.cbOutQue)
// comStat.cbOutQue bytes are awaiting transfer
调制解调器状态(a.k.a. 线性状态):
对SetCommMask的调用可能包括EV_CTS, EV_DSR, EV_RING, 和 EV_RLSD标志。这些标志显示串行端口下伏特数相对于线性状态的改变,没有这些线性的真实状态的显示,仅仅是状态改变的发生。GetCommModemStatus的作用就是通过对每一状态在低于标准返回0和高出的时候返回1这种方式使它保持在真实状态。 请注意术语RLSD(接受讯号检测)被普遍称为CD(载波检测)线。注意:EV_RING标志在Windows 95下不能象前面所提到的那样工作,然而GetCommModemStatus功能确实可以检测环线状态。 线性状态下这些改变可能引起流控制事件。ClearCommError功能报告由于流控制传输是否被暂停。如果必要的话,一个线程可能调用ClearCommError检测事件是否是由于流控制所造成的。在文章的后面我们涉及到了“流控制”。
这里有一些如何调用GetCommModemStatus的代码:
DWORD dwModemStatus;
BOOL fCTS, fDSR, fRING, fRLSD;

if (!GetCommModemStatus(hComm, &dwModemStatus))
// Error in GetCommModemStatus;
return;

fCTS = MS_CTS_ON & dwModemStatus;
fDSR = MS_DSR_ON & dwModemStatus;
fRING = MS_RING_ON & dwModemStatus;
fRLSD = MS_RLSD_ON & dwModemStatus;

// Do something with the flags.
函数扩展:
在需要时驱动器将自动改变控制线的转台。一般而言,驱动器控制状态线的改变。如果设备用不同于RS-232标准进行通讯的话,那么标准的串口通讯的标准驱动器将不能控制设备。如果标准串口通讯驱动器不能控制设备,一个常规的设备驱动器就成为必须的了。
有时有这样的情况发生:标准控制线由应用程序控制而不是由串口通讯驱动器控制。举例来说,一个应用程序可能实现自己的流控制,应用程序可能是由于RTS 和 DTR状态线的改变而造成的,EscapeCommFunction通过检测一个通讯驱动器来执行扩展操作,EscapeCommFunction可以使驱动器执行一些其它的功能,象设置或者清除BREAK状态。关于这个功能的
更多信息,请参阅SDK文件平台,微软Win32SDk知识库,或者微软网络开发程序馆。
串口设置:
DCB设置:
串口通讯应用程序最重要的方面就是设置设备控制块(DCB)结构。通常在串口通讯应用程序中出错的地方就是没有正确的初始化DCB结构。当串口通讯功能没有按预定的执行,随后的DCB结构检查通常会显示这个问题。
有三种方法去初始化DCB结构,第一种方法就是用GetCommState的功能,这种功能可以
返回当前在通讯端口中使用的DCB。下面的代码显示了怎样使用GetCommState功能DCB dcb = {0};
if (!GetCommState(hComm, &dcb))
// Error getting current DCB settings
else
// DCB is ready for use.
第二种方法就是就是使用功能调用BuildCommDCB。用这种功能填充波特,奇偶校验,结束字节的数目和DCB的数据位成员。它还可以为流控制的成员设置默认值。其它DCB的成员并未受这个功能的影响,程序的任务就是确保其它DCB成员不发生错误。被认为简单的初始化DCB结构的可以置初值0和为其成员设置大小,结构以字节为单位。如果DCB结构的初始值0不存在,那么保存的成员可能有非零值,这将在稍后使用DCB的时候产生一个错误。下面的功能展示了如何恰当使用这种方法:
DCB dcb;

FillMemory(&dcb, sizeof(dcb), 0);
dcb.DCBlength = sizeof(dcb);
if (!BuildCommDCB(”9600,n,8,1″, &dcb)) {
// Couldn’t build the DCB. Usually a problem
// with the communications specification string.
return FALSE;
}
else
// DCB is ready for use.
第三种方法是手动设置,应用程序分配DCB结构并且为每一成员设置需要的值。这种方
法对以后DCB的改变处理的不是很好,在Win32下不推荐使用。
应用程序通常需要设置不同的DCB成员或者在执行过程中修改一些设置,一旦正确的初
始化DCB,初始值的改变就成为可能。DCB结构的改变在端口不会影响任何行为,直到SetC
ommState功能执行。这儿有一段代码用来找回当前DCB,改变波特,并且试图设置配置:

DCB dcb;

FillMemory(&dcb, sizeof(dcb), 0);
if (!GetCommState(hComm, &dcb)) // get current DCB
// Error in GetCommState
return FALSE;

// Update DCB rate.
dcb.BaudRate = CBR_9600 ;

// Set new state.
if (!SetCommState(hComm, &dcb))
// Error in SetCommState. Possibly a problem with the communications
// port handle or a problem with the DCB structure itself.
这里是为每一个DCB成员作出解释,并且知道他们怎样去影响其它的串口通讯功能。
Table 2. The DCB Structure Members DCB的成员结构
Member Description
DCBlength 结构的大小(byte)需要在setcommstate更新确定.
BaudRate 波特在哪些具体通讯装置运转. 这个成员能够实际波特价值,或波特指数.
fBinary 叙述二进位的模态是否被能够。 Win32 API不支援非二进位的模态移动,因此,这一个成员应该是TRUE的。 尝试使用FALSE将无法工作。
fParity 叙述优先级的检查是否能够使用。 如果这一个成员是TRUE, 优先级检查被运行,而且优先级错误被报告。这控制被用于通信的优先级
fOutxCtsFlow 叙述CTS (clear-to-send) 信号是否为输出流量控制所检测。 如果这一个成员是TRUE,而且CTS是低点, 输出被中止,直到CTS再一次是高的。 CTS信号在 DCE(通常一个调制解调器) 的控制之下,DTE(通常个人计算机) 只是检测这信号的状态,DTE 不改变它。
fOutxDsrFlow 叙述 DSR(data-set-ready) 信号是否为输出流量控制所检测。 如果这一个成员是TRUE,而且 DSR 是低点, 输出被中止,直到 DSR 再一次是高的。信号在 DCE 的控制之下; DTE 唯一的监视这一信号。
fDtrControl 叙述 DTR(data-terminal-ready) 输入流量控制。 这一个成员可能是下列值之一:
Value Meaning
DTR_CONTROL_DISABLE EscapeCommFunction.
当被打开的时候,降低 DTR 线压。 申请能调整线的状态有 EscapeCommFunction
DTR_CONTROL_ENABLE EscapeCommFunction.
当被打开的时候,提高 DTR 线压。 申请能调整线的状态有 EscapeCommFunction。
DTR_CONTROL_HANDSHAKE EscapeCommFunction
准许 DTR 流程-控制握手。 如果这值被用, 是错误的应用,调整符合escapecommfunction.
fDsrSensitivity 叙述对 DSR 信号的状态的驱动是否是敏感的。如果这一个值是TRUE,驱动不理睬任何的收到byte信号,除非 DSR 调制解调器输入线是高的。
fTXContinueOnXoff 当输入缓冲已满,而且驱动已经传输XOFF 值的时候,叙述传输是否停止。 如果这一个值是TRUE, 之后继续传输字符。 如果这一个成员是FALSE,传输暂停,直到输入缓冲在XonLim 位元组是空的而且驱动已经传输XON 值。
fOutX 叙述 XON/XOFF 流量控制在传输期间是否可用。如果这一个成员是TRUE, 当那 XOFF 值被收到的时候,传输停止,当 XON 值被收到的时候,再一次开始。
fInX 叙述 XON/XOFF 流量控制是否被在等待期间用。 如果这一个成员是TRUE,XOFF把输入缓冲全部作为XoffLim 位元组,而当XonLim 位元组中输入缓冲是空的时,则为XON。
fErrorChar 叙述以同等错误收到的byte信号是否与被那 ErrorChar 成员指定的值一起代替。 如果这一个成员是TRUE,而且那 fParity 成员是TRUE,替换发生。
fNull 叙述无效位元组是否被丢弃。 如果这一个值是TRUE,无效位元组被收到后丢弃。
fRtsControl 叙述RTS( request-to-send) 输入流量控制。 如果这价值是零,假设值是 RTS_CONTROL_HANDSHAKE 。 这一个成员可能是下列价值之一:

Value Meaning
RTS_CONTROL_DISABLE 当被设置打开的时候,降低RTS线压。 申请能使用 EscapeCommFunction 改变线的状态。
RTS_CONTROL_ENABLE 当被设置打开的时候,提高RTS线压。 申请能使用 EscapeCommFunction 改变线的状态。
RTS_CONTROL_HANDSHAKE 准许RTS流程-控制握手。 驱动提高RTS线压, 促成 DCE 送, 当输入缓冲有充足的房间接受数据的时候。 驾驶员降低即时战略类游戏线, 避免 DCE 送, 当输入缓冲没有充足的房间接受数据的时候。 如果这价值被用, 它是调整线的申请一个错误有 EscapeCommFunction。
RTS_CONTROL_TOGGLE 叙述,如果位元组可用来传输,RTS线压将会是高的。 在所有的被缓冲的位元组已经被送之后,RTS线将会是低点。 如果这值被确定, 这将是一个错误的申请符合escapecommfunction. 这价值在Win 95 被忽略; 它导致驱动行动由RTS_CONTROL_ENABLE 被指定
fAbortOnError 叙述如果一个错误发生读、写操作是否被结束。 如果这一个值是TRUE,如果一个错误发生,驱动用一种错误状态 (ERROR_IO_ABORTED) 结束所有的读、写操作。 驱动将不接受任何的较进一步的通信申请,直到由呼叫 ClearCommError 功能确定错误。
fDummy2 保留字段,不使用
wReserved 不使用;必须置0
XonLim 在那 XON送出之前,叙述被允许输入缓冲的位元组的最小数字。
XoffLim 在那 XOFF被送出之前,叙述被允许输入缓冲的位元组的最大数字。 在位元组中,被允许的位元组的最大数字减去实际大小计算这个值,输入缓冲。
Parity 叙述要用的优先级方案。 这一个成员可能是下列价值之一
Value Meaning
EVENPARITY Even
MARKPARITY Mark
NOPARITY No parity
ODDPARITY Odd
StopBits 叙述要用的停止的数字。 这一个成员可能是下列价值之一:
Value Meaning
ONESTOPBIT 1 stop bit
ONE5STOPBITS 1.5 stop bits
TWOSTOPBITS 2 stop bits
XonChar 为传输和接待确定 XON 的值
XoffChar 为传输和接待确定 XOFF 的值性。
ErrorChar 用于代替收到的位元组错误。
EofChar 用于表示信息结束
EvtChar 设定以触发EV_RXFLAG事件
wReserved1 保留字段

Flow Control 流量控制
流量控制在设备因为繁忙或者其他原因无法通信时提供了一个串行通讯通信中断机制. 传统上有两种流量控制方式:硬件控制和软件控制.
通常的串行通信的问题是写实际上不写数据给装置的操作。时常,当计划没有叙述它的时候,问题在于被用的流量控制。 仔细分析了DCB 结构我们发现一个或者更多个下列成员可能是TRUE: fOutxCtsFlow 、 fOutxDsrFlow 或 fOutX。 另外的一个机制揭露流量控制被能够是呼叫 ClearCommError 而且调查 COMSTAT 结构。当因为流量控制传输被中止的时候,它将会被显示.
在讨论流量控制的类型之前,对有些知识的了解是必要的。 串行通信在二个装置之间发生。 传统地,有一个个人计算机和一个调制解调器或打印机。 个人计算机被分类数据终端机设备 (DTE) 。 DTE 有时叫做主机。 调制解调器,打印机或其他的周边设备被确认为数据通信设备 (DCE) 。 DCE 被有时称为设备。

Hardware flow control 硬件流量控制
硬件流量控制用电压信号控制线控制电缆是否是接收或发送信号。 DTE 和 DCE必须同意使用某类型流量控制通讯协议。 设定 DCB 构成仅仅使流量控制能够配置 DTE。DTE还需要做出一定的远程数据终端配置以确定 DTE 和 DCE 使用相同类型的流量控制。 没有由 Win32 提供的机制设定 DCE 的流量控制。它通常或者指令确立其由接收器接收。下表描述了在 DTE 和 DCE 上使用控制线进行通信的方向和流量控制。

Table 3. Hardware Flow-control Lines
Line and Direction Effect on DTE/DCE
CTS
(Clear To Send)
Output flow control DCE设定线上电压为高显示它能接受数据,线上电压为低时表明它不能够接受数据。
如果 fOutxCtsFlow DCB 的成员是TRUE,那么如果这一条线是低点, DTE 将不送数据。 如果线是高的,它将会重新开始送。
如果 fOutxCtsFlow DCB 的成员是FALSE,那么线的状态不影响传输
DSR
(Data Set Ready)
Output flow control DCE设定线上电压为高显示它能接受数据,线上电压为低时表明它不能够接受数据。
如果 fOutxCtsFlow DCB 的成员是TRUE,那么如果这一条线是低点, DTE 将不送数据。 如果线是高的,它将会重新开始送。
如果 fOutxCtsFlow DCB 的成员是FALSE,那么线的状态不影响传输。
DSR
(Data Set Ready)
Input flow control 如果 DSR 线是低压,然后到达端口的数据被忽略。 如果 DSR 线是高压,到达端口的数据被收到。
这行为发生如果 fDsrSensitivity DCB 的成员被设定成TRUE。 如果它是FALSE,那么线的状态不影响接待。

RTS
(Ready To Send)
Input flow control RTS线被 DTE 控制。
如果 DCB 的成员fRtsControl被设定成TS_CONTROL_HANDSHAKE, 下列流量控制被用: 如果输入缓冲有充足的空间接受数据 (至少,一半的缓冲是空的),DTE将RTS电压置高。 如果输入缓冲有收入数据 (少于 1/4 缓冲是空的) 的一点点空间, DTE将RTS电压置低。
如果 DCB 的成员fRtsControl,被设定成 RTS_CONTROL_TOGGLE 当数据可用来发送的时候,DTE将RTS电压置高。 当没有数据可用来发送的时候,DTE将RTS电压置低。 Windows 95 忽视此项属性而且相同于 RTS_CONTROL_ENABLE 对待它。
如果 DCB 的成员fRtsControl被设定成 RTS_CONTROL_ENABLE 或者 RTS_CONTROL_DISABLE, 当它需要的时候,申请免费改变控制线的状态。 注意,在这情况,线的状态不影响传送。当线电压低下地去的时候, DCE 将会中止传输。 当线电压高上去的时候, DCE 将会重新开始传输。
DTR
(Data Terminal Ready)
Input flow control .
DTR 线被 DTE 控制。
如果 DCB 的成员fDtrControl被设定成 DTR_CONTROL_HANDSHAKE , 下列的流量控制被用: 如果输入缓冲有充足的空间接受数据 (至少,一半的缓冲是空的), DTE将 DTR 线压置高度。 如果输入缓冲有收入数据 (少于 1/4 缓冲是空的) 的一点点空间, DTE将DTR 线压置于低点。如果 DCB 的成员fDtrControl被设定成 DTR_CONTROL_ENABLE 或者 DTR_CONTROL_DISABLE 。 当它需要的时候,申请免费改变线的状态。 在这情况,线的状态不影响传送。
当线压低下地去的时候, DCE 将会中止传输。 当线压高上去的时候, DCE 将会重新开始传输。

.
当 CE_RXOVER 错误发生的时候,对流量控制的需要容易认识。 这一个错误指出缓冲溢出和数据损失。如果数据比它更快速地到达端口,CE_RXOVER 能发生。 增加输入缓冲大小可能降低错误发生概论,但是它不完全地解决问题。 输入流量控制是必需的可以完全减轻这一个问题的方法。当输入缓冲有可得的较多的空间的时候,电压在流量-控制的线上是高电压,而且 DCE 重新开始送数据。
一个相似的错误是 CE_OVERRUN 。 当新的数据以前到达通信硬件,而且串行通信驱动完全地接受旧的数据时候,这一个错误发生。 当传输速度对通信硬件的类型或处理器是太高的时候,这能发生。这也可以发生在操作系统对通信硬件不开放资源时。减轻这一个问题的唯一方法运用一些组合减退传输速度、更换通信硬件、而且增加处理器速度。有时候第三方硬件所驾驶的CPU资源,造成效率不高、发生错误。 流量控制不能够完全地解决引起 CE_OVERRUN 错误的问题, 虽然它也许有用减少错误的频率。
Software flow control 软件流量控制
.
软件流量控制用数据通信流控制传送及接收数据.。由于采用了两个特殊流量控制字, XOFF和XON,不能用软件进行二进制流量控制; 这个控制字可能干扰二进制数据和数据传输,软件流量控制软件适合以文本基础的通信,否则数据中XON、XOFF字符将被破坏.
为了使用流量控制软件,DCB中 的fOutX 能够和 fInX一定要设定成TRUE。那 fOutX控制输出流量控制。fInX控制输入流量控制。
有一点值得注意的是, 该计划允许DCB动态转让属性作为确认流量控制字. DCB的xoffchar
对于被输入的流量控制, XoffLim 在XOFF使用之前, 规定被允许被输入的缓冲的最小自由空间。 如果在未输入缓冲区自由空间低于这一数额,那么XOFF 个性被使用。对于被输入的流量控制, XonLim 在那 XON 使用之前, DCB 的成员规定被允许输入缓冲区最小字节数。如果输入缓冲区数据量低于此值,那么XON被使用。
Table 4 lists the behavior of the DTE when using XOFF/XON flow control.
Table 4. Software flow-control behavior
Flow-control character Behavior
XOFF received by DTE DTE 传输被中止,直到 XON 被收到。 DTE 等待继续。 DCB 的 fOutX 成员控制这行为
XON received by DTE 如果因为早先 XOFF使 DTE 传输中止,DTE 传输被重新开始。 DCB 的 fOutX 成员控制这行为。
XOFF sent from DTE 当缓冲区已满XOFF 自动地被 DTE 送出。其界限被 XoffLim DCB 的成员命令。 DCB 的 fInX 成员控制这行为。 DTE 传输当做在下面描述被 fTXContinueOnXoff DCB 的成员控制。
XON sent from the DTE 当当缓冲区是空的,XON 自动地被 DTE 送出。界限被 XonLim DCB 的成员命令。 DCB 的 fInX 成员控制这行为。

如果软件流量控制能够被输入控制决定, 那么 fTXContinueOnXoff DCB 的成员奏效。在那 XOFF 值自动地被系统派遣之后,那 fTXContinueOnXoff 成员控制传输是否被中止。如果 fTXContinueOnXoff 是TRUE, 那么传输在 XOFF 之后继续被送当那接受缓冲是全部。
无任何机制可在Win32API中导致DTE像这些装置一样 行为. 当结构的成员中没有指明恢复中断传输时收到任何字符.XON的特征是引起传输恢复的唯一的原因。
另一个有关流量控制软件有趣的点是,XON和XOFF的接收特点使得等待阅读操作最终变为0字节被阅读。XON和XOFF在应用中不能被阅读,因为他们并不放在输入缓冲 。
.市场上的许多项目包括和windows一起的终端项目都给用户流量控制的三个选择:硬件,软件或者什么也没有,windows操作系统它自己不能用这种方式限制用硬, DCB 的设定同时考虑到软件和硬件流量控制。,事实上,有可能分别配置DCB中影响流量控制的每个成员、 允许几个不同流量控制配置. 对流量控制选择的限制是为了减少最终用户的混乱因为混乱使用的原因, 对流控选择的限制还可用于通信设备,因为可能不支持所有类型 流量控制.

Communications Time-outs
通信暂停
另一个影响读写操作的重大课题是时间暂停。下面是时间暂停影响了读写操作。如果一个操作比计算的暂停时间要长。这个操作就完成了。没有错误代码出现在READFILE,Writre,Getoverlappedresult,或者WaitForsinggleobject。所有指标用于监测行动表明它成功完成. 唯一知道操作暂停的是,实际上被传输的字节比要求的字节少. 所以,如果readfile显示是真,但较少的字节被读比要求的,操作暂停了。 如果重复写操作暂停了,这个重复的事件被标志和等待单独处理显示WAIT_OBJECT_O. getoverlappedresult显示ture,但dwbytestransferred含有在暂停之前被传输的字节, 以下代码说明如何处理这种重叠的写操作:
BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)
{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
DWORD dwRes;
BOOL fRes;

// Create this write operation’s OVERLAPPED structure hEvent.
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// Error creating overlapped event handle.
return FALSE;

// Issue write
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but it isn’t delayed. Report error.
fRes = FALSE;
}
else
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
// Overlapped event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, FALSE))
fRes = FALSE;
else {
if (dwWritten != dwToWrite) {
// The write operation timed out. I now need to
// decide if I want to abort or retry. If I retry,
// I need to send only the bytes that weren’t sent.
// If I want to abort, I would just set fRes to
// FALSE and return.
fRes = FALSE;
}
else
// Write operation completed successfully.
fRes = TRUE;
}
break;

default:
// An error has occurred in WaitForSingleObject. This usually
// indicates a problem with the overlapped event handle.
fRes = FALSE;
break;
}
}
}
else {
// WriteFile completed immediately.

if (dwWritten != dwToWrite) {
// The write operation timed out. I now need to
// decide if I want to abort or retry. If I retry,
// I need to send only the bytes that weren’t sent.
// If I want to abort, then I would just set fRes to
// FALSE and return.
fRes = FALSE;
}
else
fRes = TRUE;
}

CloseHandle(osWrite.hEvent);
return fRes;
}

SetCommTimeouts 功能为一个口界定了通信暂停。 为了要为一个口取回当前的暂停,一个叫 GetCommTimeouts项目。即一个在修正他们之前取回通信暂停的申请。当它用完口的时候,这让申请把暂停设定回到他们的最初设定。 下列各项是使用 SetCommTimeouts 设定新的暂停的例子::

COMMTIMEOUTS timeouts;

timeouts.ReadIntervalTimeout = 20;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.ReadTotalTimeoutConstant = 100;
timeouts.WriteTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 100;

if (!SetCommTimeouts(hComm, &timeouts))
// Error setting time-outs.

注意 再一次,通信暂停与在同步功能中被供应的暂停价值不同。 WaitForSingleObject ,举例来说,使用暂停价值等候一个物体变成发送讯号; 这与一个通信暂停不同。

设定 COMMTIMEOUTS 的成员对所有的零导致没有导致暂停发生。 非重叠操作将会阻塞到所有的被请求的位元组被转移。 ReadFile 功能被阻塞,直到所有的被请求的个性到达港口。 WriteFile 功能被阻塞,直到所有的被请求的个性被送出。 另一方面,被重叠的操作将不直到所有的个性完成被转移或者操作被失败。直到发生以下条件操作完成:

如果一个同步暂停被供应, WaitForSingleObject 总是返回 WAIT_TIMEOUT 。 如果一个无穷的同步暂停被用, WaitForSingleObject 将会永远地阻塞。
GetOverlappedResult 总是返还FALSE的而且如果对 GetOverlappedResult 在呼叫之后再次直接地呼叫, GetLastError 归还 ERROR_IO_INCOMPLETE。
设定 COMMTIMEOUTS 的成员以下列的样子,读操作将没有任何的等候,新数据立即到达

COMMTIMEOUTS timeouts;

timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;

if (!SetCommTimeouts(hComm, &timeouts))
// Error setting time-outs.
这些场合都需要使用同一个事件时的经过叙述了”终止较早的片段?. 为了使readfile返回0字节, eoutcommt成员之一的ReadIntervalTimeout结构势将设置为MAXDWORD, 而readtimeoutmultiplier和readtimeoutconstant都定为零.
当它使用一个端口通信的时候,申请必须总是明确地设定通信端口。 读操作和写操作被通信暂停影响。 当一个端口是最初开着的时候,它假设假设值暂停通信或使用以前设定的通信应用。如果一个申请暂停被设定一个特定的方法,当暂停是实际上不同的时候, 然后读和写操作可能不再结束或者经常中断。

结论
当发展一个连续的通信申请的时候,这篇文章作为讨论一些共同的陷阱. 面对着多线程的通信,这篇文章旨在利用许多技巧。
讨论. 下载和试用这个系统. 学习如何将提供作品的透彻了解Win32的串行通信功能.

目录
Brain, Marshall. Win32 System Services: The Heart of Windows NT. Englewood Cliffs, NJ: Prentice Hall, 1994.
Campbell, Joe. C Programmer’s Guide to Serial Communications. 2d ed. Indianapolis, IN: Howard W. Sams & Company, 1994.
Mirho, Charles, and Andy Terrice. “Create Communications Programs for Windows 95 with the Win32 Comm API.?Microsoft Systems Journal 12 (December 1994). (MSDN Library, Books and Periodicals)

©2000微软公司. 版权所有.

http://www.cnblogs.com/jxnclyk/archive/2010/06/10/1755315.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值