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

Use member functions for C-style callbacks and threads - a general solution

0.00/5 (No votes)
7 Jul 2001 1  
The article shows a general solution to redirect any C-style callback into a member function using adapter objects

Introduction and Motivation

Callback functions...

The Win32 API and a lot of other third party libraries were designed for the C programming language. Consequently C-style callback functions are used in many places. They are used in information retrieving (like enumerating open windows with EnumWindows()), to modify the behaviour of algorithms (like qsort() from the C runtime library), as entry points for new threads (CreateThread() and _beginthreadex()), and much more.

... are somewhat awkward in C++

However, in the object oriented world of C++, using C-style callback functions is somewhat awkward. The problem is that we can pass only static member functions (also called class functions) in these places. But class functions have no implicit this pointer, so they are rather limited. What we want is to use a real member function as a callback. The article describes how this can be achieved by using adapter objects and adapter functions, which enable us to write code like the following:

#include "stdafx.h"

#include "win_adapter.h"


struct DATA { int foo; double bar; };

struct Test
{
    // Callback for EnumWindows using a user defined type for the UserData parameter

    BOOL WindowLister( HWND hWnd, const DATA& data )
    {
        printf("  Window: %p\n",  hWnd );
        return true;
    }

    // Thread function. Note that we can use any type for the thread parameter

    DWORD ThreadStart( LPCTSTR pszName )
    {
        printf( "  Hello, I'm a Thread and my name is: %s\n", pszName );
        return 0;
    }
};

void main()
{
    Test test;
    DATA someData;

    // Calling EnumWindows with a member function as callback

    win::EnumWindows( &test, &Test::WindowLister, someData );
    printf("\n");

    // Creating a thread that starts execution in a member function

    LPCTSTR pszThreadName = "Joe, the funny thread";
    HANDLE hThread = win::beginthreadex( &test, &Test::ThreadStart, pszThreadName );
    WaitForSingleObject( hThread, INFINITE );
    CloseHandle( hThread );
}

As you can see all you have to do is to include the win_adapter.h header file and member functions of any class can be used as thread starters or callbacks. No .cpp or .lib file to add to your project, no special classes to derive from, no structural impacts to your code. Just go on an use it!

API calls supported by win_adapter.h

The win_adapter.h file defines adapter functions for almost any Win32 API call that uses callback functions. All wrapper functions are declared in the win namespace:

CreateThread(), beginthreadex(), EnumWindows(), EnumChildWindows(), EnumDesktopWindows(), EnumDesktops(), EnumWindowStations(), EnumResourceNames(), EnumResourceLanguages(), EnumThreadWindows(), EnumFonts(), EnumFontFamiliesEx(), EnumMetaFile(), EnumEnhMetaFile(), EnumICMProfiles(), EnumObjects(), EnumerateLoadedModules(), EnumDisplayMonitors(), EnumResourceTypes(), EnumSystemCodePages(), EnumSystemLocales(), EnumDateFormats(), EnumDateFormatsEx(), EnumTimeFormats(), EnumCalendarInfo()

The Demo Project

The demo project shows how to use win_adapter.h. It declares a simple class and uses the adapter functions to redirect callbacks and a thread into member functions.

The rest of this article describes the techniques behind the adapters. You do not need to understand them to use the code. However, they are quite interesting techniques on adapters and template programming, so the article is worth reading.

The problem

Why it is not possible to use a member function as callback

There is an important difference between a member function and an ordinary C function or class function (which is much the same as a C function): They use different calling conventions. Where C functions use the _cdecl or _stdcall calling convention, member functions implicitly use the thiscall calling convention. A member function always gets an additional hidden parameter, the this pointer, which contains the address of the object the member function is called for. Because of the necessary hidden this parameter the syntax to call a function through a function pointer differs for C functions and member functions, as shown in the following example:

struct B
{
    // A member function, using (implicitly) thiscall calling 

    // convention

    int foo( double data );     
    
    // A class function (which is a plain C function) using _stdcall calling 

    // convention

    static int _stdcall bar(double data);
};

void main()
{
    typedef int (_stdcall* PFBAR)(double);  // Type of pointer to B::bar()

    typedef int (B::*PFFOO)(double);        // Type of pointer to B::foo()


    PFBAR Bar = &B::bar;    // Bar is now a function pointer to B::bar()

    PFFOO Foo = &B::foo;    // Foo is now a function pointer to B::foo()


    // Call bar through the function pointer, we don't need an instance for this

    int n1 = Bar( 47.11 );

    // Call foo through the function pointer, 

    // however we need an instance that is passed for the this pointer

    B Obj;
    B* pObj = &Obj
    int n2 = (Obj.*Foo) ( 47.11 );     // Call with an instance using the .* operator

    int n3 = (pObj->*Foo) ( 47.11 ); // Call with pointer to an instance using the 

                                        // ->* operator

}

As you can see, it is not impossible to call a member function via a function pointer. However, the syntax to invoke a member function through a function pointer is quite different from the C function pointer syntax. In other words, C function pointers and C++ member function pointers are incompatible. Our goal is to write some kind of adapter that translates the C-style function call into a call to a member function.

Introducing adapters to call member functions

The traditional way

If you take a deeper look at the Win32 functions that use callbacks, you will find out that most of them accept a UserData parameter. The actual name and type of this UserData parameter varies, but it is always a 32-bit data type and always passed as last argument to the callback. Using this, we can write a static adapter function for the callback that gets the this pointer via the UserData and calls a real member function. The following example uses this approach:

class A
{
public:
    void SomeFunction()
    {
        m_nWindowCounter = 0;
        ::EnumWindows( EnumWindowsCBAdapter1, (LPARAM) this );
    }

    void SomeOtherFunction()
    {
        ::EnumWindows( EnumWindowsCBAdapter2, (LPARAM) this );
    }

private:
    virtual BOOL EnumWindowsCB1( HWND hWnd )
    {
        printf( "%d. Windows: %p\n", m_nWindowCounter++, hWnd );
        return TRUE;
    }

    virtual BOOL EnumWindowsCB2( HWND hWnd )
    {
        // Do something different with hWnd

        return TRUE;
    }

    static BOOL WINAPI EnumWindowsCBAdapter1( HWND hWnd, LPARAM UserData )
    {
        return ((A*) UserData)->EnumWindowsCB1( hWnd );
    }
    static BOOL WINAPI EnumWindowsCBAdapter2( HWND hWnd, LPARAM UserData )
    {
        return ((A*) UserData)->EnumWindowsCB2( hWnd );
    }

    int m_nWindowCounter;
};

This technique works in most cases. It is also used to create threads that start execution in a member function. However, it has a lot of drawbacks:

  1. We lose the UserData parameter, because it is now used for the this pointer.
  2. The class gets cluttered with lots of adapter functions.
  3. Because the member function to call is hard coded into the adapter function, we have to write an own adapter for each member function that we want to use as callback.
  4. We have to write the same code again and again.

Using an adapter object

We can eliminate these drawbacks by using an adapter object instead of an adapter function. The adapter object stores the UserData, the this pointer and the address of a member function to call. It implements a static function that acts as adapter function, but calls the member function through the stored pointers:

struct EnumWindowsAdapter
{
    EnumWindowsAdapter( A* pObj, BOOL (A::*pfCallback)(HWND, LPARAM), LPARAM UserData )
        : m_pObj( pObj ), m_pfCallback( pfCallback ), m_UserData( UserData) {}

    static BOOL WINAPI Callback( HWND hWnd, LPARAM UserData )
    {
        // Call member function through pointer stored in the adapter object

        EnumWindowsAdapter* _this = (EnumWindowsAdapter*) UserData;
        return (_this->m_pObj->*_this->m_pfCallback)( hWnd, _this->m_UserData ); 
    }

    A*      m_pObj;
    BOOL (A::*m_pfCallback)(HWND, LPARAM);
    LPARAM  m_UserData;
};

We use EnumWindowsAdapter by initializing it with our A instance and pass the Callback function as callback to EnumWindows() with the instance of the adapter object as UserData:

class A
{
public:
    void SomeFunction()
    {
        m_nWindowCounter = 0;
        EnumWindowsAdapter adapter( this, &A::EnumWindowsCB1, 4711 );
        ::EnumWindows( adapter.Callback, (LPARAM) &adapter );
    }

    void SomeOtherFunction()
    {
        EnumWindowsAdapter adapter( this, &A::EnumWindowsCB2, 0 );
        ::EnumWindows( adapter.Callback, (LPARAM) &adapter );
    }

private:
    virtual BOOL EnumWindowsCB1( HWND hWnd, LPARAM UserData )
    {
        printf( "%d. Windows: %p, UserData: %d\n", m_nWindowCounter++, 
                hWnd, UserData );
        return TRUE;
    }

    virtual BOOL EnumWindowsCB2( HWND hWnd, LPARAM )
    {
        // Do something different with hWnd

        return TRUE;
    }

    int m_nWindowCounter;
};

Of course this solution is still rather limited. The EnumWindowsAdapter class works only together with instances of class A and only for EnumWindows. However, it shows the principle of using adapter objects. The next chapter explains how we can generalize the adapter object to get rid of the limitations.

Generalizing the solution

A deeper look to Win32 callbacks

In our example above, we build an adapter object for the EnumWindows() callback to use in class A. Now we want to abstract the adapter from concrete classes and API-calls to use with. As so often in C++, the way to achieve this is using templates. But before we start to code the templated version of the adapter class, we should have a look at some more API-calls that use callbacks to learn what exactly we have to generalize with templates:

Prototypes of some Win32 API calls and their related callback functions
Win32 functionrelated callback function
BOOL EnumWindows(
��WNDENUMPROC lpEnumFunc,
��LPARAM lParam
)
BOOL CALLBACK EnumWindowsProc(
��HWND hwnd,
��LPARAM lParam
)
BOOL EnumDesktops(
��HWINSTA hwinsta,
��DESKTOPENUMPROC lpEnumFunc,
��LPARAM lParam
��)
BOOL CALLBACK EnumDesktopProc(
��LPTSTR lpszDesktop,
��LPARAM lParam
)
int EnumObjects(
��HDC hdc,
��int nObjectType,
��GOBJENUMPROC lpObjectFunc,
��LPARAM lParam
)
int CALLBACK EnumObjectsProc(
��LPVOID lpLogObject,
��LPARAM lpData
)
BOOL EnumResourceTypes(
��HMODULE hModule,
��ENUMRESTYPEPROC lpEnumFunc,
��LONG_PTR lParam
)
BOOL CALLBACK EnumResTypeProc(
��HMODULE hModule,
��LPTSTR lpszType,
��LONG_PTR lParam
)
BOOL EnumerateLoadedModules(
��HANDLE hProcess,
��PENUMLOADED_MODULES_CALLBACK pfCallback,
��PVOID UserContext
)
BOOL CALLBACK EnumLoadedModulesProc(
��PSTR ModuleName,
��ULONG ModuleBase,
��ULONG ModuleSize,
��PVOID UserContext
)

By a deeper look at the callbacks we realize the following points:

  1. All callbacks use the same calling convention. (CALLBACK, which is defined as _stdcall in the windows headers.)
  2. The last parameter for the callbacks is always UserData. The type of this UserData varies, but it is always a 32-bit type that can be used to pass a pointer.
  3. The argument and return types of the callbacks vary.
  4. The number of arguments of the callbacks vary.

Points 1 to 3 can be parameterized by using templates. It is not possible to parameterize the number of arguments for a function, so point 4 gives us a bit more work since we have to build a adapter class for callbacks with 2 parameters, 3 parameters and so on. However, the maximum number of parameters for a callback used in the Win32 API is 5, so this is not that much work.

A generalized adapter for callbacks with two arguments

Now we can build our general adapter class. We use template arguments for nearly everything: The return and argument types of the callback, the type of the class that contains the member function (which was A in the above example) and the type of the UserData parameter. The last point gives us additional flexibility because we then can pass UserData of any type to the callbacks and not just a 32 bit data type.

Let's have a look at our generalized adapter for callbacks with two arguments. (For three and more arguments, the code is much the same):

namespace adapter {
    template< class O, class U, class R, class A1, class A2 >
    struct CB2 
    {
        CB2( O* pObj, R (O::*pfCB)(A1, U), U UserData )
            : m_pObj( pObj ), m_pfCB( pfCB ), m_UserData( UserData ) {}

        static R CALLBACK Callback( A1 arg1, A2 pObj )
        {
            CB2* _this = (CB2*)pObj;
            return (_this->m_pObj->*_this->m_pfCB)( arg1, _this->m_UserData );
        }

        O*  m_pObj;
        CB  m_pfCB;
        U   m_UserData;
    };
}   // namespace adapter

We declare all adapters in the adapter namespace. The CB2 adapter class gets 5 template parameters:

  • O - the type of the class that contains the member function to call.
  • U - the type of our UserData parameter.
  • R - the return type of the C-style callback function.
  • A1 - the type of the first argument of the C-style callback function.
  • A2 - the type of the second argument of the C-style callback function, which is always a 32-bit user data.

The following example shows how the adapter is used:

class A
{
public:
    void SomeFunction()
    {
        m_nWindowCounter = 0;
        adapter::CB2< A, LPCTSTR, BOOL, HWND, LPARAM > 
                        adapter( this, &A::EnumWindowsCB1, "Hi all" );
        ::EnumWindows( adapter.Callback, (LPARAM) &adapter );
    }

    void SomeOtherFunction()
    {
        HDC hDC = ::GetDC( NULL );
        adapter::CB2< A, int, int, LPVOID, LPARAM > adapter( this, &A::, 0 );
        ::EnumObjects( hDC, OBJ_PEN, adapter.Callback, (LPARAM) &adapter );
        ::ReleaseDC( NULL, hDC );
    }

private:
    virtual BOOL EnumWindowsCB1( HWND hWnd, LPCTSTR pszUserData )
    {
        printf( "%d. Windows: %p, UserData: %s\n", m_nWindowCounter++, 
                hWnd, pszUserData );
        return TRUE;
    }

    int ( LPVOID pLogObject, int )
    {
        // Do something with the passed object

        return 1;
    }

    int m_nWindowCounter;
};

As you can see, we now have an adapter class that can be used to redirect any callback function with two parameters to a member function. In A::SomeFunction() we use it with ::EnumWindows() and in A::SomeOtherFunction() we use the same adapter class, but parameterized with different template arguments, for ::EnumObjects().

Increasing programming comfort

Using the adapter object class adapter::CB2 is still a bit uncomfortable. We always have to instantiate the full template and remember all types to be passed. This comes from a compiler restriction for class templates: the compiler is not able to deduce template arguments automatically for class templates. However, for function templates, deducing template arguments works quite fine. So we can make life a bit more comfortable by introducing additional wrapper functions for the API calls:

namespace win {
    template< class O, class U >
    BOOL EnumWindows( O* obj, BOOL (O::*pfMemFunc)(HWND, U), U UserData = U() )
    {
        typedef adapter::CB2<O, U, BOOL, HWND, LPARAM> adapter_t;
        return ::EnumWindows( adapter_t::Callback, (LPARAM) &adapter_t( obj, 
                              pfMemFunc, UserData ) );
    }

    template< class O, class U >
    int EnumObjects( HDC hDC, int nType, O* obj, 
                     int (O::*pfMemFunc)(LPVOID, U), U UserData = U() )
    {
        typedef adapter::CB2<O, U, int, LPVOID, LPARAM> adapter_t;
        return ::EnumObjects( hDC, nType, adapter_t::Callback, (LPARAM) 
                              &adapter_t( obj, pfMemFunc, UserData ) );
    }
}   // namespace win

Again we introduce the wrappers in our own namespace. (The win_adapter.h file contains wrappers for almost every Win32 function that uses callbacks.) Utilizing these simple wrappers it gets real fun to use member functions as callbacks, which you can see in the following example:

class A
{
public:
    void SomeFunction()
    {
        m_nWindowCounter = 0;
        win::EnumWindows( this, &A::EnumWindowsCB1, (LPCTSTR) "Hi all" );
    }

    void SomeOtherFunction()
    {
        HDC hDC = ::GetDC( NULL );
        
        // Note that we don't have to pass any UserData if we don't use it.

        // The compiler is able to deduce the type (int) from the prototype of 

        // EnumObjectsCB and passes a default value (0) 

        win::EnumObjects( hDC, OBJ_PEN, this, &A::EnumObjectsCB1 );
        ::ReleaseDC( NULL, hDC );
    }

private:
    virtual BOOL EnumWindowsCB1( HWND hWnd, LPCTSTR pszUserData )
    {
        printf( "%d. Windows: %p, UserData: %s\n", 
                m_nWindowCounter++, hWnd, pszUserData );
        return TRUE;
    }

    BOOL EnumObjectsCB1( LPVOID pLogObject, int )
    {
        // Do something with the passed object

        return TRUE;
    }

    int m_nWindowCounter;
};

Nice, isn't it? Now we have a version of EnumWindows() that looks exactly like the original Win32 call, but accepts an object and an address of a member function to call (and some UserData of a user defined type) instead of a C-style function pointer.

An Adapter for Thread Functions

The callbacks we have considered so far are called synchronously, a call to e.g. ::EnumWindows() always returns after the callback has processed the last window. However, for threads things are a bit different. Thread functions are, by nature, executed asynchronously. This means that we can no longer create the adapter object on the stack as we did so far, because it might have gone out of scope before the new thread starts execution. The solution is to create the instance of the adapter object on the heap and destroy it after thread termination:

namespace adapter {
    template< class O, class U, class R, class A1 >
    struct HeapCB1
    {
        // "Constructor", returns an instance created on the heap

        static HeapCB1* Create( O* pObj, R (O::*pfCB)(U), U UserData )
        {
            return new HeapCB1( pObj, pfCB, UserData );
        }

        static R CALLBACK CallbackAndDestroy( A1  pObj )
        {
            HeapCB1* _this = (HeapCB1*)pObj;
            R Result = (_this->m_pObj->*_this->m_pfCB)(_this->m_UserData );
            delete _this;
            return Result;
        }

        O*  m_pObj;
        CB  m_pfCB;
        U   m_UserData;

    protected:
        // Constructor is protected to prevent instances on the stack

        HeapCB1( O* pObj, R (O::*pfCB)(U), U UserData )
            : m_pObj( pObj ), m_pfCB( pfCB ), m_UserData( UserData ) {}
    };
}   // namespace adapter

The important difference is the delete _this statement in the CallbackAndDestroy() function. To make sure that instances of the adapter are always created on the heap we declare the constructor as protected and create instances through a static Create() function. This adapter is then used in the CreateThread() and _beginthreadex() wrappers as follows:

namespace win {
    template< class O, class U > // Note: Order of parameters is different 

                                       // from original ::CreateThread()

    HANDLE CreateThread( O* obj, DWORD (O::*pfThreadStart)(U), U UserData = U(), 
                         PDWORD pThreadId = NULL, DWORD dwCreationFlags = 0, 
                         DWORD dwStackSize = 0, PSECURITY_ATTRIBUTES pSA = NULL )
    {
        typedef adapter::HeapCB1<O, U, DWORD, LPVOID> adapter_t;
        DWORD dwDummy;
        return ::CreateThread( pSA, dwStackSize, adapter_t::CallbackAndDestroy, 
                               (LPVOID) adapter_t::Create( obj, 
                                  pfThreadStart, UserData ),
                               dwCreationFlags, pThreadId ? pThreadId : &dwDummy
                );
    }

    template< class O, class U > // Note: Order of parameters is different from 

                                       // original ::_beginthreadex()

    HANDLE beginthreadex( O* obj, DWORD (O::*pfThreadStart)(U), U UserData = U(), 
                          PDWORD pThreadId = NULL, DWORD dwCreationFlags = 0,  
                          DWORD dwStackSize = 0, PSECURITY_ATTRIBUTES pSA = NULL )
    {    
        typedef adapter::HeapCB1<O, U, DWORD, LPVOID> adapter_t;
        DWORD dwDummy;
        return (HANDLE)::_beginthreadex( pSA, dwStackSize, 
                (unsigned int (__stdcall *)(void *))adapter_t::CallbackAndDestroy, 
                (LPVOID) adapter_t::Create( obj, pfThreadStart, UserData ),
                dwCreationFlags, PUINT( pThreadId ? pThreadId : &dwDummy )
                );
    }
}   // namespace win

Adapter for callbacks without UserData parameter

Callbacks without UserData parameter

The techniques described so far depend heavily on the 32 bit UserData parameter passed to the C-style callback, because we need it to pass the instance of the adapter to the static function. Unfortunately there are some Win32 API calls that use callbacks, but do not provide a UserData parameter. The following table lists some of these API calls:

Prototypes of some Win32 API calls without UserData and their related callback functions
Win32 functionrelated callback function
BOOL EnumSystemCodePages(
��CODEPAGE_ENUMPROC pfEnumProc,
��DWORD dwFlags
)
BOOL CALLBACK EnumCodePagesProc(
��LPTSTR lpCodePageString
)
BOOL EnumSystemLocales(
��LOCALE_ENUMPROC pfEnumProc,
��DWORD dwFlags
)
BOOL CALLBACK EnumLocalesProc(
��LPTSTR lpLocaleString
)
BOOL EnumDateFormats(
��DATEFMT_ENUMPROC pfEnumProc,
��LCID Locale,
��DWORD dwFlags
)
BOOL CALLBACK EnumDateFormatsProc(
��LPTSTR lpDateFormatString
)

First I thought it would not be possible to integrate this kind of functions into the adapter concept. The whole concept is based on the fact that the adapter instance stores all necessary data, and the static callback gets a pointer to this instance. But there is no way to pass an instance pointer. What can we do?

I solved this using static member variables (class variables). However, using static member variables brings a lot of pitfalls, because static members are nothing else but global variables. The following chapters describe these pitfalls and how I came around them. This was the hardest part and, to be honest, I am a bit proud that I found an appropriate solution :-).

Passing the instance pointer as a static member

Okay, we have no parameter that we can use to pass the adapter instance pointer to the static callback function. But we need the adapter instance to call the stored member function. The only way out is to use some kind of global variable. Of course we do not use a "real" global variable, but a static data member (a class variable which is technical much the same as a global variable). The adapter class looks then as follows:

namespace adapter {
    template< class O, class U, class R, class A1 >
    struct StaticCB1 
    {
        StaticCB1( O* pObj, R (O::*pfCB)(A1, U), U UserData )
            : m_pObj( pObj ), m_pfCB( pfCB ), m_UserData( UserData )
        {
            StaticCB1::_this = this;
        }

        static R CALLBACK Callback( A1 arg1 )
        {
            return ( StaticCB1::_this->m_pObj->*StaticCB1::_this->m_pfCB)(arg1, 
                     StaticCB1::_this->m_UserData );
        }

        O*  m_pObj;
        CB  m_pfCB;
        U   m_UserData;

        static StaticCB1* _this;
    };
    
    template< class O, class U, class R, class A1 >
    StaticCB1<O, U, R, A1>* StaticCB1<O, U, R, A1>::_this = NULL;
}   // namespace adapter

Multithreading Issues

After I wrote the above lines the first question that came into my mind was: "What would happen in a multithreaded environment?" The answer is easy: It would not work. Consider two threads using the adapter simultaneously. Both try to store the adapters instance in the one and only StaticCB1::_this class variable. Bang!

One idea around this is to protect the class variable with some thread synchronization mechanism like a critical section. But this brings the danger of deadlocks and may result in poor performance on multiprocessor machines. So I decided to use thread local storage (TLS) instead. Variables declared as threadlocal are global on a per thread basis, meaning every thread has its own instance of the global variable, which is exactly what we need. To use TLS is quite easy, just declare the variable that should be located in TLS as __declspec(thread). The declaration of the static member in the class above looks then like __declspec(thread) static StaticCB1* _this.

Using callbacks from inside callbacks

Now the code works fine even if we used multiple threads simultaneously, because every thread uses its own instance of the static class variable. But, as usual, things are still a bit more complicated. It is also possible that we use it more than once simultaneously inside the same thread. This happens if our callback member function itself uses the adapter class for another API that uses callbacks. The following example shows such a scenario:

struct StaticTest
{
    BOOL EnumLocalesCB( LPTSTR pszLocale, LPTSTR )
    {
        adapter::StaticCB1<StaticTest, LPTSTR, BOOL, LPTSTR> 
                  adapter( this, &StaticTest::EnumCodePagesCB, pszLocale );
        ::EnumSystemCodePages( adapter.Callback, CP_INSTALLED );
        return TRUE;
    }

    BOOL EnumCodePagesCB( LPTSTR pszCP, LPTSTR pszLocale )
    {
        printf("Locale %s, Codepage %s\n", pszLocale, pszCP );
        return TRUE;
    }
};

void main ()
{
    StaticTest test;
    adapter::StaticCB1<StaticTest, LPTSTR, BOOL, LPTSTR>  
                  adapter( &test, &StaticTest::EnumLocalesCB, NULL );
    ::EnumSystemLocales( adapter.Callback, LCID_INSTALLED );
}

The main() function instantiates an adapter and uses it to redirect the EnumSystemLocales() callback function to the member function StaticTest::EnumLocalesCB(). There we start an enumeration of the system code pages, again using an adapter to redirect the callback to StaticTest::EnumCodePagesCB(). Do you see the problem?

The problem is, that both instantiations of the adapter::StaticCB1 template use the same template arguments (<StaticTest, LPTSTR, BOOL, LPTSTR>). In this case the compiler instantiates the template only once and reuses it for the second usage. In other words, both adapter instances are of the same class. Consequently they share all class data, especially our static _this member used to pass the current adapter instance to the adapters C-style callback function: The adapter instance in main() sets adapter::CB1<StaticTest, LPTSTR, BOOL, LPTSTR>::_this to its own address. Afterwards EnumSystemLocales() is called, which invokes (via the adapter) StaticTest::EnumLocalesCB() for the first locale. There another adapter instance is created and ...::_this is overidden. On EnumLocalesCB() return, the second adapter instance goes out of scope and is destroyed. However, ...::_this still points to its address! If EnumSystemLocales() invokes the callback for the next locale, an invalid adapter instance is used and the program probably crashes.

Forcing template instantiations

The above problem would not arise, if the template parameter list of the adapters differ. The compiler creates an own version of the template class (instantiates the template) for every unique combination of template parameters. So we have to ensure that we use every template parameter list only once. We can do this by adding another "dummy" template parameter that is not used inside the class, but differs for every template instantiation:

namespace adapter {
    template< class DUMMY, class O, class U, class R, class A1 >
    struct StaticCB1 
    {
        ... // as above

    };
    
    template< class DUMMY, class O, class U, class R, class A1 >
    StaticCB1<DUMMY, O, U, R, A1>* StaticCB1<DUMMY, O, U, R, A1>::_this = NULL;
}   // namespace adapter


struct S1 {};    // Dummy types

struct S2 {};

struct StaticTest
{
    BOOL EnumLocalesCB( LPTSTR pszLocale, LPTSTR )
    {
        adapter::StaticCB1<S1, StaticTest, LPTSTR, BOOL, LPTSTR> 
                        adapter( this, &StaticTest::EnumCodePagesCB, pszLocale );
        ::EnumSystemCodePages( adapter.Callback, CP_INSTALLED );
        ...
    }

    BOOL EnumCodePagesCB( LPTSTR pszCP, LPTSTR pszLocale )
    {
        ...
    }
};

void main ()
{
    StaticTest test;
    adapter::StaticCB1<S2, StaticTest, LPTSTR, BOOL, LPTSTR> 
                        adapter( &test, &StaticTest::EnumLocalesCB, NULL );
    ::EnumSystemLocales( adapter.Callback, LCID_INSTALLED );
}

However, this way we need a lot of dummy types which makes the code look a little bit strange. A better solution is to do not use an additional class template parameter, but an int template parameter. Then all we have to do is to pass an unique number for every template instantiation:

namespace adapter {
    template< int INSTANCE, class O, class U, class R, class A1 >
    struct StaticCB1 
    {
        ... // as above

    };
    
    template< int INSTANCE, class O, class U, class R, class A1 >
    StaticCB1<INSTANCE, O, U, R, A1>* StaticCB1<INSTANCE, O, U, R, A1>::_this = NULL;
}   // namespace adapter


struct StaticTest
{
    BOOL EnumLocalesCB( LPTSTR pszLocale, LPTSTR )
    {
        adapter::StaticCB1<4711, StaticTest, LPTSTR, BOOL, LPTSTR> adapter( this, 
                                    &StaticTest::EnumCodePagesCB, pszLocale );
        ...
    }
    ...
};

void main ()
{
    ...
    adapter::StaticCB1<0815, StaticTest, LPTSTR, BOOL, LPTSTR> adapter( &test, 
                          &StaticTest::EnumLocalesCB, NULL );
    ...
}

However, maintaining a list of unique number is also hard work. But we can "misuse" the C preprocessor to generate them automatically for us:

#undef OFFSET
#define OFFSET 10000
#define GENERATE_UNIQUE ( __LINE__ + OFFSET )

struct StaticTest
{
    BOOL EnumLocalesCB( LPTSTR pszLocale, LPTSTR )
    {
        adapter::StaticCB1<GENERATE_UNIQUE, StaticTest, LPTSTR, BOOL, LPTSTR> 
                      adapter( this, &StaticTest::EnumCodePagesCB, pszLocale );
        ...
    }
    ...
};

void main ()
{
    ...
    adapter::StaticCB1<GENERATE_UNIQUE, StaticTest, LPTSTR, BOOL, LPTSTR> 
                      adapter( &test, &StaticTest::EnumLocalesCB, NULL );
    ...
}

The trick is to use the __LINE__ preprocessor macro, which is replaced during preprocessing by the current line number. Because we never declare two adapters in the same source line, the number is unique. However, to be on the safe side under all circumstances, we add an offset that is specific for the source file. This ensures that it could never happen that the same number is generated twice even if we use the technique in multiple source files.

Conclusion

Adapters are a very powerful concept to make things play together that otherwise would not do so, because of incompatible interfaces. This approach is well known for making classes work together and denoted as the Adapter pattern. Even if a bit unusual, it can also applied on function level. The interface of a C++ member function differs in many points from the interface of an ordinary C function. However, using our adapter classes we can "translate" the C callback interface into the interface of a C++ member callback.

The win_adapter.h includes a powerful set of such adapters to support the very different kinds of callbacks used in the Win32 API. It also contains comfortable wrapper functions for almost every Win32 call that uses callbacks. However, the technique and the adapter classes itself are not restricted to the Win32 API. You can use them to redirect any C-style callback into a member function of your choice.

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