Windows 7/64中的串行异步I / O.

时间:2012-09-25 12:58:35

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

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

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

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

通过在调试器中检入分配了正确的OVERLAPPED结构的地址,或者通过设置temp,我可以看到它已损坏。全局变量指向它。

我的问题是:为什么会发生这种情况,为什么它只发生在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结构进行内部硬拷贝?我可以看到这会导致这种行为,因为我依赖于在结构末端潜入的其他参数(这对我来说似乎是一个黑客,但显然我从官方的MSDN示例中得到了“黑客”)。

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

由于Win 7/64中的一些“虚假干扰”会产生带有损坏或无关参数的错误回调调用,因此似乎执行了回调。

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

1 个答案:

答案 0 :(得分:2)

我发现了这个问题,结果很烦人。

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

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

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