【问题标题】:Serial asynchronous I/O in Windows 7/64Windows 7/64 中的串行异步 I/O
【发布时间】:2012-09-25 12:58:34
【问题描述】:

我有一个多线程 Windows 程序,它通过“原始”Win API 调用执行串行端口异步 I/O。它在除 Windows 7/64 之外的任何 Windows 版本上都可以正常工作。

问题是程序可以很好地找到并设置 COM 端口,但它无法发送或接收任何数据。无论我在 Win XP 或 7 中编译二进制文件,我都无法在 Win 7/64 上发送/接收。兼容模式,以管理员身份运行等无济于事。

我已设法将问题缩小到 FileIOCompletionRoutine 回调。每次调用时,dwErrorCode 始终为 0,dwNumberOfBytesTransfered 始终为 0。函数内部的 GetOverlappedResult() 始终返回 TRUE(一切正常)。似乎正确设置了 lp​​NumberOfBytesTransferred。但是 lpOverlapped 参数是损坏的,它是一个指向垃圾值的垃圾指针。

我可以通过在调试器中检查正确的 OVERLAPPED 结构分配在哪个地址或通过设置一个临时值来看到它已损坏。指向它的全局变量。

我的问题是:为什么会发生这种情况,为什么它只发生在 Windows 7/64 上?调用约定是否存在一些我不知道的问题?还是以某种方式对重叠结构进行了不同的处理?


贴出以下代码的相关部分:

class ThreadedComport : public Comport
{
  private:

    typedef struct
    {
      OVERLAPPED       overlapped;
      ThreadedComport* caller;                   /* add user data to struct */
    } OVERLAPPED_overlap;

    OVERLAPPED_overlap _send_overlapped;
    OVERLAPPED_overlap _rec_overlapped;

  ...

static void WINAPI  _send_callback     (DWORD dwErrorCode,
                                        DWORD dwNumberOfBytesTransfered,
                                        LPOVERLAPPED lpOverlapped);
static void WINAPI  _receive_callback  (DWORD dwErrorCode,
                                        DWORD dwNumberOfBytesTransfered,
                                        LPOVERLAPPED lpOverlapped);

  ...
};

在没有实现多线程和异步 I/O 的基类中完成打开/关闭:

void Comport::open (void)
{
  char          port[20];
  DCB           dcbCommPort;
  COMMTIMEOUTS  ctmo_new      = {0};

  if(_is_open)
  {
    close();
  }

  sprintf(port, "\\\\.\\COM%d", TEXT(_port_number));

  _hcom = CreateFile(port,
                     GENERIC_READ | GENERIC_WRITE,
                     0,
                     0,
                     OPEN_EXISTING,
                     0,
                     0);

  if(_hcom == INVALID_HANDLE_VALUE)
  {
   // error handling
  }

  GetCommTimeouts(_hcom, &_ctmo_old);
  ctmo_new.ReadTotalTimeoutConstant    = 10;
  ctmo_new.ReadTotalTimeoutMultiplier  = 0;
  ctmo_new.WriteTotalTimeoutMultiplier = 0;
  ctmo_new.WriteTotalTimeoutConstant   = 0;

  if(SetCommTimeouts(_hcom, &ctmo_new) == FALSE)
  {
    // error handling
  }

  dcbCommPort.DCBlength = sizeof(DCB);
  if(GetCommState(_hcom, &(DCB)dcbCommPort) == FALSE)
  {
    // error handling
  }

  // setup DCB, this seems to work fine

  dcbCommPort.DCBlength = sizeof(DCB);
  dcbCommPort.BaudRate = baudrate_int;

  if(_parity == PAR_NONE)
  {
    dcbCommPort.fParity = 0;                     /* disable parity */
  }
  else
  {
    dcbCommPort.fParity = 1;                     /* enable parity */
  }
  dcbCommPort.Parity  = (uint8)_parity;
  dcbCommPort.ByteSize = _databits;
  dcbCommPort.StopBits = _stopbits;

  SetCommState(_hcom, &(DCB)dcbCommPort);
}


void Comport::close (void)
{
  if(_hcom != NULL)
  {
    SetCommTimeouts(_hcom, &_ctmo_old);
    CloseHandle(_hcom);
    _hcom = NULL;
  }
  _is_open = false;
}

整个多线程和事件处理机制比较复杂,相关部分有:

发送

result = WriteFileEx (_hcom,              // handle to output file
                      (void*)_write_data, // pointer to input buffer
                      send_buf_size,      // number of bytes to write
                      (LPOVERLAPPED)&_send_overlapped, // pointer to async. i/o data
                      (LPOVERLAPPED_COMPLETION_ROUTINE )&_send_callback);

接收

  result = ReadFileEx (_hcom,                  // handle to output file
                       (void*)_read_data,      // pointer to input buffer
                       _MAX_MESSAGE_LENGTH,    // number of bytes to read
                       (OVERLAPPED*)&_rec_overlapped, // pointer to async. i/o data
                       (LPOVERLAPPED_COMPLETION_ROUTINE )&_receive_callback);

回调函数

void WINAPI ThreadedComport::_send_callback (DWORD dwErrorCode,
                                             DWORD dwNumberOfBytesTransfered,
                                             LPOVERLAPPED lpOverlapped)
{
  ThreadedComport* _this = ((OVERLAPPED_overlap*)lpOverlapped)->caller;

  if(dwErrorCode == 0)                           // no errors
  {
    if(dwNumberOfBytesTransfered > 0)
    {
      _this->_data_sent = dwNumberOfBytesTransfered;
    }
  }

  SetEvent(lpOverlapped->hEvent);
}


void WINAPI ThreadedComport::_receive_callback (DWORD dwErrorCode,
                                                DWORD dwNumberOfBytesTransfered,
                                                LPOVERLAPPED lpOverlapped)
{
  if(dwErrorCode == 0)                           // no errors
  {
    if(dwNumberOfBytesTransfered > 0)
    {
      ThreadedComport* _this = ((OVERLAPPED_overlap*)lpOverlapped)->caller;
      _this->_bytes_read = dwNumberOfBytesTransfered;
    }
  }

  SetEvent(lpOverlapped->hEvent);
}

编辑

更新:我花了一天的大部分时间研究 OVERLAPPED 变量在执行回调之前超出范围的理论。我已经验证这永远不会发生,我什至尝试将 OVERLAPPED 结构声明为静态,同样的问题仍然存在。如果 OVERLAPPED 结构超出范围,我希望回调指向之前分配结构的内存位置,但它没有,它指向其他地方,在一个完全不熟悉的内存位置。为什么会这样,我不知道。

也许 Windows 7/64 制作了 OVERLAPPED 结构的内部硬拷贝?我可以看到这将如何导致这种行为,因为我依赖于在结构末尾潜入的附加参数(这对我来说似乎是一个 hack,但显然我从官方 MSDN 示例中得到了那个“hack”)。

我也尝试更改调用约定,但这根本不起作用,如果我更改它,程序就会崩溃。 (标准调用约定会导致它崩溃,无论标准是什么,cdecl?__fastcall 也会导致崩溃。)有效的调用约定是 __stdcall、WINAPI 和 CALLBACK。我认为这些都是 __stdcall 的相同名称,我在某处读到 Win 64 无论如何都会忽略该调用约定。

似乎执行回调是因为 Win 7/64 中的一些“虚假干扰”生成带有损坏或不相关参数的错误回调调用。

多线程竞争条件是另一种理论,但在我运行重现错误的场景中,只有一个线程,我可以确认调用 ReadFileEx 的线程与执行回调的线程相同。

【问题讨论】:

  • 首先摆脱在 Read/WriteFileEx() 调用中的糟糕转换。这只会阻止编译器告诉你你做错了。
  • @HansPassant 此代码部分适用于为嵌入式系统编写的库,这些库将 uint8 用于字符串。 API 函数需要一个普通的char,它具有实现定义的签名。因此,强制转换消除了 uint8 和 char 之间完全不相关的强制转换警告。由于宇宙中的任何地方都不存在负索引符号表,因此转换在任何系统上都是完全安全的。
  • 请停止投票以关闭帖子,因为主题太高级了。如果 Windows 7/64 中存在导致异步 I/O 行为异常的错误,则问题不是太本地化。如果错误在我的代码中,那么它可能确实是相当本地化的。在我们知道错误的来源之前,现在说还为时过早。
  • @Deanna 让我很恼火的是,有数百名用户/巨魔积极试图从这个网站上吓跑主题问题。持续的傲慢趋势似乎是在他们不知道的任何话题上投票结束。
  • 可能是企鹅恐慌:不是 bash 脚本,所以一定太高级了:)

标签: c++ winapi windows-7 asynchronous serial-port


【解决方案1】:

我发现了问题,结果很简单。

在 CreateFile() 中,我没有指定 FILE_FLAG_OVERLAPPED。由于未知的原因,这在 32 位 Windows 上是不必要的。但如果你在 64 位 Windows 上忘记了它,它显然仍会使用 FileIOCompletionRoutine 生成回调,但它们的参数已损坏。

我在任何地方都没有找到任何关于这种行为改变的文档;也许这只是 Windows 中的一个内部错误修复,因为旧文档还指定您必须设置 FILE_FLAG_OVERLAPPED。

至于我的具体情况,出现错误是因为我有一个假定同步 I/O 的基类,然后被使用异步 I/O 的类继承。

【讨论】:

  • 嘿 - 从来没有看过那个'因为我本来希望它在没有打开重叠标志的任何操作系统上都不起作用。这是多么奇怪:)
  • FILE_FLAG_OVERLAPPED 绝对独立于 32/64 位
  • @RbMm 这个错误是关于错误使用标志时会发生什么。如果您在 Win 7/64 之前没有设置它但仍然使用重叠 I/O,它可以工作。但是如果你在 64 上做同样的错误,程序就会崩溃。
  • 绝对没有。这里的任务不在此。如果你在CreateFile 中使用这个标志,io 将是异步的。如果未设置 - 不。这不取决于 32/64 位或操作系统版本。但是您使用 apc 回调 - 如果操作成功,将调用它。即使对于同步 io。我确定您代码中的问题出在其他问题上
  • @RbMm 不,就是这样。我所做的唯一更改是答案中的那个,然后程序停止崩溃。
猜你喜欢
  • 2013-06-19
  • 1970-01-01
  • 2015-07-24
  • 2011-03-14
  • 1970-01-01
  • 1970-01-01
  • 2012-09-14
  • 2011-11-25
  • 2012-05-16
相关资源
最近更新 更多