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

Serial asynchronous I/O in Windows 7/64

本文关键字:异步 Windows      更新时间:2023-10-16

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

问题是,该程序可以很好地查找和设置COM端口,但无法发送或接收任何数据。无论我是在WinXP还是7中编译二进制文件,我都无法在Win7/64上发送/接收。兼容模式,以管理员身份运行等没有帮助。

我已经设法将问题缩小到FileIOCompletionRoutine回调。每次调用时,dwErrorCode始终为0,dwNumberOfBytesTransfered始终为0。函数内部的GetOverlappedResult()始终返回TRUE(一切正常)。它似乎正确设置了lpNumberOfBytesTransfered。但是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结构的内部硬拷贝?我可以看到这将如何导致这种行为,因为我依赖于在结构末尾偷偷输入的其他参数(这对我来说似乎是一个黑客攻击,但显然我是从MSDN官方示例中得到的"黑客攻击")。

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

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

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

我发现了这个问题,结果发现它非常简单。

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

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

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