Feb 11, 2011

Encapsulating Windows Threads in C++ Objects

Threads in Windows can be created using the following three routines

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,
DWORD cbStack,
LPTHREAD_START_ROUTINE lpStartAddr,
LPVOID lpvThreadParam,
DWORD fdwCreate,
LPDWORD lpIDThread
);
uintptr_t _beginthread( 
void( *start_address )( void * ),
unsigned stack_size,
void *arglist 
);
uintptr_t _beginthreadex( 
void *security,
unsigned stack_size,
unsigned ( *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr 
);
//start_address: is the starting address of a routine that begins the execution of a new thread.

Key Points

1. The above routines expects a pointer to the application defined function which would be the entry point or starting address of the thread.
2. A static member function does not have access to the this pointer of the class whereas the member functions have an implicit parameter which points to the object (the this pointer)
3. A static member function is same as an ordinary C function
4. You should not pass pointer to a member function to a system call that starts a thread. A member function is meaning less without an object to invoke it

The threads in windows can be encapsulated using a static member function which acts as the entry point of the thread and passing the this pointer as the function argument. The ThreadImpl class shown below uses static unsigned __stdcall dispatch(void *); method as the thread start address.

#ifndef _Thread_H_
#define _Thread_H_

//File: Thread.h

#include "NonCopyable.h"

namespace examples
{
    class Thread : public NonCopyable
    {
    public:
        Thread();
        Thread(const char *);
        ~Thread();

        bool join();
        bool join(size_t ms);
        void start();
        void setName(const std::string&);
        const char* getName() const;

    private:
        class ThreadImpl *m_impl;
    };

}
#endif //_Thread_H_
#ifndef _ThreadImpl_H_
#define _ThreadImpl_H_

//File: ThreadImpl.h

#include <windows.h>
#include <process.h>
#include <iostream>
#include <string>
#include "NonCopyable.h"

namespace examples
{
    class ThreadImpl : public NonCopyable
    {
    public:
        ThreadImpl();
        ThreadImpl(const std::string&);
        ~ThreadImpl();

        bool join() const;
        bool join(size_t) const;
        void start() const;
        void setName(const std::string&);
        const char* getName() const;

    private:
        // thread entry point
        static unsigned __stdcall dispatch(void *);
        void run();
        bool spawn();

        HANDLE       m_hthread;
        unsigned     m_thrdid;
        std::string m_thrName;
    };

}
#endif //_ThreadImpl_H_

#include "Thread.h"

#include "ThreadImpl.h"
#include <iostream>

//File: Thread.cpp

using namespace examples;

Thread::Thread() : m_impl(new ThreadImpl())
{}

Thread::Thread(const char *name) : m_impl(new ThreadImpl(name))
{}

Thread::~Thread()
{}

bool Thread::join()
{
    return m_impl->join();
}

bool Thread::join(size_t ms)
{
    return m_impl->join(ms);
}

const char* Thread::getName() const
{
    return m_impl->getName();
}

void Thread::setName(const std::string& thrName)
{
    return m_impl->setName(thrName);
}

void Thread::start()
{
    m_impl->start();
}
#include "ThreadImpl.h"

//File: ThreadImpl.cpp

using namespace examples;

unsigned __stdcall ThreadImpl::dispatch(void *args)
{
    ThreadImpl *self = static_cast<ThreadImpl *>(args);
    self->run();
    return 0;
}

ThreadImpl::ThreadImpl() : m_hthread(0), m_thrdid(0), m_thrName("")
{
    spawn();
}

ThreadImpl::ThreadImpl(const std::string& thrName) : m_hthread(0), m_thrdid(0), m_thrName(thrName)
{
    spawn();
}

ThreadImpl::~ThreadImpl()
{
    ::CloseHandle(m_hthread);
    m_hthread = NULL;
}

void ThreadImpl::run()
{
    while(true)
    {
        std::cout << "[" << m_thrName << "]:Executing" << std::endl;
        ::Sleep(1000);
    }
}

const char* ThreadImpl::getName() const
{
    return m_thrName.c_str();
}

void ThreadImpl::setName(const std::string& thrName)
{
    m_thrName = thrName;
}

bool ThreadImpl::spawn()
{
    m_hthread = (HANDLE) ::_beginthreadex(0, 0, &ThreadImpl::dispatch, this, CREATE_SUSPENDED, &m_thrdid);
    std::cout << "Created Thread with id[" << m_thrdid << "]" << std::endl;
    return m_hthread != NULL;
}

bool ThreadImpl::join() const
{
    DWORD retval = ::WaitForSingleObjectEx(m_hthread, INFINITE, false);
    return retval == WAIT_OBJECT_0;
}

bool ThreadImpl::join(size_t ms) const

{
    DWORD retval = ::WaitForSingleObjectEx(m_hthread, ms, false);
    return retval == WAIT_OBJECT_0;
}

void ThreadImpl::start() const
{
    ::ResumeThread(m_hthread);
}
//File: Main.cpp
#include <iostream>
#include "Thread.h"
#define MAX 3

using namespace examples;

int main()
{
    try
    {
        Thread t[MAX];

        t[0].setName("A");
        t[1].setName("B");
        t[2].setName("C");

        for(int i = 0; i < MAX; ++i)
        {
            t[i].start();
        }

        for(int i = 0; i < MAX; ++i)
        {
            t[i].join(10000); // run all threads for 10 secs

        }
    }
    catch(std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
    catch(...)
    {
        std::cerr << "Unknown Exception" << std::endl;
    }
    return 0;
}
Output:
The console does not present a nice picture of the run as the output is basically interleaved messages from different threads

Reference:
Pointer to a C++ member function?
Passing a pointer-to-member-function to a system call that starts a thread

No comments :

Post a Comment