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
{
BOOL WindowLister( HWND hWnd, const DATA& data )
{
printf(" Window: %p\n", hWnd );
return true;
}
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;
win::EnumWindows( &test, &Test::WindowLister, someData );
printf("\n");
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
{
int foo( double data );
static int _stdcall bar(double data);
};
void main()
{
typedef int (_stdcall* PFBAR)(double);
typedef int (B::*PFFOO)(double);
PFBAR Bar = &B::bar;
PFFOO Foo = &B::foo;
int n1 = Bar( 47.11 );
B Obj;
B* pObj = &Obj
int n2 = (Obj.*Foo) ( 47.11 );
int n3 = (pObj->*Foo) ( 47.11 );
}
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 )
{
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:
- We lose the UserData parameter, because it is now used for the
this
pointer.
- The class gets cluttered with lots of adapter functions.
- 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.
- 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 )
{
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 )
{
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 function | related 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:
- All callbacks use the same calling convention. (
CALLBACK
, which is defined as
_stdcall
in the windows headers.)
- 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.
- The argument and return types of the callbacks vary.
- 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;
};
}
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 )
{
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 ) );
}
}
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 );
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 )
{
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
{
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:
HeapCB1( O* pObj, R (O::*pfCB)(U), U UserData )
: m_pObj( pObj ), m_pfCB( pfCB ), m_UserData( UserData ) {}
};
}
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 >
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 >
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 )
);
}
}
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 function | related 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;
}
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
{
...
};
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;
}
struct S1 {};
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
{
...
};
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;
}
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.