写串口程序时waitcommevent或writefile导致死锁问题

http://blog.csdn.net/luckly_wang/article/details/6237225

 

 

WinXP与WinCE串口的运行机制之比较
//========================================================================
//TITLE:
//    WinXP与WinCE串口的运行机制之比较
//AUTHOR:
//    norains
//DATE:
//    Saturday  11-November-2006
//Passed Environment:
//    PC:WinXP+VC6.0
//    CE:WinCE4.2+EVC4.0
//========================================================================
    查看微软相关的串口通信文档,可以发现在桌面操作系统中,串口通信分为两种模式:同步和异步.而WinCE只有一种,但文档中却没标明归属哪种模式.实际上,WinCE的串口通信模式更像介于同步和异步之间.
    在此先简要地介绍何为同步和异步.所谓的同步,指得是对同一个设备或文件(在文中只的是串口COM1)的读或写操作必须要等待上一个操作完成才能进行.比如说,调用ReadFile()函数读取串口,但由于上一个WriteFile()操作没完成,ReadFile()的操作就被阻塞,直到WriteFile()完成后才能运行.而异步,则无论上一个操作是否完成,都会执行目前调用的操作.还是拿前面举的例子,在异步模式下,即使WriteFile()没有执行完成,ReadFile()也会立刻执行.
   
   
1.CreateFile()参数的差异
    首先说明一下WinCE和WinXP打开串口时参数的差异.以打开串口COM1为例子,WinCE下的名字为"COM1:",而WinXP为"COM1",两者的唯一区别仅仅在于WinCE下多个分号.
    例如:
    HANDLE hd = CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinCE
    HANDLE hd = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinXP
    在这稍微多说一下,在默认的环境下,TEXT宏在WinCE下会编译为双字节,而WinXP为单字节.换句话说,TEXT("COM1")在WinCE下相当于L"COM1",而WinXP则为"COM1".
   
   
2.单线程比较
   还是用代码做例子来说明比较形象.这是一段单线程的代码,先对串口进行写操作,然后再读.对于WinXP来说,这是同步模式.(与主题无关的代码省略)  
   int WINAPI WinMain( HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPTSTR    lpCmdLine,
     int       nCmdShow)
   {
     ...     
      HANDLE hCom = CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinCE
      //HANDLE Com = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinXP
     
      ...
      DWORD dwBytes;
      if(WriteFile(hCom,TEXT("COM1:"),5,&dwBytes,NULL) == FALSE) //WinCE
      //if(WriteFile(hCom,TEXT("COM1"),5,&dwBytes,NULL) == FALSE) //WinXP
      {
        return 0x05;
      }
     
      ...
      DWORD dwRead;
      char szBuf[MAX_READ_BUFFER];
      if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,NULL) == FALSE)
      {
        return 0x10;
      }
     
      ...
   }
    经过实验,可以发现这段代码在WinCE和WinXP下都能正常工作,并且其表现也相同,都是在WriteFile()函数返回后才执行ReadFile().
    由于异步模式在单线程中也能正常运作,唯一的不同只是在执行WriteFile()时可能也会执行ReadFile()(依WriteFile()函数执行的时间而定),所在此不表.
  
3.多线程比较
    单线程两者表现相同,那多线程呢?下面这段代码采用多线程,先是创建一个读的线程,用来监控串口是否有新数据到达,然后在主线程中对串口写出数据.
    这里假设是这么一个情况,有两台设备,分别为A和B,下面的代码运行于设备A,设备B仅仅只是应答而已.换句话说,只有A向B发送数据,B才会返回应答信号.
    //主线程
    int WINAPI WinMain( HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPTSTR    lpCmdLine,
     int       nCmdShow)
    {
      ...
      CreateThread(NULL,0,ReadThread,0,0,&dwThrdID); //创建一个读的线程.
   
      ...     
      HANDLE hCom = CreateFile(TEXT("COM1:"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinCE
      //HANDLE Com = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL); //WinXP
     
      ...
      DWORD dwBytes;
      if(WriteFile(hCom,"AT/r/n",4,&dwBytes,NULL) == FALSE) //WinCE
      //if(WriteFile(hCom,"AT/r/n",5,&dwBytes,NULL) == FALSE) //WinXP
      {
        return 0x05;
      }
     
      ...
   
    }
   
    //读线程
    DWORD WINAPI ReadThread()
    {
      ...
      SetCommMask(hCom),EV_RXCHAR);
      DWORD dwCommStatus = 0;
    if(WaitCommEvent(hCom),&dwCommStatus,NULL) == FALSE)
    {
     //Clear the error flag
     DWORD dwErrors;
     COMSTAT comStat;
     memset(&comStat,0,sizeof(comStat));
     ClearCommError(hCom,&dwErrors,&comStat);
     return 0x15;
    }
   
    ...
    char szBuf[MAX_READ_BUFFER]={0};
    DWORD dwRead;
    if(ReadFile(hCom),szBuf,MAX_READ_BUFFER,&dwRead,NULL) == FALSE || dwRead == 0)
   {
    return 0x20;
   }
  
   ...
    }
   
    这段代码在WinCE下运行完全正常,读线程在监听收到的数据的同时,主线程顺利地往外发数据.
    然而同样的代码,在WinXP下则根本无法完成工作.运行此代码,我们将发现CPU的占用率高达99%.通过单步调试,发现两个线程分别卡在WaitCommEvent()和WriteFile()函数中.因为根据同步模式的定义,当前对设备的操作必须要等待上一个操作完毕方可执行.在以上代码中,因为设备B没接到设备A的命令而不会向设备A发送应答,故WaitCommEvent()函数因为没有检测到接受数据而一直在占用串口;而WaitCommEvent()一直占据串口使得WriteFile()没有得到串口资源而处于阻塞状态,这就造成了死锁.
    而这种情况没有在WinCE上出现,只要WaitCommEvent()和WriteFile()不在同一个线程,就可以正常工作.这应该和系统的调度方式有关.
   
    如果要在PC上同时进行WaitCommEvent()和WriteFile()操作,需要把串口的模式改写为异步模式.
    更改后的代码如下:
   
    //主线程
    int WINAPI WinMain( HINSTANCE hInstance,
     HINSTANCE hPrevInstance,
     LPTSTR    lpCmdLine,
     int       nCmdShow)
    {
      ...
      CreateThread(NULL,0,ReadThread,0,0,&dwThrdID); //创建一个读的线程.
   
      ...     
      HANDLE Com = CreateFile(TEXT("COM1"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,FILE_FLAG_OVERLAPPED);
     
      ...
      OVERLAPPED olWrite;
     memset(&olWrite,0,sizeof(m_olWrite));
     olWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
 
      DWORD dwBytes; 
     if(WriteFile(hCom,"AT/r/n",4,&dwBytes,&olWrite) == FALSE)
     {
      if(GetLastError() != ERROR_IO_PENDING)
      {
       return 0x20;
      }
     }
     if(GetOverlappedResult(hCom,&olWrite,&dwBytes,TRUE) == FALSE)
     {
      return 0x25;
     }
      ...
   
    }
   
    //读线程
    DWORD WINAPI ReadThread()
    {
      ...    
     memset(&olWaite,0,sizeof(olWaite)); 
     olWaite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);     
      SetCommMask(hCom),EV_RXCHAR);
      DWORD dwCommStatus = 0;
    WaitCommEvent(hCom,&dwCommStatus,olWaite);
    DWORD dwByte; //norains:It is only suitable for the GetOverlappedResult(),not undefined here.
    if(GetOverlappedResult(hCom,olWaite,&dwByte,TRUE) == FALSE)
    {
     if(GetLastError() != ERROR_IO_PENDING)
     {
      return 0x30;
     }
     //Clear the error flag
     DWORD dwErrors;
     COMSTAT comStat;
     memset(&comStat,0,sizeof(comStat));
     ClearCommError(hCom,&dwErrors,&comStat);
     return 0x35;
    }
   
    ...
    memset(&olRead,0,sizeof(olRead));
    olRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    char szBuf[MAX_READ_BUFFER]={0};
    DWORD dwRead;
    if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead) ==FALSE)
    {
     if(GetLastError() != ERROR_IO_PENDING)
     {
      return 0x40;
     }
     if(GetOverlappedResult(hCom,olRead,&dwRead,TRUE) == FALSE)
     {
      return 0x45;
     }
     if(dwRead == 0)
     {
      return 0x50;
     }
    }
  
   ...
    }
   
    测试经过更改后的代码,可以发现在WinXP下终于可以同时调用WaitCommEvent()和WriteFile()而不造成死锁.
    在这里可以发现WinCE和WinXP的串口调度的差异性:单线程中,WinCE的串口工作方式和WinXP串口的同步工作模式相符;而多线程中,WinCE串口工作方式却又和WinXP的异步方式吻合.虽然无法确切比较WinCE的单一串口模式是否比WinXP的双模式更为优越,但可以确认的是,WinCE的这种串口调用方式给程序员带来了极大的便利.
   
   
4.WinXP异步模式两种判断操作是否成功的方法
    因为在WinXP的异步模式中,WriteFile(),ReadFile()和WaitCommEvent()大部分情况下都是未操作完毕就返回,所以不能简单地判断返回值是否为TRUE或FALSE来判断.
    以ReadFile()函数做例子.
    一种是上文所用的方法:
    if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead) ==FALSE)
    {
     if(GetLastError() != ERROR_IO_PENDING)
     {
      return 0x40;
     }
     if(GetOverlappedResult(hCom,olRead,&dwRead,TRUE) == FALSE)
     {
      return 0x45;
     }
     if(dwRead == 0)
     {
      return 0x50;
     }
    }
    如果ReadFile()返回为TRUE,则表明读文件已经完成.但这种情况几乎不会出现,因为对外设的读写相对于内存的读写来说非常慢,所以一般在ReadFile()函数还没执行完毕,程序已经执行到下一个语句.
    当ReadFile()返回为FALSE时,需要采用GetLastError()函数判断读操作是否在后台进行.如果在后台进行,则调用GetOverlappedResult()函数获取ReadFile()函数的结果.在这里要注意的是,GetOverlappedResult()函数的最后一个参数必须设置为TRUE,表明要等ReadFile()函数在后台运行完毕才返回.如果最后一个参数设置为FALSE,则即使ReadFile()还在后台执行,GetOverlappedResult()函数也会立刻返回,从而造成判断错误.
   
    另一种是调用WaitForSingleObject函数达到此目的:
    if(ReadFile(hCom,szBuf,MAX_READ_BUFFER,&dwRead,olRead) ==FALSE)
    {
     if(GetLastError() != ERROR_IO_PENDING)
     {
      return 0x40;
     }
     if(WaitForSingleObject(olRead.hEvent,INFINITE) != WAIT_OBJECT_0)
    {
     return 0x55;
    }
     if(GetOverlappedResult(hCom,olRead,&dwRead,FALSE) == FALSE)
     {
      return 0x45;
     }
     if(dwRead == 0)
     {
      return 0x50;
     }
    }
    因为ReadFile()在后台执行完毕以后,会发送一个event,所以在这里可以调用WaitForSingleObject()等待ReadFile()执行完毕,然后再调用GetOverlappedResult()获取ReadFile()的最终结果.在这里需要注意的是,GetOverlappedResult()的最后一个参数一定要设置为FALSE,因为WaitForSingleObject()已经捕获了ReadFile()发出的event,再也没有event传递到GetOverlappedResult()函数.如果此时GetOverlappedResult()最后一个参数设置为TRUE,则线程会一直停留在GetOverlappedResult()函数而不往下执行.
--------------------------------------------------------------------------------
eVC中串口编程思路和VC大致相同,但是有几点要注意:
1) Windows CE是Unicode编码,读取字符时候,要注意字节数的确定。
2) eVC不支持重叠I/O,所有的函数中与OVERLAPPED结构有关的参数都必须置为 NULL。
3) eVC不支持BuildCommDCB(),GetOverlappedResult()。
4) eVC中串口的写法和一般VC中的写法不同,如串口1,要写为“COM1:”而不能写为“COM1”。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
书名:《Visual Basic.NET自动化系统监控--RS-232串行通信》(清华大学出版社.范逸之.廖锦棋)。PDF格式扫描版,全书共分为9章,共475页。 介绍 Visual Basic .NET为广大Visual Basic用户打开了通往程序设计殿堂的大门。本书具体内容包括:串行通信的相关硬件概念、Visual Basic .NET的常用组件、Win32 API的运用、串行通信的传输方式、串行通信的类别、串行数据的处理方式、仪器设备上的串行通信、分布式的串行通信。本书不仅介绍了串行通信的概念,还详细解析了在窗口环境中设计通信程序的过程,并给出一些技巧与注意事项。 本书可供自动控制和通信领域的开发人员和其他相关技术人员使用或参考。 目录 第1章 基本概念 1 1.1 通信 1 1.1.1 数据传送 1 1.1.2 通信的种类 2 1.2 串行通信 3 1.2.1 RS-232串行通信 3 1.2.2 RS-485串行通信 5 1.2.3 USB接口 7 1.2.4 IEEE 1394 9 1.3 串行通信端口 9 1.3.1 信号定义 10 1.3.2 ASCII码对照表 11 1.3.3 针脚意义及方向 12 1.3.4 通信参数 16 1.4 模式及流量 19 1.4.1 工作模式 19 1.4.2 硬件握手 20 1.4.3 软件握手 22 1.5 接线和错误预防 24 1.5.1 接线方法 24 1.5.2 错误预防 25 1.5.3 CRC程序解析 27 常识问答 28 本章习题 28 第2章 Visual Basic .NET及 常用组件简介 29 2.1 Windows程序概念简述 29 2.1.1 对象的概念 29 2.1.2 界面 31 2.1.3 使用Visual Basic .NET 开发系统 33 2.1.4 Visual Basic .NET的 环境模式 36 2.1.5 项目开发的步骤 37 2.1.6 Visual Basic .NET与 操作系统的关系 38 2.2 常用组件介绍 39 2.2.1 Label组件 39 2.2.2 Button组件 40 2.2.3 Timer组件 42 2.2.4 PictureBox组件 43 2.2.5 RadioButton组件 43 2.2.6 GroupBox组件 44 2.2.7 ListBox组件 45 2.2.8 TextBox组件 45 2.3 程序简述 46 2.3.1 解决方案的组成 46 2.3.2 数据与运算符 48 2.3.3 命名空间 51 2.3.4 语法 52 2.3.5 基础的信息对话框 55 2.3.6 字符串类型及其处理函数 59 2.4 事件处理 66 2.4.1 事件的种类 66 2.4.2 事件中的程序代码 69 常识问答 71 本章习题 71 第3章 串行通信程序及API 72 3.1 引用Windows API 72 3.1.1 程序与硬件 73 3.1.2 Declare声明语句 73 3.1.3 DllImport声明方式 76 3.1.4 常数声明 77 3.1.5 枚举声明 77 3.1.6 结构声明 79 3.2 串行通信的Windows API简述 80 3.2.1 串行通信相关函数 80 3.2.2 CreateFile/CloseHandle (打开/关闭通信端口) 81 3.2.3 GetCommState (取得通信端口参数) 82 3.2.4 SetCommState (设置通信端口参数) 84 3.2.5 WriteFile (输出数据至通信端口) 85 3.2.6 ReadFile (自通信端口读取数据) 85 3.2.7 ClearCommError (清除通信端口错误状况) 85 3.2.8 PurgeComm(清除通信端口) 87 3.2.9 EscapeCommFunction (要求特定控制工作) 87 3.2.10 SetCommMask(信息屏蔽) 88 3.2.11 WaitCommEvent (检测事件是否已发生) 89 3.2.12 GetCommModemStatus (电位状态检测) 89 3.2.13 使用流程 90 3.2.14 检查资源设置 90 3.3 通信测试 92 3.3.1 通信步骤 92 3.3.2 回路测试 92 3.3.3 串行端口的数字 输出控制 113 3.3.4 串行端口的数字 输入检测 120 3.4 自动与事件 126 3.4.1 自动读取传入的字符串 126 3.4.2 通信事件 132 3.4.3 创建多线程 140 3.4.4 定器与DoEvents() 151 常识问答 154 本章习题 154 第4章 串行通信中的字符与字节 155 4.1 字符与字节 155 4.1.1 字符和字节的差别 155 4.1.2 Visual Basic .NET中的 字符串类型 156 4.1.3 中英文字符串长度计算 158 4.1.4 字符编码内容 163 4.2 字节数据的送收 168 4.2.1 字节类型、声明与送收 168 4.2.2 动态数组 176 常识问答 182 本章习题 182 第5章 串行通信类的创建及使用 183 5.1 类的基础 183 5.1.1 类的组成 183 5.1.2 类成员 185 5.1.3 类创建的步骤 188 5.2 通信类的创建 190 5.2.1 类分析 190 5.2.2 枚举值、结构、常数、 Win32 API 的声明 191 5.2.3 属性创建 197 5.2.4 方法的考虑 206 5.2.5 事件的创建 209 5.2.6 整合类 213 5.3 使用串行通信类 232 5.3.1 类测试——数据收发 232 5.3.2 类测试——数字输出 237 5.3.3 类测试——数字输入 242 5.3.4 类事件测试——自动 读取数据 246 5.4 类库 250 5.4.1 类库项目的创建 250 5.4.2 类库的程序开发 252 5.4.3 类库的生成 254 5.4.4 类库的使用 254 常识问答 259 本章习题 260 第6章 串行数据的处理 261 6.1 命令字符串 261 6.1.1 沟通方式 261 6.1.2 CheckSum的使用 264 6.1.3 CheckSum的讨论 271 6.2 PS232实验仪器简介 275 6.2.1 PS232功能简介 275 6.2.2 PS232上的接口定义 276 6.2.3 串行仪控的实习 277 6.2.4 通信参数的设置 278 6.3 客户端的创建 278 6.3.1 TextBox组件与数据显示 279 6.3.2 状态灯号与数据显示 285 6.3.3 Visual Basic .NET中 的绘图 292 6.3.4 以曲线图表示数据 298 6.3.5 使用事件进行数据接收 305 6.3.6 PaintBox与数据 显示——字节数据 310 6.4 数据的存储及打印 319 6.4.1 数据存取 319 6.4.2 PS232数据及文件存取 322 6.4.3 打印及预览 331 6.4.4 打印及预览程序开发 332 常识问答 343 本章习题 344 第7章 其他串行通信组件 及串行端口 345 7.1 Windows的终端机 345 7.1.1 选择与使用终端机 345 7.1.2 与设备的连接测试 347 7.2 PComm Pro软件 348 7.2.1 PComm Pro的终端机 349 7.2.2 PComm Pro的串行 端口性能测试 351 7.2.3 PComm Pro的数据监视器 352 7.3 增加串行通信端口 355 7.3.1 MOXA C168多端口卡 355 7.3.2 USB转RS-232转接器 358 第8章 仪器设备上的串行通信 360 8.1 噪声计 360 8.1.1 仪器连接 360 8.1.2 噪声计简介 361 8.1.3 命令格式 362 8.1.4 沟通项目的设计 363 8.1.5 噪声读值的采集 370 8.2 电功率计 375 8.2.1 电功率计简介 375 8.2.2 接口及命令格式讨论 376 8.2.3 测试项目的创建 379 8.3 测量用放大器 390 8.3.1 BK-2525振动计简介 390 8.3.2 RS-232接口及命令 格式说明 391 8.3.3 项目的创建 395 8.4 电源供应器 400 8.4.1 电源供应器简介 400 8.4.2 接口及命令格式 402 8.4.3 控制项目的创建 404 8.5 温度记录器 409 8.5.1 温度记录器简介 409 8.5.2 接口及命令格式 411 8.5.3 沟通项目的创建 414 8.6 转速计 424 8.6.1 转速计介绍 424 8.6.2 接口及命令格式 425 8.6.3 项目程序的创建 426 8.7 条形码识别器 431 8.7.1 识别器设备介绍 432 8.7.2 界面及格式说明 433 8.7.3 项目程序的创建 434 8.8 测量电表 438 8.8.1 电表设备简介 438 8.8.2 接口及命令格式 439 8.8.3 项目程序的创建 443 8.9 波形发生器 449 8.9.1 设备介绍 449 8.9.2 接口及命令格式 450 8.9.3 项目程序的创建 453 常识问答 459 本章习题 460 第9章 分布式监控及网络化简介 461 9.1 分布式监控 461 9.1.1 何谓分布式监控 461 9.1.2 多模块的网络系统 462 9.1.3 RS-232与RS-485的转换 464 9.2 命令与格式 465 9.2.1 格式讨论 465 9.2.2 送收程序 468 9.2.3 取得模块的配置 468 9.3 网络化的串行通信 473 9.3.1 网络化的连接 473 9.3.2 工业上的网络连接 474 常识问答 475 本章习题 476 附录 ASCII码 477 参考文献 478

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值