Win32 串口编程(一)

http://blog.sina.com.cn/s/blog_56dee71a0100ex0l.html

翻译自:ms-help://MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.WIN32COM.v10.en/dnfiles/html/msdn_serial.htm

老外写的文章,虽比较全面,但很啰嗦,不如看各个函数的文档来得快。为方便以后查阅,列出本文涉及的主要函数如下:

CreateFile、ReadFile、WriteFile、GetOverlappedResult、WaitForSingleObject

SetCommMask、WaitCommEvent

ClearCommError、GetCommModemStatus、EscapeCommFunction

GetCommState、BuildCommDCB、SetCommState、SetCommTimeouts

此外,新浪的博客系统限制文章最大长度为40000字节,只好把这篇文章分成几部分了。

0 简介

本文仅关注在Windows NT和95间兼容的API。Windows 95支持Telephony API(TAPI),但Windows NT 3.x不支持TAPI,所以本文不讨论它。

本文的示例程序MTTTY(Multithreaded TTY)使用了三个线程:一个进行内存管理的界面线程;控制所有写入操作的写入者线程;读取数据和处理端口状态改变的读取/状态线程。示例采用了一些不同的堆来进行内存管理;还大量使用了同步方法来进行线程间通信。

1 打开端口

使用CreateFile函数打开端口。打开端口时有两种方法:重叠的和非重叠的。下列代码片段以重叠方式打开端口:

 

HANDLE hComm;
hComm = CreateFile( gszPort,  
                    GENERIC_READ | GENERIC_WRITE, 
                    0, 
                    0, 
                    OPEN_EXISTING,
                    FILE_FLAG_OVERLAPPED,
                    0);
if (hComm == INVALID_HANDLE_VALUE)
   // error opening port; abort

 

 

 

 

 

 







去掉参数中的FILE_FLAG_OVERLAPPED就是非重叠操作方式了。
用CreateFile打开通信端口时,有下列限制:

  • fdwShareMode 必须是0。通信端口不能像文件那样被共享。要共享通信端口,需要使用句柄继承或者复制操作。
  • fdwCreate 必须指定 OPEN_EXISTING 标志。
  • hTemplateFile 参数必须是 NULL

端口名通常是COM1、COM2、COM3和COM4。Win32 API不提供确定系统中有哪些端口可用的机制。Windows NT和Windows 95跟踪系统已安装端口的方法是不同的,所以不太可能提供兼容的确定可用端口的方法。某些系统可能有多于4个端口,而传统的通信端口最大个数是4。硬件厂商和串口驱动编写者可以自由地为端口命名。所以,程序最好可以让用户指定要使用的端口名字。如果端口不存在,则试图打开端口时会返回ERROR_FILE_NOT_FOUND错误,这时应该提示用户端口不可用。

2 读写操作

通信端口的读写操作与文件I/O操作非常相似,它们使用同样的函数。Win32的I/O操作可分为两种:重叠(overlapped)的和非重叠的(nonoverlapped)。平台SDK文档分别使用异步(asynchronous)和同步(synchronous)来表示这两种I/O方式。

很多开发者都熟悉非重叠I/O,因为它就是传统的I/O方式:函数返回时,所请求的操作已经完成。然而在重叠I/O的情况下,系统则可能在操作还没有完成的情形下立即返回,随后才通知调用者操作完成。程序可以在发起I/O请求和请求被完成之间进行一些后台工作。

2.1 非重叠I/O

非重叠I/O的工作方式很简单:I/O操作进行时,调用线程被阻塞;操作完成后,函数返回,调用线程可以继续执行。在多线程应用中,这种I/O方式很有用:一个线程阻塞在某I/O操作上时,其他线程可以继续工作。应用程序应该保证对端口的串行访问。某个线程阻塞在等待某I/O操作上时,其他线程后续的通信API调用也都将阻塞。比如说,一个线程在等待ReadFile调用返回时,另一个线程的WriteFile函数调用将阻塞。

在选择使用非重叠还是重叠方式时,可移植性是要考虑的因素之一。有时候重叠操作并不是好的选择,因为很多操作系统不支持它;然而很多操作系统都支持某种形式的多线程。所以从兼容性方面考虑,多线程非重叠I/O可能是最好的选择。

2.2 重叠I/O

重叠I/O不像非重叠I/O那样简单易懂,但却灵活高效。使用重叠方式打开的端口允许多个线程同时进行I/O操作,并且在操作进行期间可以进行其他的工作。此外,重叠操作的行为方式还允许单个线程提交多个不同的请求,然后在操作进行期间进行其他后台工作。

在单线程和多线程应用中,都必须在提交I/O请求和处理操作结果间进行一些同步操作。线程可能需要在操作结果可用前阻塞;当然也可以进行其他工作。如果没有其他需要进行的工作,则重叠I/O的优点是更好的用户响应性能。

MTTTY使用了重叠I/O。它创建用于读取数据和监测端口状态的线程,并且还定时进行一些后台工作;此外它还另外创建一个线程用于写入数据。

重叠I/O操作分为两个部分:创建I/O操作和检测操作完成。创建I/O操作涉及到建立OVERLAPPED结构体、创建用于同步的手动复位事件、调用恰当的函数(ReadFile或者WriteFile)。I/O操作可能立即完成,也可能不能立即完成,不能认为一个重叠I/O操作请求总是生成一个重叠操作。如果操作立即完成,程序应该可以继续进行通常的处理。检测操作完成涉及到等待事件句柄、检查操作完成结果、处理数据。与重叠I/O相关的工作更多的原因是有更多的失败点。非重叠操作中,简单地通过函数返回值判断操作是否失败;而重叠操作中,则可能在创建操作请求时失败,或者操作阻塞期间失败,也可能是操作超时,或者是等待操作完成信号超时。

2.2.1 读操作

下面的代码片段展示了提交重叠的读操作请求的方法。注意,如果ReadFile返回TRUE,调用了一个函数处理数据。代码定义了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结构体的事件句柄被传递给WaitForSingleObject以等待事件授信,操作完成。注意,事件受信表示操作完成,而不是操作成功完成。应该用GetOverlappedResult来取得操作结果,它返回TRUE表示操作成功完成;FALSE表示有错误发生,用GetLastError可以取得具体的错误码。也可以用GetOverlappedResult来检测操作完成:GetOverlappedResult返回FALSE,GetLastError返回ERROR_IO_INCOMPLETE表示操作进行中。如果对bWait参数传入TRUE,则效果就是重叠操作变成了非重叠的,直到操作完成,函数才返回。下面的代码片段展示了一种检测重叠读取操作完成的方法。注意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;
   }
}

 

 

2.2.2 写入操作

写入操作跟读取操作非常相似。下面的代码片段展示了如何提交写入操作,并等待操作完成。

 

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返回。有三种方法可以克服此问题:

  • 把代码放在单独的线程中。这样写入线程在等待写操作完成时,其他线程可以进行任何所需的操作。MTTTY就是这么做的。
  • 使用COMMTIMEOUTS使得写操作在经过一个超时值指定的时间后完成。本文后面的“通信超时”节将详细讨论它。MTTTY也可以使用这种方法。
  • 修改WaitForSingleObject调用,使用超时值。这样会更麻烦:如果原来的操作仍在进行中,程序提交另一个操作请求,则需要分配新的OVERLAPPED结构和重叠事件。这种记录跟踪保持是很困难的,尤其是与“工作队列”相比较时。MTTTY使用了工作队列。

上面代码中的WaitForSingleObject使用了INFINITE作为超时值,其效果等同于使用TRUE作为GetOverlappedResult的fWait参数。下面是等效的更简洁的代码:

 

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并不总是等待重叠操作完成的最好方法。比如说,如果应用需要同时等待另一个事件句柄,则第一个代码片段模型比第二个更好,因为可以很容易地用WaitForMultipleObjects替换WaitForSingleObject,来等待更多的句柄。MTTTY就是这么做的。

在前一个重叠操作完成前重用OVERLAPPED结构是重叠I/O编程中常现的一个错误。如果要在前一个重叠操作完成前提交新的重叠操作请求,则需要分配新的OVERLAPPED结构,其hEvent字段也应该包含新的手动复位事件句柄。只有在重叠操作完成后,OVERLAPPED结构和其事件句柄才可以被重用。

串口通信中使用OVERLAPPED结构时只需要修改hEvent字段,其他字段只需要初始化为零。

你可能感兴趣的:(Win32 串口编程(一))