如何发现 ReadFile 函数超时?

How do I find out that ReadFile function timed-out?

当我将我的 USB-UART 转换器连接到我的笔记本电脑后,我没有通过 GetTimeout 和 SetTimeout 设置超时设置时,ReadFile 变得阻塞。但是,一旦我通过 SetTimeOut 设置它们,ReadFile 就不再阻塞并且 returns 即使没有读取给定的字节数也是如此。

当 ReadFile returns 为真但 dwRead 参数为 0 时,因为没有数据通过串行端口进入我的 PC,我得出结论,ReadFile 函数一定已经超时。但是,使用 GetLastError returns 0。我如何在我的 C++ 程序中验证 ReadFile 实际上已经超时?

SetCommTimeouts()ReadFile() 一起使用时,COMMTIMEOUTS 文档说:

ReadIntervalTimeout
The maximum time allowed to elapse before the arrival of the next byte on the communications line, in milliseconds. If the interval between the arrival of any two bytes exceeds this amount, the ReadFile operation is completed and any buffered data is returned. A value of zero indicates that interval time-outs are not used.

如果发生超时,则读取完成(ReadFile() returns TRUE),并且在超时结束前缓冲的字节数会反映在您的 dwRead 中多变的。因此,如果 dwRead 小于 您要求 ReadFile() 读取的字节数,您就会知道是否发生了超时。如果没有缓冲数据,dwRead 将为 0。如果 dwRead 等于 您要求的字节数,则没有超时,因为 ReadFile() 在读取最后一个请求的字节时退出。

最简单和准确的方法使用 NT api NtReadFile 并检查操作的最终状态。超时 <=> iosb.Status == STATUS_TIMEOUT。 kernel32 api ReadFile - 丢失了此状态的信息 (STATUS_TIMEOUT),因为它只检查 STATUS_PENDING 和 0 > status

对于同步工作,可以使用这样的代码:

void SyncTest(POBJECT_ATTRIBUTES poa)
{
    HANDLE hFile;
    IO_STATUS_BLOCK iosb;
    NTSTATUS status = NtOpenFile(&hFile, FILE_ALL_ACCESS, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT);
    if (0 <= status)
    {
        SERIAL_TIMEOUTS st = { 4000, 1, 0, 1, 0 };
        status = NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_SERIAL_SET_TIMEOUTS, &st, sizeof(st), 0, 0);
        DbgPrint("time %x[%x,%p]\n", status, iosb.Status, iosb.Information);
        if (0 <= status)
        {
            UCHAR buf[256];
            status = NtReadFile(hFile, 0, 0, 0, &iosb, buf, sizeof(buf), 0, 0);
            DbgPrint("read %x [%x,%p]\n", status, iosb.Status, iosb.Information);
            //assert(status == iosb.Status);
            if (status == STATUS_TIMEOUT)
            {
                DbgPrint("timeout\n");
            }
        }
        NtClose(hFile);
    }
}

对于异步:

class __declspec(novtable) IoObject 
{
    friend class UserIrp;
protected:
    HANDLE _hFile;
private:
    LONG _dwRef;

protected:
    virtual ~IoObject()
    {
        if (_hFile) NtClose(_hFile);
    }
    virtual void OnIoComplete(NTSTATUS status, ULONG_PTR Information, ULONG code, PVOID pv) = 0;
public:

    NTSTATUS BindIoCompletion();

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef)) delete this;
    }

    IoObject()
    {
        _hFile = 0;
        _dwRef = 1;
    }
};

class UserIrp : public IO_STATUS_BLOCK
{
    friend IoObject;

    IoObject* _pObj;
    PVOID _pv;
    LONG _dwRef;
    ULONG _code;

    static VOID WINAPI OnComplete(NTSTATUS Status, ULONG_PTR Information, UserIrp* This)
    {
        This->_pObj->OnIoComplete(Status, Information, This->_code, This->_pv);
        This->Release();
    }

    ~UserIrp()
    {
        _pObj->Release();
    }

public:

    NTSTATUS CheckStatus(NTSTATUS status)
    {
        if (NT_ERROR(status))
        {
            OnComplete(status, Information, this);
        }

        return status;
    }

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef)) delete this;
    }

    UserIrp(IoObject* pObj, ULONG code, PVOID pv) : _pObj(pObj), _dwRef(1), _code(code), _pv(pv)
    {
        pObj->AddRef();
    }
};

NTSTATUS IoObject::BindIoCompletion()
{
    return RtlSetIoCompletionCallback(_hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)UserIrp::OnComplete, 0);
}

class MySerial : public IoObject
{
    void OnIoComplete(NTSTATUS status, ULONG_PTR Information, ULONG code, PVOID pv)
    {
        DbgPrint("OnIoComplete(%x, %p, %.4s, %p)\n", status, Information, &code, pv);

        switch (code)
        {
        case 'time':
            if (0 <= status)
            {
                if (PUCHAR buf = new UCHAR[256])
                {
                    if (UserIrp* Irp = new UserIrp(this, 'read', buf))
                    {
                        static LARGE_INTEGER ByteOffset;
                        status = Irp->CheckStatus(NtReadFile(_hFile, 0, 0, Irp, Irp, buf, 256, &ByteOffset, 0));
                        DbgPrint("read begin = %x\n", status);
                        return ;
                    }
                    delete buf;
                }
            }
            break;
        case 'read':
            DbgPrint("read end(%x, %p)\n", status, Information);
            if (status == STATUS_TIMEOUT)
            {
                DbgPrint("timeout\n");
            }
            delete pv;
            break;
        }
    }

    virtual ~MySerial()
    {
        DbgPrint("--MySerial<%p>\n", this);
    }

public:

    MySerial()
    {
        DbgPrint("++MySerial<%p>\n", this);
    }

    NTSTATUS Open(POBJECT_ATTRIBUTES poa)
    {
        IO_STATUS_BLOCK iosb;
        return NtOpenFile(&_hFile, FILE_ALL_ACCESS, poa, &iosb, FILE_SHARE_VALID_FLAGS, 0);
    }

    NTSTATUS SetTimeouts(ULONG ms)
    {
        if (UserIrp* Irp = new UserIrp(this, 'time', 0))
        {
            SERIAL_TIMEOUTS st = { ms, 1, 0, 1, 0 };
            return Irp->CheckStatus(ZwDeviceIoControlFile(_hFile, 0, 0, Irp, Irp, IOCTL_SERIAL_SET_TIMEOUTS, &st, sizeof(st), 0, 0));
        }

        return STATUS_INSUFFICIENT_RESOURCES;
    }
};

void AsyncTest(POBJECT_ATTRIBUTES poa)
{
    if (MySerial* p = new MySerial)
    {
        if (0 <= p->Open(poa) && 0 <= p->BindIoCompletion())
        {
            NTSTATUS status = p->SetTimeouts(4000);
            DbgPrint("set timeout=%x\n", status);
        }
        p->Release();
    }
}