casynchronouswinapi

Asynchronous Serial I/O on Win32 -- How To Create An Alert Event


I tried following Allen Denver's article to implement asynchronous serial port I/O. What isn't mentioned in the article is how or where to call the functions in a main program loop in a useful manner. After putting them in a loop that comes back every second, it was clear there should be no need to call the ReadFile() periodically. I tried putting a WaitCommEvent() in the loop so the read would only occur if characters arrived at the port. That caused a cascade of errors. In trying to simplify the code into something I could post here, even my attempts to open the file in OVERLAPPED mode started failing, so I can't seem to post a useful example. However, I will post something similar with the basic notion that I don't really care if the reads and writes have to be synchronous if I can simply trigger them with an asynchronous event like characters showing up on the serial port. The asynchronous functions from Allen Denver's article are quite involved, and it's not clear to me from his examples how to trigger a read on characters arriving at the port.

In the following code there are five lines commented out in the body of the while(1) loop where I was trying to wait for the characters to arrive. The WaitCommEvent() errors appeared to cite pending I/O even though the port was opened in overlap mode (in the original code), and both the send and receive functions had their own OVERLAPPED structures. What is the proper way to resolve this?

/* WINSIO.c                                                        2020-01-22 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int ConfigureSerialPort(HANDLE hPort)
{
  int Status;
  DCB dcb = {0};
  COMMTIMEOUTS timeouts;

  dcb.DCBlength = sizeof(dcb);

  Status = GetCommTimeouts(hPort, &timeouts);
  GetCommTimeouts(hPort, &timeouts);
  timeouts.ReadIntervalTimeout          = 50;
  timeouts.ReadTotalTimeoutConstant     = 50;
  timeouts.ReadTotalTimeoutMultiplier   = 50;
  timeouts.WriteTotalTimeoutConstant    = 50;
  timeouts.WriteTotalTimeoutMultiplier  = 10;
  if (!SetCommTimeouts(hPort, &timeouts)) {
    printf("Error setting timeouts on port!\n");
  }

  Status = GetCommState(hPort, &dcb);
  dcb.BaudRate =          19200;
  dcb.Parity =            NOPARITY;
  dcb.fBinary =           TRUE;             // Binary mode; no EOF check 
  dcb.fParity =           FALSE;            // Enable parity checking 
  dcb.fOutxCtsFlow =      FALSE;            // No CTS output flow control 
  dcb.fOutxDsrFlow =      FALSE;            // No DSR output flow control 
  dcb.fDtrControl =       DTR_CONTROL_DISABLE; // DTR flow control type 
  dcb.fDsrSensitivity =   FALSE;            // DSR sensitivity 
  dcb.fTXContinueOnXoff = FALSE;            // XOFF continues Tx 
  dcb.fOutX =             FALSE;            // No XON/XOFF out flow control 
  dcb.fInX =              FALSE;            // No XON/XOFF in flow control
  dcb.fErrorChar =        FALSE;            // Disable error replacement 
  dcb.fNull =             FALSE;            // Disable null stripping 
  dcb.fRtsControl =       RTS_CONTROL_DISABLE; // RTS flow control 
  dcb.fAbortOnError =     FALSE;            // Do not abort reads/writes on err
  dcb.ByteSize =          8;                // Number of bits/byte, 4-8 
  dcb.StopBits =          ONESTOPBIT;       // 0,1,2 = 1, 1.5, 2
  dcb.EvtChar =           0x84;             // 'T' 

  if (!SetCommState (hPort, &dcb)) {
    printf("Unable to configure serial port!\n");
  }
  return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int sendpckt(HANDLE hComm, unsigned length, unsigned char * pckt)
{
    unsigned long NbytesWritten = 0;
    int result;
    DWORD dwCommEvent;
    OVERLAPPED oWrite;

    oWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (oWrite.hEvent == NULL)
      return FALSE; // Error creating overlapped event handle.

    result = WriteFile(hComm, pckt, length, &NbytesWritten, NULL);
    //result = WriteFile(hComm, pckt, length, &NbytesWritten, &oWrite);

    if (!result) printf("Err: %d\n", GetLastError());
    WaitCommEvent(hComm, &dwCommEvent, &oWrite);

//    printf("Wrote? %d:%d\n", result, NbytesWritten);
    CloseHandle(oWrite.hEvent);
    return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int recvpckt(HANDLE hComm, unsigned char * pckt)
{
  int Status, idx = 0, len = 0;
  unsigned long Nbytes;
  unsigned char chRead;
  OVERLAPPED oRead;

    do {
      //Status = ReadFile(hComm, &chRead, 1, &Nbytes, &oRead);
      Status = ReadFile(hComm, &chRead, 1, &Nbytes, NULL);
      if (Status) {
      pckt[idx++] = chRead;
      if(Nbytes > 0) len = idx;
       }
      }
     while(Nbytes > 0);
  return len;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int args(int argc, char * argv[], char * port) 
{
  static int i;
  i = atoi(argv[1]);
  sprintf(port, "\\\\.\\COM%d", i);
  return i;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void main(int argc, char *argv[]) {

  HANDLE hPort;
  int result, len, Status;
  DWORD dwCommEvent;
  OVERLAPPED o;
  unsigned idx = 0;
  unsigned char TXBUF[] = "T", RXBUF[2048], port[64];

  if (argc > 1) result = args(argc, argv, port);
   SetConsoleTitle(port); // To specify serial port number on open 
  hPort = CreateFile (port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
                       //OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); 
                       OPEN_EXISTING, 0, NULL); 

  ConfigureSerialPort(hPort);
    if (hPort == INVALID_HANDLE_VALUE) {
     printf("Error in openning serial port\n");
     exit(0); // No point in going on if serial port didn't open
     }
    else
     printf("Serial Port Open\n");

    Status = SetCommMask(hPort, EV_RXCHAR);
  if (Status == FALSE) printf("Error! Setting CommMask\n");

  o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (o.hEvent == NULL) printf("Error creating overlapped event; abort.\n");

  while (1) {

    sendpckt(hPort, 1, TXBUF);
    printf("Sending 0x%.2X\n", TXBUF[0]);

//  Status = WaitCommEvent(hPort, &dwCommEvent, &o); // Wait for chars
//  if (Status == FALSE)
//    printf("Error Setting WaitCommEvent()%d\n", GetLastError());
//  else { // If WaitCommEvent() == TRUE then Read the received data 

//    printf("Chars Recveived\n");  
    len = recvpckt(hPort, RXBUF);
    if (len > 0) 
    {
    printf("RCVD(%d): ", len);
    }

  for (idx=0; idx<len; idx++)
   {
    printf("%d:0x%.2X ", idx+1, RXBUF[idx]);
   }

//    }
    SleepEx(1000, TRUE); // How often to look for data in RX buffer
  }
  CloseHandle(o.hEvent); // Close the Event Handle
  CloseHandle(hPort); // Close the serial port
}

Posting additional code after much appreciated feedback from Rita Han. What was clear from the other posts was my Synchronous example didn't get the point across. So I'm attaching the asynchronous version that mostly works, but leaves much to be desired. If it could be simplified to the length of Rita Han's example, that would be fantastic. This example does "function" meaning that it both sends and receives. The problem is, as originally stated, it requires polling the ports periodically for characters present, and that is a super huge drag. The following code is a nearly identical recreation of Allen Denver's code, and it does not provide the event based response I'm looking for, but it does send and receive chars. I am currently running it across a null modem cable with two serial ports, so the behavior is quite easy to observe. The reliance on polling the serial port is clear by changing the timeout value on the SleepEX() statement. As useful as Rita Han's feedback was, the code did not exhibit the behavior I'm looking for. If someone can take the following example, and make it respond to characters arriving at the serial port rather than waiting to get polled, that is what I'm looking for. Do I need to use ReadFileEX()? Is there some other way to register an event that needs to get mixed in here? Joseph M. Newcomer discounts all use of serial events in this article saying they only exist for 16 bit windows compatibility, and then goes on to provide a substantially more convoluted example of how to program a serial port than Allen Denver did. Does it really need to be this difficult?

/* WINAIO.c                                                        2020-01-22 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int ConfigureSerialPort(HANDLE hPort) {
  int Status;
  DCB dcb = {0};
  COMMTIMEOUTS timeouts;

  dcb.DCBlength = sizeof(dcb);

  Status = GetCommTimeouts(hPort, & timeouts);
  GetCommTimeouts(hPort, & timeouts);
  timeouts.ReadIntervalTimeout = 50;
  timeouts.ReadTotalTimeoutConstant = 50;
  timeouts.ReadTotalTimeoutMultiplier = 50;
  timeouts.WriteTotalTimeoutConstant = 50;
  timeouts.WriteTotalTimeoutMultiplier = 10;
  if (!SetCommTimeouts(hPort, & timeouts)) {
    printf("Error setting timeouts on port!\n");
  }

  Status = GetCommState(hPort, & dcb);
  dcb.BaudRate = 19200;
  dcb.Parity = NOPARITY;
  dcb.fBinary = TRUE; // Binary mode; no EOF check 
  dcb.fParity = FALSE; // Enable parity checking 
  dcb.fOutxCtsFlow = FALSE; // No CTS output flow control 
  dcb.fOutxDsrFlow = FALSE; // No DSR output flow control 
  dcb.fDtrControl = DTR_CONTROL_DISABLE; // DTR flow control type 
  dcb.fDsrSensitivity = FALSE; // DSR sensitivity 
  dcb.fTXContinueOnXoff = FALSE; // XOFF continues Tx 
  dcb.fOutX = FALSE; // No XON/XOFF out flow control 
  dcb.fInX = FALSE; // No XON/XOFF in flow control
  dcb.fErrorChar = FALSE; // Disable error replacement 
  dcb.fNull = FALSE; // Disable null stripping 
  dcb.fRtsControl = RTS_CONTROL_DISABLE; // RTS flow control 
  dcb.fAbortOnError = FALSE; // Do not abort reads/writes on err
  dcb.ByteSize = 8; // Number of bits/byte, 4-8 
  dcb.StopBits = ONESTOPBIT; // 0,1,2 = 1, 1.5, 2
  dcb.EvtChar = 0x84; // 'T' 

  if (!SetCommState(hPort, & dcb)) {
    printf("Unable to configure serial port!\n");
  }
  return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int sendpckt(HANDLE hComm, unsigned length, unsigned char *pckt) {
  unsigned long NbytesWritten = 0;
  OVERLAPPED ovl = {0};
  BOOL fRes;

  ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (ovl.hEvent == NULL)
    return FALSE; // Error creating overlapped event handle.

  //Issue Write.
  if (!WriteFile(hComm, pckt, length, & NbytesWritten, & ovl)) {
    if (GetLastError() != ERROR_IO_PENDING) {
      fRes = FALSE; // WriteFile failed, but isn't delayed. Report error...
    } else {
      //Write is pending.
      if (!GetOverlappedResult(hComm, & ovl, & NbytesWritten, TRUE))
        fRes = FALSE;
      else
        fRes = TRUE;
    }
  } else // Write operation completed successfully.
    fRes = TRUE;
  //    printf(" 0X%.2X ", chWrite);
  CloseHandle(ovl.hEvent);
  return fRes;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int recvpckt(HANDLE hComm, unsigned char *pckt) {
  # define READ_TIMEOUT 500 // milliseconds
  int len = 0;
  unsigned long Nbytes;
  unsigned char chRead, BUF[2048];
  BOOL fWaitingOnRead = FALSE;
  OVERLAPPED ovl = {0};
  DWORD dwRes;

  /*
  Status = SetCommMask(hComm, EV_RXFLAG);
  if (Status == FALSE) printf("Error! Setting CommMask\n");
//  else                 printf("Setting CommMask Succesful\n");
//  */

  ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (ovl.hEvent == NULL) printf("Error creating overlapped event; abort.\n");

  if (!fWaitingOnRead) {
    // Issue read operation
    if (!ReadFile(hComm, & BUF, 2048, & Nbytes, & ovl))
    //if(!ReadFile(hComm, &chRead, 1, &Nbytes, &ovl))
    {
      if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
        // Error in communications; report it
        printf("Error in Communications...\n");
      else {
        fWaitingOnRead = TRUE;
        //   printf("l:%d ", len); // shows loop alive
      }
    } else {
      if (Nbytes > 0) {
        // read completed immediately
        memcpy(pckt, BUF, Nbytes);
        len = Nbytes;
        printf("Immediate Read Completion\n");
        //HandleASuccessfulRead(lpbuf, dwRead);
      }
    }
  }
  if (fWaitingOnRead) {
    dwRes = WaitForSingleObject(ovl.hEvent, READ_TIMEOUT);
    switch (dwRes) {
      // Read completed.
    case WAIT_OBJECT_0:
      if (!GetOverlappedResult(hComm, & ovl, & Nbytes, FALSE))
        printf("Error in Communications Lower Portion\n");
      // Error in communications; report it.
      else {
        if (Nbytes > 0) {
          // Read completed successfully
          // HandleASuccessfulRead(lpbuf, dwRead);
          // Will run away and execute here every time
          fWaitingOnRead = FALSE;
          memcpy(pckt, BUF, Nbytes);
          len = Nbytes;
          printf("Read Completion After Wait\n");
        }
      }
    case WAIT_TIMEOUT:
      //printf("l:%d %d\n", len,rxstate);
      // Operation isn't complete yet. fWaitingOnRead flag isn't changed
      // since I'll loop back around, and I don't want to issue another read
      // until the first one finishes.
      // Good time to do some background work.
      break;

    default:
      // Error in the WaitForSingleObject; abort.
      // This indicates a problem with the OVERLAPPED structure's event handle
      break;
    }
  }
  CloseHandle(ovl.hEvent);
  return Nbytes;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int args(int argc, char *argv[], char *port) {
  static int i;
  i = atoi(argv[1]);
  sprintf(port, "\\\\.\\COM%d", i);
  return i;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void main(int argc, char *argv[]) {

  HANDLE hPort;
  int result, len, Status;
  DWORD dwCommEvent;
  OVERLAPPED o;
  unsigned idx = 0;
  unsigned char TXBUF[] = "T", RXBUF[2048], port[64];

  if (argc > 1) result = args(argc, argv, port);
  SetConsoleTitle(port); // To specify serial port number on open 
  hPort = CreateFile(port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
    OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
  //OPEN_EXISTING, 0, NULL); 

  ConfigureSerialPort(hPort);
  if (hPort == INVALID_HANDLE_VALUE) {
    printf("Error in openning serial port\n");
    exit(0); // No point in going on if serial port didn't open
  } else
    printf("Serial Port Open\n");

  Status = SetCommMask(hPort, EV_RXCHAR);
  if (Status == FALSE) printf("Error! Setting CommMask\n");

  o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  if (o.hEvent == NULL) printf("Error creating overlapped event; abort.\n");

  while (1) { // This is the loop for getting work done.

    sendpckt(hPort, 1, TXBUF);
    printf("Sending 0x%.2X\n", TXBUF[0]);

    //  Status = WaitCommEvent(hPort, &dwCommEvent, &o); // Wait for chars
    //  if (Status == FALSE)
    //    printf("Error Setting WaitCommEvent()%d\n", GetLastError());
    //  else { // If WaitCommEvent() == TRUE then Read the received data 

    //    printf("Chars Recveived\n");  
    len = recvpckt(hPort, RXBUF);
    if (len > 0) {
      printf("RCVD(%d): ", len);
    }

    for (idx = 0; idx < len; idx++) {
      printf("%d:0x%.2X ", idx + 1, RXBUF[idx]);
    }

    //    }
    SleepEx(1000, TRUE); // How often to look for data in RX buffer
    // And how often to send data to the other port
  }
  CloseHandle(o.hEvent); // Close the Event Handle
  CloseHandle(hPort); // Close the serial port
}

Solution

  • The following is an example you can refer to:

    int sendpckt(HANDLE hComm, unsigned length, unsigned char * pckt)
    {
        BOOL result;
        DWORD dwCommEvent;
        OVERLAPPED oWrite = { 0 };
        DWORD errCode;
    
        result = SetCommMask(hComm, EV_TXEMPTY);
        if (!result) printf("Err: %d\n", GetLastError());
    
        result = WriteFile(hComm, pckt, length, NULL, &oWrite);
        if (result == FALSE)
        {
            errCode = GetLastError();
    
            if (errCode != ERROR_IO_PENDING)
                printf("Error! Setting CommMask\n");
        }
    
        OVERLAPPED commOverlapped = { 0 };
    
        commOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (commOverlapped.hEvent == NULL)
            return FALSE; // Error creating overlapped event handle.
    
        assert(commOverlapped.hEvent);
    
        result = WaitCommEvent(hComm, &dwCommEvent, &commOverlapped);
        if (!dwCommEvent)
            printf("Error Setting WaitCommEvent()%d\n", GetLastError());
        else
        {
            // If WaitCommEvent() == TRUE then Read the received data 
            if (dwCommEvent & EV_TXEMPTY)
            {
                printf("Send complete.\n");
            }
    
        }
    
        CloseHandle(oWrite.hEvent);
        CloseHandle(commOverlapped.hEvent);
    
        return 0;
    };
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    int recvpckt(HANDLE hComm, unsigned char * pckt)
    {
        BOOL result;
        int len = 0;
        OVERLAPPED oRead = { 0 };
        DWORD errCode;
        DWORD dwCommEvent;
    
        result = SetCommMask(hComm, EV_RXCHAR);
        if (!result) printf("Err: %d\n", GetLastError());
    
        result = ReadFile(hComm, pckt, 2048, NULL, &oRead);
        if (result == FALSE)
        {
            errCode = GetLastError();
    
            if (errCode != ERROR_IO_PENDING)
                printf("nError! Setting CommMask\n");
        }
    
        OVERLAPPED commOverlapped = { 0 };
    
        commOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (commOverlapped.hEvent == NULL)
            return FALSE; // Error creating overlapped event handle.
    
        assert(commOverlapped.hEvent);
    
        result = WaitCommEvent(hComm, &dwCommEvent, &commOverlapped);
        if (!dwCommEvent)
            printf("Error Setting WaitCommEvent()%d\n", GetLastError());
        else
        {
            if (dwCommEvent & EV_RXCHAR)
            {
                printf("Chars Recveived\n");
                len = oRead.InternalHigh;
            }
    
        }
    
        CloseHandle(oRead.hEvent);
        CloseHandle(commOverlapped.hEvent);
    
        return len;
    };
    
    void main(int argc, char *argv[]) {
    
        HANDLE hPort;
        int len;
        unsigned idx = 0;
        unsigned char TXBUF[] = "T", RXBUF[2048], port[64] = "COM8";
    
        SetConsoleTitle(port); // To specify serial port number on open 
        hPort = CreateFile(port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); 
    
        ConfigureSerialPort(hPort);
        if (hPort == INVALID_HANDLE_VALUE) {
            printf("Error in openning serial port\n");
            exit(0); // No point in going on if serial port didn't open
        }
        else
            printf("Serial Port Open\n");
    
        while (1) {
    
            sendpckt(hPort, 1, TXBUF);
            printf("Sending 0x%.2X\n", TXBUF[0]);
    
            len = recvpckt(hPort, RXBUF);
            if (len > 0)
            {
                printf("RCVD(%d): \n", len);
            }
    
            for (idx = 0; idx < len; idx++)
            {
                printf("%d:0x%.2X \n", idx + 1, RXBUF[idx]);
            }
    
            SleepEx(1000, TRUE); // How often to look for data in RX buffer
        }
    
        CloseHandle(hPort); // Close the serial port
    }
    

    Update:

    Another way is using WaitForSingleObject without SetCommMask and WaitCommEvent. Take read operation as an example:

    int recvpckt(HANDLE hComm, unsigned char * pckt)
    {
        BOOL result;
        int len = 0;
        OVERLAPPED oRead = { 0 };
        DWORD errCode;
        DWORD dwCommEvent;
    
        oRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (oRead.hEvent == NULL)
            return FALSE; // Error creating overlapped event handle.
    
        assert(oRead.hEvent);
    
        result = ReadFile(hComm, pckt, 2048, NULL, &oRead);
        if (result == FALSE)
        {
            errCode = GetLastError();
    
            if (errCode != ERROR_IO_PENDING)
                printf("nError! Setting CommMask\n");
        }
    
        DWORD dwWaitResult = WaitForSingleObject(oRead.hEvent, INFINITE);
        switch (dwWaitResult)
        {
        case WAIT_OBJECT_0:
            printf("Received.\n");
            break;
        case WAIT_TIMEOUT:
            printf("Timeout.\n");
            break;
        case WAIT_FAILED:
            printf("Failed.\n");
            break;
        case WAIT_ABANDONED:
            printf("Abandoned.\n");
            return FALSE;
        }
    
        CloseHandle(oRead.hEvent);
    
        return len;
    };