スキップしてメイン コンテンツに移動

C++ Asynchronous Delegate for Microsoft Windows

Microsoft Windows 2000 and later have a very useful system function to make an asynchronous function call: QueueUserWorkItem. With this function and its thread pool that is aware of what Windows is actually doing at a given time, Windows takes care of all asynchronous function call complicatedness for you in the simplest form. This high-level function is a god-send for lazy programmers who would concentrate on what an application can do in a reasonable performance range rather than bothering about how it does things with the smallest performance hit.

But people can never be lazy enough, setting it up with context information each time will soon become a boring task especially when you want to asynchronously call a member function of a C++ object. But it's not possible to make it completelly dynamic, either. You have to manually write a wrapper function, since QueueUserWorkItem is a mere C function that knows jack about C++. This article introduces a minimalistic toolkit AsyncDelegate.h that lends itself to solving this issue by using C++ templates.

To use AsyncDelegate.h, declare an asynchronously called member function and define an inner class for context information. This inner class can have a ctor/dtor if needed.

#include <string>
#include "AsyncDelegate.h"

class X
{
public:

// ...

struct ContextY : AsyncDelegateContextHeader<X>
{ // you can add parameters as members.
std::string strParameter_;
};

void callbackY(ContextY* pContext);
};



X::callbackY is defined like this in a .cpp file:

void X::callbackY(ContextY* pContext)
{
// do something with pContext_->strParameter_
}



Now you can activate it anywhere you want where a pointer to a target X object is available.

X* p = new X;

// ...

{
AsyncDelegate<
X,
X::ContextY,
&amp;amp;X::callbackY
> c(p);

c.pContext_->strParameter_ = &quot;parameter&quot;;

// an asynchronous call to X::callbackY is implicitly initiated here by the destructor of c
}



X::callbackY is called in the context of the specific X object p with a ContextY object as a parameter. In other words, AsyncDelegate.h is a tool to do deferred execution of this member function call.

X* p = new X;

ContextY pContext;

p->callbackY(pContext);



Let's take a look at the actual AsyncDelegate.h.

// AsyncDelegate.h

#ifndef CALC_ASYNCDELEGATE_H
#define CALC_ASYNCDELEGATE_H

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif _WIN32_WINNT

#include <windows.h>



AsyncDelegateContextHeader is inserted before a context object as a hidden information header that knows to which object this call belongs.


template <class T>
struct AsyncDelegateContextHeader
{
T* pSelf_;
};


This class casts a void parameter into T and destructs it at the end of a function call.


template <class T>
class AsyncDelegateContextCleaner
{
private:
AsyncDelegateContextCleaner() {}
public:
T* pContext_;

explicit AsyncDelegateContextCleaner(LPVOID lpParameter) : pContext_((T*)lpParameter) {}
~AsyncDelegateContextCleaner()
{
delete(pContext_);
}
};


MemFunPointerCreator is a class to make a C++ member function pointer type from 2 template parameter types. This class is required to put a member function pointer in the template parameters of queueUserWorkItemThreadProc.

template <class T, class C>
struct MemFunPointerCreator
{
typedef void (T::* ClientMemFunPointerType)(C*);
};


This function template creates a function called by QueueUserWorkItem. A C++ member function pointer created by MemFunPointerCreator is set as a non-type template parameter.

template <class T, class C, typename MemFunPointerCreator<T, C>::ClientMemFunPointerType pMemberFunction>
DWORD WINAPI queueUserWorkItemThreadProc(LPVOID lpParameter)
{
AsyncDelegateContextCleaner<C> c(lpParameter);

((c.pContext_->pSelf_)->*pMemberFunction)(c.pContext_);

return 1;
}



This is the main class exposed to a user. If bLong is true QueueUserWorkItem is called with WT_EXECUTELONGFUNCTION that implies spawning a new thread which is the default behavior of this tool.

template <
class T,
class C,
typename MemFunPointerCreator<T, C>::ClientMemFunPointerType pMemberFunction,
bool bLong_ = true
>
struct AsyncDelegate
{
C* pContext_;

explicit AsyncDelegate(T* p)
{
pContext_ = new C;
pContext_->pSelf_ = p;
}

~AsyncDelegate()
{
QueueUserWorkItem(
queueUserWorkItemThreadProc<T, C, pMemberFunction>, // starting address
pContext_, // function data
bLong_ ? WT_EXECUTELONGFUNCTION : WT_EXECUTEDEFAULT // worker options
);
}
};

#endif CALC_ASYNCDELEGATE_H



It may be counter-intuitive to call this tool a "delegate" as it doesn't support copy and other smart pointer properties, but it's OK for me because its usage is limited to kicking a worker thread immediately.

コメント