Passing static member function as callback to Windows DLL

Discussion in 'C++' started by Evan Burkitt, Aug 24, 2007.

  1. Evan Burkitt

    Evan Burkitt Guest

    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
     
    Evan Burkitt, Aug 24, 2007
    #1
    1. Advertising

  2. Evan Burkitt wrote:
    > 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 );
    }
     
    Gianni Mariani, Aug 24, 2007
    #2
    1. Advertising

  3. Evan Burkitt

    Evan Burkitt Guest

    "Gianni Mariani" <> wrote in message
    news:46ce84dd$0$27809$...
    > 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
     
    Evan Burkitt, Aug 24, 2007
    #3
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Newsgroup - Ann
    Replies:
    5
    Views:
    627
    John Carson
    Jul 30, 2003
  2. 0to60
    Replies:
    4
    Views:
    529
    jeffc
    Nov 21, 2003
  3. aling
    Replies:
    6
    Views:
    490
    Xiaobin.Huang
    Oct 30, 2005
  4. dolphin
    Replies:
    3
    Views:
    1,362
    Pete Becker
    Dec 5, 2007
  5. Angus
    Replies:
    3
    Views:
    698
    James Kanze
    Mar 2, 2009
Loading...

Share This Page