Passing static member function as callback to Windows DLL

E

Evan Burkitt

Hi, all.

I have a Windows DLL that exports a number of functions. These functions
expect to receive a pointer to a callback function and an opaque void*
parameter. The callback functions are typedef'd to take void* parameters,
through which the DLL's function passes its void* parameter to the callback
function. This allows me to use class instances as callback handlers, as in
the example below.

//Executable and DLL know this:
typedef void (*callback)(void*);

//in DLL "Exporter.dll":
void ExportedFn(callback cb, void *opaque) { cb(opaque); }

//in executable:
class CallbackHandler
{
public:
static void Thunk(void *fromDll)
{
CallbackHandler *handler = static_cast<CallbackHandler*>(fromDll);
...
}
};
....
HANDLE dllInst = ::LoadLibrary("Exporter.dll");
typedef void (*DllExport)(callback, void*);
DllExport exportedFn = reinterpret_cast<DllFn>:):GetProcAddress(dllInst,
"ExportedFn"));
CallbackHandler handlerInst;
exportedFn(CallbackHandler::Thunk, &handlerInst); /**/
....

What bothers me is that CallbackHandler::Thunk() must have a void* parameter
to match the callback signature, even though it's part of the
CallbackHandler class and its fromDll parameter is and must be a
CallbackHandler*.

My question is, is there a 'best practices' way to let Thunk()s parameter be
a CallbackHandler* and yet have the line marked with /**/ compile? By 'best
practice', I mean less of a blunt instrument than reinterpret_cast<> or a
C-style cast.

-Evan
 
G

Gianni Mariani

Evan said:
Hi, all.

I have a Windows DLL that exports a number of functions. These functions
expect to receive a pointer to a callback function and an opaque void*
parameter. The callback functions are typedef'd to take void* parameters,
through which the DLL's function passes its void* parameter to the callback
function. This allows me to use class instances as callback handlers, as in
the example below.

//Executable and DLL know this:
typedef void (*callback)(void*);

//in DLL "Exporter.dll":
void ExportedFn(callback cb, void *opaque) { cb(opaque); }

//in executable:
class CallbackHandler
{
public:
static void Thunk(void *fromDll)
{
CallbackHandler *handler = static_cast<CallbackHandler*>(fromDll);
...
}
};
...
HANDLE dllInst = ::LoadLibrary("Exporter.dll");
typedef void (*DllExport)(callback, void*);
DllExport exportedFn = reinterpret_cast<DllFn>:):GetProcAddress(dllInst,
"ExportedFn"));
CallbackHandler handlerInst;
exportedFn(CallbackHandler::Thunk, &handlerInst); /**/
...

What bothers me is that CallbackHandler::Thunk() must have a void* parameter
to match the callback signature, even though it's part of the
CallbackHandler class and its fromDll parameter is and must be a
CallbackHandler*.

My question is, is there a 'best practices' way to let Thunk()s parameter be
a CallbackHandler* and yet have the line marked with /**/ compile? By 'best
practice', I mean less of a blunt instrument than reinterpret_cast<> or a
C-style cast.

If you're stuck with having to call ::GetProcAddress and pull names from
a DLL, so be it, however there are much more elegant ways of doing this.
One way of looking at this is that the DLL contains a number of
factories. Upon loading, the factories automagically register
themselves and you can avoid all of the casts. I use this technique
with DLL's and it's portable - no need to worry about mangled names.

Below is a suggestion - it uses templates to create yet another function
that calls the function you want. There are some limitations.

#include <iostream>

//Executable and DLL know this:
typedef void (*callback)(void*);

//in DLL "Exporter.dll":
void ExportedFn(callback cb, void *opaque) { cb(opaque); }

template <typename T, void (*F)( T* )>
struct Thunker
{
static void Do( void * fromDll )
{
F( static_cast<T*>(fromDll) );
}
};

//in executable:
class CallbackHandler
{
public:
static void Thunk(CallbackHandler *handler)
{
std::cout << "callback handler called\n";
}
};

typedef void (*DllExport)(callback, void*);

template <typename T, void (*F)( T* )>
void CallDll( T * ptr, DllExport exfn )
{
exfn( & Thunker<T,F>::Do, static_cast<void *>( ptr ) );
}

//HANDLE dllInst = ::LoadLibrary("Exporter.dll");


DllExport exportedFn = ExportedFn;

CallbackHandler handlerInst;

int main()
{
CallDll<CallbackHandler,&CallbackHandler::Thunk>( & handlerInst,
exportedFn );
}
 
E

Evan Burkitt

Gianni Mariani said:
If you're stuck with having to call ::GetProcAddress and pull names from a
DLL, so be it, however there are much more elegant ways of doing this. One
way of looking at this is that the DLL contains a number of factories.
Upon loading, the factories automagically register themselves and you can
avoid all of the casts. I use this technique with DLL's and it's
portable - no need to worry about mangled names.

Yes, this DLL exposes only a C interface, which will eventually be called by
non-C/C++ languages. I'm constrained to only use simple types for function
parameter lists and return types.
Below is a suggestion - it uses templates to create yet another function
that calls the function you want. There are some limitations.

#include <iostream>

//Executable and DLL know this:
typedef void (*callback)(void*);

//in DLL "Exporter.dll":
void ExportedFn(callback cb, void *opaque) { cb(opaque); }

template <typename T, void (*F)( T* )>
struct Thunker
{
static void Do( void * fromDll )
{
F( static_cast<T*>(fromDll) );
}
};

//in executable:
class CallbackHandler
{
public:
static void Thunk(CallbackHandler *handler)
{
std::cout << "callback handler called\n";
}
};

typedef void (*DllExport)(callback, void*);

template <typename T, void (*F)( T* )>
void CallDll( T * ptr, DllExport exfn )
{
exfn( & Thunker<T,F>::Do, static_cast<void *>( ptr ) );
}

//HANDLE dllInst = ::LoadLibrary("Exporter.dll");


DllExport exportedFn = ExportedFn;

CallbackHandler handlerInst;

int main()
{
CallDll<CallbackHandler,&CallbackHandler::Thunk>( & handlerInst,
exportedFn );
}

In my case I can use your Thunker struct on the caller's side. Although for
completeness my example included a call to the callback as well, my
implementation only needs to supply a function pointer suitable (to the
compiler) for the signature of the callback. The actual call to it is made
from the DLL, which believes the parameter to be a void* anyway.

Using terminology from our examples, what I did boils down to:
ExportedFn(Thunker<CallbackHandler, &CallbackHandler::Thunk>::Do,
&handlerInst);
This is still essentially a typecast, but wrapped up in such a way as to
cause compiler errors if the signature of CallbackHandler::Thunk() changes,
which was my biggest objection to brute-force casting the function pointer
itself.

Working through this has forced me to learn a bit more about template usage.
I appreciate your help.

-Evan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top