2

I have this helper class that I use to call member methods for code that's expecting static C functions. This particular "version" is compatible with Windows LPTHREADROUTINE callbacks, taking a DWORD (class::method) (void *) function as a parameter, called like this:

CreateThread(NULL, 0, runThreadFunction<SomeClass>, makeThreadInfo(&myClass, &SomeClass::ThreadFunction, NULL), 0, NULL);

I wish to make the entire thing generic, and I know it can be done with the new C++11 standard, but I'm unable to pull it off.

#pragma once
#include <stdafx.h>

template <typename C>
struct ThreadInfo
{
    // we have an object
    C* obj;
    // and that object has a function that takes void* and returns DWORD, and so is suitable for a threadproc (except for it being a member function)

    DWORD (C::* function)(void*);
    // and we have any amount of extra data that we might need.

    void* data;
    // default copy c-tor, d-tor and operator= are fine

    ThreadInfo(C* o, DWORD (C::*func)(void*), void* d) : obj(o), function(func), data(d)
    {
    }
};

template <typename C>
DWORD WINAPI RunThreadFunction(void* data)
{
    shared_ptr<ThreadInfo<C> > ti((ThreadInfo<C>*)data);
    //ThreadInfo<C>* ti = (ThreadInfo<C>*) data;
    return ((ti->obj)->*(ti->function))(ti->data);
}

template <typename C>
void* MakeThreadInfo(C* o, DWORD (C::* f)(void*), void* d)
{
    return (void*)new ThreadInfo<C>(o, f, d);
}

I've tried changing the interface of the MakeThreadInfo function to something like this:

template <typename C, typename R, typename... P>
void* MakeThreadInfo(C* o, std::function<R(P&...)> f, void* d)

Which would seem to be the way to go, but I was unable to then pass this value upstream.


Here's what I want to get at:

Given a class MyClass with a method MyMethod, and a callback of variable return type and one or more parameters of varying types (the last of which is a void *userData), how can I, with as little boilerplating as possible, pass something to the callback and have it in turn call MyClass::MyMethod.

To illustrate:

typedef bool (*Callback1)(void *userData);
typedef int  (*Callback2)(bool param, void *userData);

void TheirLibrary::Function1(Callback1 callback, void *userData);
void TheirLibrary::Function2(Callback2 callback, void *userData);

class MyClass
{
    bool MyMethod1(void *userData);
    int  MyMethod2(bool someParam, void *userData);

    void DoSomething()
    {
        Function1(CreateGenericCPointer(&MyClass::MyMethod1), &MyClass);
        Function2(CreateGenericCPointer(&MyClass::MyMethod2), &MyClass);
    }
}

What's a valid implementation of CreateGenericCPointer?

Mahmoud Al-Qudsi
  • 28,357
  • 12
  • 85
  • 125
  • What's the goal of making `MakeThreadInfo` generic? Do you want to support functors? – outis Dec 28 '11 at 22:12
  • I want to use this in conjunction with a C library that has a dozen different callback formats. Instead of duplicating this class for each, I want to modify it to automatically deduce the correct return and parameter types for the dynamically-created callback. – Mahmoud Al-Qudsi Dec 28 '11 at 22:15
  • Do you mean you want `MakeThreadInfo` to work for methods that may take arguments other than `void*` and return things other than `DWORD`s? Note that SO isn't a forum and comments aren't for discussion. Clarifications should be edited into the question. – outis Dec 28 '11 at 22:39
  • possible duplicate of [Class method as winAPI callback](http://stackoverflow.com/questions/3725425/class-method-as-winapi-callback) – sbi Dec 28 '11 at 22:48
  • @sbi: no, it's not. See my edit. – Mahmoud Al-Qudsi Dec 28 '11 at 22:53
  • [Use member functions for C-style callbacks and threads - a general solution](http://www.codeproject.com/KB/winsdk/callback_adapter.aspx) – Maxim Egorushkin Dec 28 '11 at 23:40

4 Answers4

1

It's not entirely clear to me what level of genericity you're looking for, but maybe this will get you started:

typedef std::function<DWORD()> ThreadFuncT;

DWORD WINAPI RunThreadFunction(void* data)
{
    std::unique_ptr<ThreadFuncT> tf(static_cast<ThreadFuncT*>(data));
    return (*tf)();
}

template<typename F>
ThreadFuncT* MakeThreadFunction(F&& f)
{
    return new ThreadFuncT(std::forward<F>(f));
}

// ...

auto myClass = std::make_shared<SomeClass>(/* ... */);
CreateThread(
    nullptr,
    0,
    RunThreadFunction,
    MakeThreadFunction([=]() { return myClass->ThreadFunction(nullptr); }),
    0,
    nullptr
);

Note that because myClass is a std::shared_ptr<> and is captured by value, the underlying SomeClass's lifetime will terminate properly even if myClass goes out of scope before the thread is finished executing (as long as RunThreadFunction is eventually called).


EDIT: Here's another approach (untested, may be syntax errors):

template<typename R>
R WINAPI RunThreadFunction(void* data)
{
    typedef std::function<R()> ThreadFuncT;
    std::unique_ptr<ThreadFuncT> tf(static_cast<ThreadFuncT*>(data));
    return (*tf)();
}

template<typename F>
auto MakeThreadFunction(F&& f) -> std::function<decltype(f())()>*
{
    return new std::function<decltype(f())()>(std::forward<F>(f));
}

// ...

auto myClass = std::make_shared<SomeClass>(/* ... */);
auto f = [=]() { return myClass->ThreadFunction(nullptr); };
CreateThread(
    nullptr,
    0,
    RunThreadFunction<decltype(f())>,
    MakeThreadFunction(std::move(f)),
    0,
    nullptr
);
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • Yeah, I'm looking for more generic than that. See my edit, please. Basically, DWORD should be deduced, as well as any other possible params. – Mahmoud Al-Qudsi Dec 28 '11 at 22:53
  • @Mahmoud : All arguments are already bound inside the lambda, so the `std::function<>` will always be nullary; i.e., there _are no_ arguments to deduce. The only thing remaining is the return type, and deducing that is not fundamentally possible since `RunThreadFunction` needs a concrete type to cast to from `void*`. This is already about as generic as you can get AFAIK. – ildjarn Dec 28 '11 at 22:54
  • ildjarn: your update brings it very close to what I had in mind. I had the templated return value in one of my implementations, but I'm almost perfectly certain that variadic parameters can also be implemented. I have to run out now, but I think I'll spend some time on this tonight if no one else has any suggestions. – Mahmoud Al-Qudsi Dec 28 '11 at 23:51
  • @Mahmoud : But, why do you need variadic parameters? The lambda calls your real underlying member function with any necessary parameters and the lambda is always nullary, so the resulting `std::function<>` is always nullary. What do you need that the lambda doesn't accomplish? – ildjarn Dec 29 '11 at 00:03
  • To avoid having to explicitly declare `f` each time. Although as a local variable and whatnot, it's more than acceptable if this is the best possible, but I just can't help but feel that there is a solution that can skip this step made possible by the full C++11 spec. – Mahmoud Al-Qudsi Dec 29 '11 at 02:43
  • @Mahmoud : IMO, whatever boilerplate you come up with to avoid explicitly declaring `f` each time will be far more verbose than declaring a simple lambda (unless `f` is identical each time, in which case just make a proper functor). But I'm curious to see if anyone comes up with something clever. :-] – ildjarn Dec 29 '11 at 02:47
  • I was able to pull it off with automatic creation of functions including deduction of complete signatures (return + all params) using generic variadic templates, but only for the one case where 'userData' was the *first* parameter instead of the last (because the variadic params must be the last in the function). Useless to me, unfortunately. – Mahmoud Al-Qudsi Dec 29 '11 at 18:45
1

See if this is what you want. You still have to specify return type, but it's nicely (in my opinion) specified as a template parameter to struct that hold static wrapper functions. You can still improve it if you need higher degree of flexibility for TTst - I'm not sure how you want to define member functions to be called, so I kept their signature as callback's.

#include <iostream>

typedef int (*TFoo00)( void * );
typedef bool (*TFoo01)( int, void * );

void Bar00( TFoo00 fnc, void * ud )
{
 std::cout << "Bar00" << std::endl;
 fnc( ud );
}
void Bar01( TFoo01 fnc, void * ud )
{
 std::cout << "Bar01 " << std::endl;

 fnc( -1, ud );
}

class TTst;

template< typename PResult >
struct TWrap
{

  static PResult Call( void * ud )
  {
   std::cout << "TWrap::Call( P00 )" << std::endl;
   return ( static_cast< TTst * > ( ud )->Foo00() );
  }
  template< typename P00 >
  static PResult Call( P00 u00, void * ud )
  {
   std::cout << "TTst::Call( P00, P01 )" << std::endl;
   return ( static_cast< TTst * > ( ud )->Foo01( u00 ) );
  }
};

class TTst
{
 public:
  int Foo00( void )
  {
   std::cout << "TTst::Foo00" << std::endl;
   return ( 0 );
  }
  bool Foo01( int u00 )
  {
   std::cout << "TTst::Foo01 : "  << u00 << std::endl;
   return ( u00 != 0 );
  }

  void Do( void )
  {
   Bar00( TWrap< int >::Call, this );
   Bar01( TWrap< bool >::Call, this );
  }

};

int main( void )
{
 TTst lT;

 lT.Do();

 return ( 0 );
}

EDIT: modified arguments to Bar01 - I didn't notice it accepts 2 arguments as Bar00... Just to clarify, you need to define one templated Call function for all Callback's that have the same number of arguments.

lapk
  • 3,838
  • 1
  • 23
  • 28
0

I had the same problem. I wanted to cast "pointer to a class member method" to "pointer to a function" as Windows API callbacks are all "pointer to a function". I work with X86-64 compiler and there is only one calling conventions. When using "pointer to a class member method", the first parameter passed to the function is "this", so when you want to cast it to a "pointer to a function" you have to consider "this" to be passed to your "pointer to a class member method" as the first argument and then the other arguments are passed. My method does this by creating executable binary code in virtual address space on the fly. Remember that it works with functions with at most 3 parameters and also it does not support floating-point arguments as the input arguments of functions. (Because floating-point arguments are transferred by XMM0, XMM1, XMM2, XMM3 registers).

#include <windows.h>
#include <cstdio>

const byte jumper[] = {
    // mov r9,r8; mov r8,rdx; mov rdx,rcx
    0x4D, 0x89, 0xC1, 0x49, 0x89, 0xD0, 0x48, 0x89, 0xCA, 
    // movabs rcx,0x123456789abcdef; this = 0x123456789abcdef;
    0x48, 0xB9,
    0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, 
    // movabs rax,0x123456789abcdef; function_pointer = 0x123456789abcdef
    0x48, 0xB8, 
    0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, 
    // jmp    rax
    0xFF, 0xE0
};

template <typename TSrc, typename TSrcThis, typename TDest>
class CallbackConvertor {
    typedef union  {
        void* pointer;
        byte* byte_pointer;
        TSrc src;
        TDest dest;    
    } FunctionPointer;
    public:
        FunctionPointer dest;
        CallbackConvertor(TSrc src, TSrcThis& self) {
            dest.pointer = VirtualAlloc(NULL,sizeof(jumper), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
            memcpy(dest.pointer,jumper,sizeof(jumper));
            void* s = &self;
            memcpy( &dest.byte_pointer[11]  , &s, 8);
            FunctionPointer pSrc = {.src = src};
            //pSrc.src = src;
            memcpy( &dest.byte_pointer[21] ,&pSrc.pointer,8);
        }
        ~CallbackConvertor() {
            VirtualFree(dest.pointer,0,MEM_RELEASE);
        }
        TDest Result() {
            return dest.dest;
        }
};


class TestClass {
    public:
        int data = 0;
        TestClass() {
            data = 10;
        }
        int OneParameter(int First){
            printf("Called with %d and this.data = %d\n",First,data);
            return 101;
        }

        int TwoParameter(int First, int Second){
            printf("Called with (%d,%d) and this.data = %d\n",First,Second,data);
            return 102;
        }
        int ThreeParameter(int First, int Second, int Third){
            printf("Called with (%d,%d,%d) and this.data = %d\n",First,Second,Third,data);
            return 103;
        }
};

int main() {
    TestClass test;
    CallbackConvertor<int(TestClass::*)(int),TestClass,int(*)(int)> OneParam(&test.OneParameter,test);
    int p = OneParam.Result()(11);
    printf("Result = %d\n",p);
    test.data = 2;
    OneParam.Result()(12);
    CallbackConvertor<int(TestClass::*)(int,int),TestClass,int(*)(int,int)> TwoParam(&test.TwoParameter,test);
    TwoParam.Result()(13,14);
    test.data = 3;
    TwoParam.Result()(15,16);
    CallbackConvertor<int(TestClass::*)(int,int,int),TestClass,int(*)(int,int,int)> ThreeParam(&test.ThreeParameter,test);
    ThreeParam.Result()(17,18,19);
    test.data = 4;
    ThreeParam.Result()(20,21,22);
}
0

EDIT: This is not quite what the OP needs. The OP needs a generic version of this.

Why not use a std::function all the way through?

It would look like

std::function<void(void)> myFunc = std::bind(&Class::memberFunc,&classInstance);
CreateThread(NULL,0,runStdFunction, new std::function<void(void)>(myFunc),0,NULL);

runStdFunction will then be as simple as pie

I am using typedef std::function<void(void)> voidFunction for simplicity.

void runStdFunction(void *data)
{
   std::unqiue_ptr<voidFunction> func (static_cast<voidFunction*>(data));
   (*func)();
}

The idea is simple, all you are doing is passing std::functions through and then calling them. Leave all of the converting to/from member functions/etc to std::bind.

Of course your return type is not void, but that's a minor detail.

  • See my edit for why this isn't the ideal solution I'm looking for. – Mahmoud Al-Qudsi Dec 28 '11 at 22:52
  • 1
    @MahmoudAl-Qudsi Oh I see, you want a generic version of runStdFunction. That's a much more interesting(and difficult) question. I am betting the correct answer is going to involve complex variadic templates. –  Dec 28 '11 at 23:00