Win32 API的:如何读超时时间内串口,或退出,如果不是数据(Win32 API: how to

2019-10-20 17:44发布

I need a function to read data from a serial port or return if none came within a time interval. E.g. on GNU/Linux you could use poll()orselect()+read(). Anything analogous in Windows?

Below is something I tried: it ought to work, but the function GetOverlappedResult() is either buggy or not well documented; instead of number of bytes read it reports nothing (not even zero, e.g. if I don't initialize the lpNumberOfBytesTransferred argument, it just contains junk).

// NOTE: the `file` should be opened in non-blocking mode
long ReadData(HANDLE file, char* buf, long szbuf, int msTimeout){
    OVERLAPPED ReadState = {0};
    ReadState.hEvent = CreateEvent(0, true, false, "☠");
    if (!ReadState.hEvent){
        PrintLastErr();
        return -1;
    }
    unsigned long BytesRead = 0; //number of bytes was read
    if (!ReadFile(file, buf, szbuf, &BytesRead, &ReadState)){
        // This creates a reading event. Below we wait for it to complete, and cancel on timeout
        if (GetLastError() != ERROR_IO_PENDING){
            PrintLastErr();
            return -1;
        }
        // No, I can't use WaitForSingleObject(), it exits with disregard to whether
        // reading is ongoing or no more data left (e.g. in case of a serial port).
        while(1) {
            std::this_thread::sleep_for(std::chrono::milliseconds( msTimeout ));
            unsigned long ret;
            puts("Getting result");
            if (!GetOverlappedResult(file, &ReadState, &ret, false)
                && GetLastError() != ERROR_IO_INCOMPLETE){
                PrintLastErr();
                return -1;
            }
            printf("result is %lu\n",ret);
            if(ret == BytesRead){
                return BytesRead;
            }
            BytesRead = ret;
        }
    } else { //completed immediately
        printf("Bytes read %i\n");
        assert(BytesRead <= LONG_MAX);
        return (long)BytesRead;
    }
}

Answer 1:

这是真的很难弄清楚; 但由于论坛和大量的实验,我终于找到了一种方法。 下面的代码:

/**
 * Opens a file in overlapped mode.
 * \returns HANDLE to a file, or 0 on fail
 */
HANDLE OpenFile(const char* FileName){
    HANDLE file = CreateFile( FileName,
                              GENERIC_READ | GENERIC_WRITE,
                              0, //we're greedy(or just lazy)
                              0,
                              OPEN_EXISTING,
                              FILE_FLAG_OVERLAPPED,
                              0);
    if (file == INVALID_HANDLE_VALUE){
        return 0;
    }
    if(!SetCommMask(file, EV_RXCHAR)){ // set a mask for incoming characters event.
        return 0;
    }
    return file;
}

/**
 * Waits for data to arrive
 * \param file a file opened in overlapped mode
 * \param msTimeout is a maximum time to wait
 * \returns -1 on system error, 0 on success, and 1 if time out
 */
int WaitForData(HANDLE file, unsigned long msTimeout){
    int ret;
    unsigned long Occured;//returns the type of an occured event
    OVERLAPPED FileEvent = {0};
    FileEvent.hEvent = CreateEvent(0, true, false, 0);
    do{
        if(!WaitCommEvent(file, &Occured, &FileEvent)){
            if(GetLastError() != ERROR_IO_PENDING){
                ret = -1;
                break;
            }
        }
        switch(WaitForSingleObject(FileEvent.hEvent, msTimeout)){
            case WAIT_OBJECT_0: //the requested event happened
                ret = 0; //a success
                break;
            case WAIT_TIMEOUT://time out
                ret = 1;
                break;
            default://error in WaitForSingleObject
                ret = -1;
                break;
        }
        break;
    }while(0);
    CloseHandle(FileEvent.hEvent);
    return ret;
}

/**
 * Reads data from a file
 * \param file a file opened in overlapped mode
 * \param buf a buf for data
 * \param szbuf size of buffer
 * \returns number of bytes read or -1 on fail
 */
unsigned long ReadData(HANDLE file, char* buf, unsigned long szbuf){
    int ret;
    unsigned long BytesRead = 0; //number of bytes was read
    OVERLAPPED ReadState = {0};
    do{
        ReadState.hEvent = CreateEvent(0, true, false, 0);
        if (!GetOverlappedResult(file, &ReadState, &BytesRead, false)){//get how many bytes incame
            ret = ULONG_MAX;
            break;
        }
        if (ReadFile( file,
                      buf,
                      (BytesRead<=szbuf) ? BytesRead : szbuf,
                      0,
                      &ReadState) == 0){
            ret = ULONG_MAX;
            break;
        }
        ret = BytesRead;
    }while(0);
    CloseHandle(ReadState.hEvent);
    return ret;
}

那么它是怎样工作的?

  1. 打开与端口FILE_FLAG_OVERLAPPED标志
  2. 使用SetCommMask()设置EV_RXCHAR ,我们感兴趣的是输入数据的唯一事件。
  3. 在一个周期内等待数据的特定时间量,阅读如果有什么后果。

同时@ ScottMcP-MVP的答案是错的:有没有用的SetCommTimeouts() 在第一眼看到这个功能和COMMTIMEOUTS看起来像你虽然想要什么,这驱使我困惑小时。 ☺



Answer 2:

打开COM端口后调用SetCommTimeouts。 这台ReadFile的如果没有接收到数据的时间间隔后返回。 然后你只需调用ReadFile的。 无需重叠或事件或GetOverlappedResult。



文章来源: Win32 API: how to read the serial, or exit within a timeout if wasn't a data