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

A C++ Wrapper for WaitForMultipleObjects Part II - Adding Timer Support

, 9 Jan 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Describes a C++ class that encapsulates the WaitForMultipleObjects API usage pattern making it easy to integrate it with C++ objects.

Introduction

In a previous article, I had discussed a C++ wrapper around the WaitForMultipleObjects API that provides a generic interface to the API which eliminates the need to write the rather ubiquitous switch-case statement to process its return value and transfer control to the appropriate method or function. The wrapper allows you to dynamically add and remove wait handles associating a function object as the handler function for a wait handle. In this article I'll be adding Win32 timer support to this class so that class clients can register function objects to be called when a preset time interval has elapsed.

Background

It is typical of most software to have routine processing tasks. Examples for such tasks are housekeeping functions that perform clean up of unused resources or a function to monitor the health of connected clients in a network application. A common programming idiom used to implement this sort of mechanism is the timer which involves invoking functions when a preset time interval has elapsed. Timers can be fully self managed where a worker thread, amongst its other duties, keeps routinely checking if one of the required timer tasks ought to be invoked. If the program has GUI, Windows provides timer messages that will be sent to the specified window at a preset interval where the required processing can be undertaken. Yet another alternative is if the underlying operating system provides the necessary API, that can be used to register callbacks which will be invoked when the specific time interval has elapsed. Windows SDK provides a few functions that implement the last style of timer mechanism.

The Windows SDK timer APIs provide for two types of timers -- manual-reset and synchronization timers. The former type exhibits a behavior that is quite similar to Win32 events in that they can be waited upon in a wait function to be signalled. When the preset time elapses their state changes to signalled and the wait function which is monitoring the handle returns. The latter uses Asynchronous Procedure Calls (APC) to implement their functionality and requires the thread that is managing the timers to be in, what Microsoft defines as an alertable wait state. When the timer interval has elapsed an APC is queued to the thread APC queue which is serviced when the thread enters an alertable wait state.

Using Windows SDK provided timers are inheretntly more efficient as they are built into the scheduler. Using them also leads to somewhat simpler and cleaner code as the operating system will do the hard work of keeping track of the elapsed interval and when to invoke the timer function (or signal the timer object). Furthermore, multiple timer objects can be created and managed independently which helps achieve better encapsulation.

For integrating timer functionality into WFMOHandler, I'll be using the manual-reset timers. I chose this as they are closer to waitable objects in behavior which made the implementation similar to that of WaitHandler<>. Furthermore, from the MSDN documentation for SetWaitableTimer, this approach seemed to provide better guarantee of timer delivery than synchronous timers. The down-side is that every timer would occupy a slot in the limited WaitForMultipleObjects handle array thereby reducing the number of wait handles that can be monitored.

Using the Code

Timer support is added through two additional methods -- AddTimer() and RemoveTimer().

AddTimer() is a function template and has a signature quite similar to AddWaitHandle(). Besides the function object that is the timer handler which will be called when the timer expires, it takes two additional parameters; the timer interval to be specified in milliseconds and a boolean that specifies whether this is a one-off timer or a repeat timer. As the name implies, one-off timers are just that -- timer function object is invoked once after the preset interval elapses and the timer is no longer usable and is disposed. Repeat timers, on the other hand, work like the good old GUI windows timer, routinely invoking the timer function object until it is explicitly stopped. The function returns an unsigned, which is the unique id of the timer just created. This id can be later used to stop the timer by supplying it to the RemoveTimer() method.

To use, declare your class deriving it from WFMOFHandler and register the timers that you need by supplying the timer handler function or method as a function object. As with wait handles, this argument can be an explicitly constructed function object or one that is composed on the fly using the STL std::bind() facility. Both these two usages are shown below in the MyDaemon constructor.

#include "wfmohandler.h"

// A simple class that implements an asynchronous 'recv' UDP socket.
// Socket binds to loopback address!
class AsyncSocket {
    USHORT m_port;
    WSAEVENT m_event;
    SOCKET m_socket;
    // hide default and copy ctor
    AsyncSocket();
    AsyncSocket(const AsyncSocket&);
public:
    AsyncSocket(USHORT port)
        : m_port(port)
        , m_event(::WSACreateEvent())
        , m_socket(::WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0))
    {
        // bind the socket
        struct sockaddr_in sin = {0};
        sin.sin_family = AF_INET;
        sin.sin_port = ::htons(port);
        sin.sin_addr.s_addr = ::inet_addr("127.0.0.1");
        if (m_event != NULL && m_socket != INVALID_SOCKET 
            && ::bind(m_socket, reinterpret_cast<const sockaddr*>(&sin), sizeof(sin)) == 0) {
            // put it in 'async' mode
            if (::WSAEventSelect(m_socket, m_event, FD_READ) == 0)
                return;
        }
        std::cerr << "Error initializing AsyncSocket, error code: " << WSAGetLastError() << std::endl;
        // something went wrong, release resources and raise an exception
        if (m_event != NULL) ::WSACloseEvent(m_event);
        if (m_socket != INVALID_SOCKET) ::closesocket(m_socket);
        throw std::exception("socket creation error");
    }
    ~AsyncSocket()
    {
        ::closesocket(m_socket);
        ::WSACloseEvent(m_event);
    }
    // for direct access to the embedded event handle
    operator HANDLE() { return m_event; }
    // Read all incoming packets in the socket's recv buffer. When all the packets 
    // in the buffer have been read, resets the associated Win32 event preparing it
    // for subsequent signalling when a new packet is copied into the buffer.
    void ReadIncomingPacket()
    {
        std::vector<char> buf(64*1024);
        struct sockaddr_in from = {0};
        int fromlen = sizeof(from);
        int cbRecd = ::recvfrom(m_socket, &buf[0], buf.size(), 
            0, reinterpret_cast<sockaddr*>(&from), &fromlen);
        if (cbRecd > 0) {
            std::cerr << cbRecd << " bytes received on port " << m_port << std::endl;
        } else {
            int rc = ::WSAGetLastError();
            if (rc == WSAEWOULDBLOCK) {
                // no more data, reset the event so that WaitForMult..will block on it
                ::WSAResetEvent(m_event);
            } else {
                // something else went wrong
                std::cerr << "Error receiving data from port " << m_port 
                      << ", error code: " << ::WSAGetLastError() << std::endl;
            }
        }
    }
};

class MyDaemon : public WFMOHandler {
    AsyncSocket m_socket1;
    AsyncSocket m_socket2;
    AsyncSocket m_socket3;
    class AnotherEventHandler { // an explicit functor
        AsyncSocket& m_socket;
    public:
        AnotherEventHandler(AsyncSocket& sock) : m_socket(sock) {}
        void operator()() {
            m_socket.ReadIncomingPacket(); // handle incoming socket data
        }
    };
    AnotherEventHandler m_aeh;
public:
    MyDaemon() 
        : WFMOHandler()
        , m_socket1(5000)
        , m_socket2(6000)
        , m_socket3(7000)
        , m_aeh(m_socket3)
    {
        // setup two handlers on the two AsyncSockets that we created
        WFMOHandler::AddWaitHandle(m_socket1, 
            std::bind(&AsyncSocket::ReadIncomingPacket, &m_socket1));
        WFMOHandler::AddWaitHandle(m_socket2, 
            std::bind(&AsyncSocket::ReadIncomingPacket, &m_socket2));
        WFMOHandler::AddWaitHandle(m_socket3, m_aeh);
        // install two timers -- a repeat timer and a one-off timer
        // note how the timer ids are preserved for removal later (if necessary)
        m_timerid = WFMOHandler::AddTimer(1000, true, 
            std::bind(&MyDaemon::RoutineTimer, this, &m_socket1));
        m_oneofftimerid = WFMOHandler::AddTimer(3000, false, 
            std::bind(&MyDaemon::OneOffTimer, this));
    }
    virtual ~MyDaemon()
    {
        Stop();
        // just being graceful, WFMOHandler dtor will cleanup anyways 
        WFMOHandler::RemoveWaitHandle(m_socket2);
        WFMOHandler::RemoveWaitHandle(m_socket1);
    }
    void RoutineTimer(AsyncSocket* pSock)
    {
        pSock;
        std::cout << "Routine timer has expired!" << std::endl;
    }
    void OneOffTimer()
    {
        std::cout << "One off tmer has expired!" << std::endl;
        m_oneofftimerid = 0;
    }
};

The code above is the same as what was used for the previous article save for the addition of two methods and hooking them up with the new timer API. Both use cases of the timer API -- one off timer and repeat timer -- are exhibited in the sample.

Points of Interest

Unlike the wait handles, timer handles are managed internally by WFMOHandler. When a timer is added, a new Win32 timer object is created and added to the wait handle array. This handle is not visible to the class client and is released when the timer is removed by a corresponding call to the RemoveTimer() method. However, for one-off timers, the timer handle will automatically be released after the timer function returns irrespective of whether RemoveTimer() is called or not.

Under the hood, the timer interface is implemented using the same technique as what is employed for wait handles. However, the inner class template, TimerHandler<>, used for generating timer specific class instances, adds an extra class, TimerIntermediate, to the derivation chain. On the surface, this class would appear to have been created to hold the unique id of the timer which is used to track the removal request from client classes and might even seem redundant as the timer id can be stored in the template class TimerHandler<>. However, there is another reason why this additional class is there in the derivation chain of TimerHandler<>.

Having TimerIntermediate in the derivation chain helps us distinguish these objects from WaitHandler<> class instance objects from the common m_waithandlers collection. This is necessary as in RemoveTimer() we need to locate the TimerHandler<> class instance object for the timer id that is being requested to be removed. However, since the objects corresponding to all wait handles that we track are stored uniformly, as a pointer to the base class WaitHandlerBase, we need a mechanism to differentiate the two children. This is where having TimerIntermediate in the derivation chain comes handy. Since all the TimerHandler<> instances also have a TimerIntermediate in the derivation chain, we can distinguish the two by using dynamic_cast<TimerIntermediate*> to cast the WaitHandlerBase pointer upwards to TimerIntermediate.

Note that we could have achieved the objective of distinguishing the two children of WaitHandlerBase by adding an extra method to the class that returns a boolean by default. TimerHandler<> can then override this method and return a different value to identify itself. Sample implementation is shown below.

    // base class for waitable triggers
    struct WaitHandlerBase {
        HANDLE m_h;
        bool m_markfordeletion;
        WaitHandlerBase(HANDLE h) : m_h(h), m_markfordeletion(false)
        {}
        virtual ~WaitHandlerBase()
        {}
        virtual bool IsTimer() { return false; }
        virtual void invoke(WFMOHandler*) = 0;
    };
    template<typename Handler>
    struct TimerHandler : public WaitHandlerBase, public TimerIntermediate {
        ...
        virtual bool IsTimer() { return true; }
        ...
    }

However, this entails changing the base class design to accommodate a derived class' requirement. Something that is to be ideally avoided if possible. Of course this would be the more efficient approach of the two, but since removing of timers is typically not an oft repeated task in a program, I felt such a design (or compromise of pure design, rather) was not warranted.

Caveats

One caveat of the implementation is that once a timer is created, its duration cannot be changed. If the timer interval is to be changed, the only solution is to remove the existing timer and then add a new timer with the new interval. Of course this behavior can be altered though this was enough for my needs as I developed this class. Implementing this would however require the TimerIntermediate class to be extended so that it maintains the timer interval as well as the repeat flag so that these properties can be updated from the relevant method.

History

07 Jan 2014 - Intial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Hari Mahadevan
Software Developer IBM
Taiwan Taiwan
Experienced developer having worked on drivers, daemons and applications -- essentially the entire breadth of components in both Windows & Linux. My choice programming language is C++, though I dabble with Python every now and then. Very comfortable with Microsoft technologies and enjoy tackling complex problems.
 
When not coding, I do a bit of photography, read stuff and love making my hands greasy.
 
I live in Taipei, work for IBM as a Technical Lead and am fluent in Chinese.
 
The views and opinions expressed in this article are mine and do not necessarily reflect the policy or position of my employer.
Follow on   Google+   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 PinprofessionalMihai MOGA14-Feb-14 18:06 
GeneralRe: My vote of 5 PinmemberHari Mahadevan16-Feb-14 21:22 
QuestionCan't compile on VS2008,,, Pinmembersun mi Kang13-Jan-14 16:46 
AnswerRe: Can't compile on VS2008,,, PinmemberHari Mahadevan13-Jan-14 18:18 
QuestionGood Article Pinmembertmichals13-Jan-14 9:39 
AnswerRe: Good Article PinmemberHari Mahadevan13-Jan-14 18:14 

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 | Terms of Use | Mobile
Web03 | 2.8.1411023.1 | Last Updated 9 Jan 2014
Article Copyright 2014 by Hari Mahadevan
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid