串口异步读写

 

串口异步读写


最近尝试写了一个串口读写的程序,学习并复习了一些知识点。本文首先讲异步读写注意点,然后讲串口的注意点。因为有些问题没有深入研究下去,所以本文也仅仅当做一个笔记。

 

1.      文件指针

我们使用ReadFile和WriteFile来进行读写,这两个API是用来读写文件的,在同步读写中,有一个文件指针的概念,但是在异步读写中,系统会忽略文件指针。

OVERLAPPED的结构如下:

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  DWORD Offset;
  DWORD OffsetHigh;
  HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

 

上述结构的5个成员,其中三个成员Offset、OffsetHigh和hEvent必须在调用ReadFile和WriteFile之前初始化,hEvent是一个通知事件,而Offset和OffsetHigh就是用来代替同步读写中文件指针的——用来指定本次读写的开始的位置。

 

非文件设备会忽略Offset和OffsetHigh——我们必须将这两个成员都初始化为0,否则I/O请求会失败,这时调用GetLastError会返回ERROR_INVALID_PARAMETER。

 

Internal和InternalHigh原本设计时是内部使用的,但是后来用途变了:Internal用作返回状态值;InternalHigh返回读取字符数。这些在后面有进一步说明。

 

2.      异步读写中实际读写的字符数

以ReadFile为例,函数定义如下:

BOOL WINAPI ReadFile(
  _In_   HANDLE hFile,
  _Out_  LPVOID lpBuffer,
  _In_  DWORD nNumberOfBytesToRead,
  _Out_opt_ LPDWORD lpNumberOfBytesRead,
  _Inout_opt_  LPOVERLAPPED lpOverlapped
);

 

 

在同步读时,lpNumberOfBytesRead返回实际读取的数据长度,但是在异步读时,lpOverlapped不为NULL,此时lpNumberOfBytesRead可以设为NULL值。

 

ReadFile sets this value to zero before doing any work or error checking. Use NULL for this parameter if this is an asynchronous operation to avoid potentially erroneous results.

 

返回数据的长度在lpOverlapped->InternalHigh中。

 

3.      触发事件内核对象

异步读写时,ReadFile和WriteFile中必须设置lpOverlapped值(不能置为NULL)。但是可以将lpOverlapped->hEvent置为NULL。当异步读写完成时,Windows系统会触发文件内核对象hFile,如果lpOverlapped->hEvent不是NULL值,Windows也会触发该事件。

 

因为hFile可以被多个异步读写同时共享,所以如果在hFile上的等待可能会被误触发(其他读写完成了,系统触发了hFile)。当只有一个读或者写时,可以使用hFile等待。其它情况应该使用lpOverlapped->hEvent事件等待。

 

但是有一点要明确,Windows会触发hFile和lpOverlapped->hEvent(如果有的话)。

 

4.      lpOverlapped

当这个OVERLAPPED结构的地址通过ReadFile/WriteFile传给系统后,系统会一直使用这个数据块,直到读写完成。

完成时,系统会将读写数据长度放在InternalHigh中,将返回状态值放在Internal中。所以千万不要在读写结束前将这个OVERLAPPED内存块销毁或者挪作他用。

 

5.      GetOverlappedResult

这个函数原型如下:

BOOL WINAPI GetOverlappedResult(
  _In_   HANDLE hFile,
  _In_   LPOVERLAPPED lpOverlapped,
  _Out_  LPDWORD lpNumberOfBytesTransferred,
  _In_   BOOL bWait
);

 

 

这个函数的源代码可以看我以前的文章。从源码看以看出这个如下几点:

1) hFile不一定是必须的。只有在bWait为TRUE且lpOverlapped->hEvent为0时才会使用hFile.

2) lpOverlapped就是前面调用ReadFile、WriteFile时所使用的地址值,不能是其他的。

3) 这个函数的功能其实很简单:将lpOverlapped->InternalHigh赋给lpNumberOfByesTransferred;将lpOverlapped->Internal赋给返回值。

 

所以,当读写完成时,这个函数基本没有什么用。也因此,《Windows核心编程》基本就不用这个函数了。

HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
BYTE bBuffer[100];
OVERLAPPED o = { 0 };
o.Offset = 345;
 
BOOL bReadDone = ReadFile(hFile, bBuffer, 100, NULL, &o);
DWORD dwError = GetLastError();
 
if (!bReadDone && (dwError == ERROR_IO_PENDING)) {
   // 异步I/O,等待返回
   WaitForSingleObject(hFile, INFINITE);//有时此处也会发生错误。
   bReadDone = TRUE;
}
//返回了,不管是同步也是异步。
if (bReadDone) {
   // o.Internal包含错误值
   // o.InternalHigh包含传输字节数
   // bBuffer包含读取的数据
} else {
   // 发生错误,见dwError
}

 

 

6.      串口通讯

 

应用程序实际上是直接和驱动程序打交道的。所以虽然是串口I/O操作,很多函数都是很快返回的。这可能有一定的好处,但是也引发一些不利的地方,比如不能了解PC串口上是否真正连接到设备上。就是说对一个空的COM口,大部分COMM函数都能成功返回的。

 

7.      CreateFile打开串口设备

CreateFile函数原型如下:

HANDLE WINAPI CreateFile(

  _In_      LPCTSTR lpFileName,

  _In_      DWORD dwDesiredAccess,

  _In_      DWORD dwShareMode,

  _In_opt_  LPSECURITY_ATTRIBUTES lpSecurityAttributes,

  _In_      DWORD dwCreationDisposition,

  _In_      DWORD dwFlagsAndAttributes,

  _In_opt_  HANDLE hTemplateFile

);

lpFileName是指定要打开设备的名称。COM1至COM9口可以直接lpFileName指定为“COM1”..“COM9”。但是超过COM10这种方法就没有用了,必须使用“\\.\COM10”了。所以为了统一可以都使用“\\.\COMx”方式来指定名称。

 

8.      SetCommTimeouts

串口通讯中使用该函数来设置超时(GetCommTimeouts用来获取超时设置)。超时设置会影响并决定何时完成读写(返回ReadFile/WriteFile或者WaitFor…)。

 

函数设置了一个COMMTIMEOUTS结构:

typedef struct _COMMTIMEOUTS {
  DWORD ReadIntervalTimeout;
  DWORD ReadTotalTimeoutMultiplier;
  DWORD ReadTotalTimeoutConstant;
  DWORD WriteTotalTimeoutMultiplier;
  DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS, *LPCOMMTIMEOUTS;

 

设置了超时后,只要满足条件,ReadFile/WriteFile都会正常返回(hEvent和hFile被触发)。

 

在WaitForSingleObject中:

DWORD WINAPI WaitForSingleObject(
  _In_  HANDLE hEvent,
  _In_  DWORD dwMilliseconds
);

 

 

这里也有一个dwMilliseconds超时值,这和SetCommTimeouts设置的超时是不同的:

1)  CommTimeouts超时,hHandle会被触发。WaitForSingleObject返回WAIT_OBJECT_0ol.InternalHigh返回读写数据字节数。

2) dwMilliseconds超时,WaitForSingleObject返回WAIT_TIMEOUT

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值