Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A class to encapsulate the basics of overlapped I/O

0.00/5 (No votes)
1 Sep 2004 2  
Overlapped I/O - practice

Introduction

My very first article on CodeProject discussed one good reason why you might want to use overlapped I/O[^ ]. In that article I used pseudocode to outline the steps needed to use overlapped I/O in order to demonstrate just why you might choose to use it. This time around I'm going to present a usable class that encapsulates the basics of overlapped I/O. What follows assumes that you've read my earlier article.

COverlappedIO

looks like this.
class COverlappedIO
{
public:
                    COverlappedIO(void);
    virtual         ~COverlappedIO(void);

    void            Attach(HANDLE hIO);
    OVERLAPPED      *operator&()      { return &m_op; }

    virtual bool    Read(BYTE *pbData, DWORD dwBufferSize, 
                         LPDWORD pdwBytesRead, 
                         HANDLE hStopEvent = INVALID_HANDLE_VALUE);
    virtual bool    Write(BYTE *pbData, DWORD dwBufferSize, 
                          HANDLE hStopEvent = INVALID_HANDLE_VALUE);

    virtual bool    Wait(LPDWORD pdwBytesTransferred,
                         HANDLE hStopEvent = INVALID_HANDLE_VALUE);

private:
    HANDLE          m_hIO;
    OVERLAPPED      m_op;
};

The class uses the create/attach idiom. You create an instance of COverlappedIO , either as a class member, a local variable or on the heap and then attach an open handle to an object you can write to or read from. This can be a file handle, a handle to a named or anonymous pipe, a handle to a comms port or handle to a maislot.

Once you've done the Attach() you Read() or Write() through the object as appropriate. The Write() method takes a pointer to the buffer to be written and the length, in bytes, of the buffer. The Read() method takes the same parameters plus a pointer to a DWORD variable which receives the number of bytes actually read. Both methods also take an optional HANDLE to an event object. If the handle isn't specified in the call it defaults to INVALID_HANDLE_VALUE. If you use the default value the Read() or Write() methods return immediately, having initiated an overlapped I/O operation. If you pass a handle the methods wait until the I/O operation completes or until the handle is signalled. Note that neither method can actually verify that what you passed is a valid event handle; all they can check is if the handle you passed was the default handle of INVALID_HANDLE_VALUE.

The class also doesn't know if the operation you're about to perform on the handle you attached is a valid operation for that handle. If you want to read from the handle you need to ensure the handle was opened with read permissions; likewise for writing. This lack of knowing isn't due to carelessness; there are no API's that I know of that you can call to determine if a handle supports the operation you're about to perform on it, so the class has no choice but to trust you.

Read

looks like this. Write() is similar.

bool COverlappedIO::Read(BYTE *pbData, DWORD dwBufferSize, 
                         LPDWORD pdwBytesRead, HANDLE hStopEvent)
{
    assert(pbData);
    assert(dwBufferSize);
    assert(pdwBytesRead);

    if (m_hIO != INVALID_HANDLE_VALUE)
    {
        ::ReadFile(m_hIO, pbData, dwBufferSize, pdwBytesRead, &m_op);

        if (hStopEvent != INVALID_HANDLE_VALUE)
            return Wait(pdwBytesRead, hStopEvent);
        else
            return true;
    }

    //  Something went wrong

    return false;
}

We do a bunch of assert s on the parameters and proceed. We check if Attach() has been called to attach a valid handle. If so we initiate a ReadFile() call using overlapped I/O and, if that succeeds, we either return the result of a call to Wait() or true. Which we return depends on whether a valid stop handle was passed.

If you called Read() or Write() without specifying a valid stop handle the call returns after setting up the I/O operation but possibly before it completed. This is normal for overlapped I/O (it's called overlapped for a reason). You initiate an I/O operation and then continue with your normal work. Some time later you need to know if the I/O has completed so that you know it's safe to continue processing. That's where the Wait() method comes in. As with the Read() and Write() methods the Wait() method takes an optional stop event handle. The Wait() method looks like this.

bool COverlappedIO::Wait(LPDWORD pdwBytesTransferred, HANDLE hStopEvent)
{
    HANDLE hOverlapped[2] = { m_op.hEvent, hStopEvent };

    if (m_hIO != INVALID_HANDLE_VALUE)
    {
        switch (WaitForMultipleObjects(
                    hStopEvent == INVALID_HANDLE_VALUE ? 1 : 2, 
                    hOverlapped, FALSE, INFINITE))
        {
        case WAIT_OBJECT_0:
            //  Signalled on the overlapped event handle, check the result

            if (GetOverlappedResult(m_hIO, &m_op, pdwBytesTransferred, FALSE))
                return true;

        case WAIT_OBJECT_0 + 1:
            //  Signalled to stop, just stop...

            return false;
        }
    }

    return false;
}

Nothing terribly earth-shattering there. We create a 2 member array containing the overlapped event handle (the one that gets signalled when the overlapped I/O operation completes) and the stop handle. Then we check if the object has actually had an I/O handle attached and if so we fall into a call to WaitForMultipleObjects(), passing it the 2 member array we created earlier and a handle count telling it how many handles to actually monitor. If the stop handle we passed is the default (INVALID_HANDLE_VALUE) then we pass a count of 1 to WaitForMultipleOjects() otherwise we pass a count of 2. Either way, WaitForMultipleObjects() will wait until something happens. If the something is that the overlapped event handle was signalled we then call GetOverlappedResult() to determine if the I/O operation succeeded and return an appropriate status. If anything else happens we return false.

Cleanup

The class cleans up after itself for any objects it created. It won't (and shouldn't) call CloseHandle() on the handle you attached to it. That's the reason there's an Attach() method but there isn't a Detach() method.

History

August 21, 2004 - Initial version.

September 3, 2004 - Minor changes to the class interface.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here