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

随声附和 提交于 2020-01-25 06:36:24

问题


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;
    }
}

回答1:


It was really hard to figure; but thanks to a forum and lots of experimentation I finally found a way. Here's the code:

/**
 * 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;
}

So, how does it work?

  1. Open the port with FILE_FLAG_OVERLAPPED flag
  2. Use SetCommMask() to set EV_RXCHAR as the only events we're interested in is incoming data.
  3. In a cycle wait for data for a specified amount of time, read if anything came.

Also @ScottMcP-MVP's answer is wrong: there's no use to SetCommTimeouts(). On the first sight this function and COMMTIMEOUTS looks like exactly what you'd want though, which drove me to hours of confusion. ☺




回答2:


Call SetCommTimeouts after you open the COM port. This sets ReadFile to return after an interval if no data is received. Then you simply call ReadFile. No overlap or event or GetOverlappedResult is needed.



来源:https://stackoverflow.com/questions/25364525/win32-api-how-to-read-the-serial-or-exit-within-a-timeout-if-wasnt-a-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!