Click here to Skip to main content
Click here to Skip to main content

A class to encapsulate the basics of overlapped I/O

, 1 Sep 2004
Rate this:
Please Sign up or sign in to vote.
Overlapped I/O - practice
<!-- Add the rest of your HTML here -->

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Rob Manderson

United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.
 
I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.
 
Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.
 
Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.
 
I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.
 
Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions

 
GeneralA little confusion Pinmemberaxio18-Jul-05 14:27 
GeneralKentucky Colonel PinsussAnonymous2-Jun-05 12:54 
GeneralRe: Kentucky Colonel PinprotectorRob Manderson2-Jun-05 13:05 
GeneralRe: Kentucky Colonel PinsussAnonymous5-Jun-05 13:49 
QuestionAn example perhaps? PinmemberJerry Evans22-Aug-04 1:53 
AnswerRe: An example perhaps? PinprotectorRob Manderson3-Oct-04 23:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140827.1 | Last Updated 2 Sep 2004
Article Copyright 2004 by Rob Manderson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid