Динамически создавать общий указатель функции из указателя метода, выводя возвращаемое значение и параметры.

У меня есть этот вспомогательный класс, который я использую для вызова методов-членов для кода, ожидающего статических функций C. Эта конкретная «версия» совместима с обратными вызовами Windows LPTHREADROUTINE, принимая в качестве параметра функцию DWORD (class::method) (void *), вызываемую следующим образом:

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

Я хочу сделать все это универсальным, и я знаю, что это можно сделать с помощью нового стандарта С++ 11, но я не могу это осуществить.

#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);
}

Я попытался изменить интерфейс функции MakeThreadInfo примерно так:

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

Казалось бы, это правильный путь, но я не смог передать это значение вверх по течению.


Вот что я хочу получить:

Имея класс MyClass с методом MyMethod и обратный вызов типа возвращаемого значения variable и один или несколько параметров различных типов (последним из которых является void *userData), как можно Я, с минимальными шаблонами, насколько это возможно, передаю что-то обратному вызову, и он, в свою очередь, вызывает MyClass::MyMethod.

Проиллюстрировать:

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);
    }
}

Какая правильная реализация CreateGenericCPointer?


person Mahmoud Al-Qudsi    schedule 28.12.2011    source источник
comment
Какова цель сделать MakeThreadInfo универсальным? Вы хотите поддерживать функторы?   -  person outis    schedule 29.12.2011
comment
Я хочу использовать это в сочетании с библиотекой C, которая имеет дюжину различных форматов обратного вызова. Вместо того, чтобы дублировать этот класс для каждого, я хочу изменить его, чтобы автоматически выводить правильные возвращаемые значения и типы параметров для динамически создаваемого обратного вызова.   -  person Mahmoud Al-Qudsi    schedule 29.12.2011
comment
Вы хотите, чтобы MakeThreadInfo работал для методов, которые могут принимать аргументы, отличные от void*, и возвращать что-то, кроме DWORDs? Обратите внимание, что SO не является форумом, а комментарии не предназначены для обсуждения. Уточнения должны быть отредактированы в вопросе.   -  person outis    schedule 29.12.2011
comment
возможный дубликат метода класса как обратного вызова winAPI   -  person sbi    schedule 29.12.2011
comment
@sbi: нет, это не так. Смотрите мою правку.   -  person Mahmoud Al-Qudsi    schedule 29.12.2011


Ответы (3)


Мне не совсем понятно, какой уровень универсальности вы ищете, но, возможно, это поможет вам начать:

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
);

Обратите внимание, что поскольку myClass является std::shared_ptr<> и захватывается по значению, базовое время жизни SomeClass завершится должным образом, даже если myClass выйдет за пределы области действия до завершения выполнения потока (до тех пор, пока RunThreadFunction в конечном итоге вызывается).


EDIT: Вот еще один подход (непроверенный, могут быть синтаксические ошибки):

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
);
person ildjarn    schedule 28.12.2011
comment
Да, я ищу более общий, чем это. Смотрите мою правку, пожалуйста. В принципе, DWORD должен быть выведен, как и любые другие возможные параметры. - person Mahmoud Al-Qudsi; 29.12.2011
comment
@Mahmoud: все аргументы уже связаны внутри лямбды, поэтому std::function<> всегда будет нулевым; т. е. нет аргументов для вывода. Единственное, что осталось, — это тип возвращаемого значения, и его вывод принципиально невозможен, поскольку RunThreadFunction требуется конкретный тип для приведения к void*. Это уже настолько общее, насколько вы можете получить AFAIK. - person ildjarn; 29.12.2011
comment
ildjarn: Ваше обновление очень близко к тому, что я имел в виду. У меня было шаблонное возвращаемое значение в одной из моих реализаций, но я почти полностью уверен, что вариативные параметры также могут быть реализованы. Мне нужно бежать сейчас, но я думаю, что потрачу немного времени на это сегодня вечером, если ни у кого больше нет предложений. - person Mahmoud Al-Qudsi; 29.12.2011
comment
@Mahmoud Махмуд: Но зачем вам вариативные параметры? Лямбда вызывает вашу реальную базовую функцию-член с любыми необходимыми параметрами, а лямбда всегда имеет значение NULL, поэтому результирующее значение std::function<> всегда равно NULL. Что вам нужно, чего не делает лямбда? - person ildjarn; 29.12.2011
comment
Чтобы каждый раз явно не объявлять f. Хотя в качестве локальной переменной и еще чего-то, это более чем приемлемо, если это наилучшее из возможных, но я просто не могу не чувствовать, что есть решение, которое может пропустить этот шаг, что стало возможным благодаря полной спецификации С++ 11. - person Mahmoud Al-Qudsi; 29.12.2011
comment
@Mahmoud: IMO, любой шаблон, который вы придумаете, чтобы избежать явного объявления f каждый раз, будет гораздо более подробным, чем объявление простой лямбды (если только f каждый раз не идентично, и в этом случае просто создайте правильный функтор). Но мне любопытно посмотреть, придумает ли кто-нибудь что-нибудь умное. :-] - person ildjarn; 29.12.2011
comment
Мне удалось добиться этого с помощью автоматического создания функций, включая вывод полных подписей (возврат + все параметры) с использованием общих шаблонов с переменным числом аргументов, но только в одном случае, когда параметром first был 'userData'. последнего (поскольку переменные параметры должны быть последними в функции). Бесполезно для меня, к сожалению. - person Mahmoud Al-Qudsi; 29.12.2011

Посмотрите, хотите ли вы этого. Вам все еще нужно указать тип возвращаемого значения, но он прекрасно (на мой взгляд) указан как параметр шаблона для struct, который содержит static функции-оболочки. Вы все еще можете улучшить его, если вам нужна более высокая степень гибкости для TTst. Я не уверен, как вы хотите определить вызываемые функции-члены, поэтому я сохранил их подпись как callback.

#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 );
}

РЕДАКТИРОВАТЬ: изменены аргументы для Bar01 - я не заметил, что он принимает 2 аргумента как Bar00... Просто чтобы уточнить, вам нужно определить одну шаблонную функцию Call для всех Callback, которые имеют одинаковое количество аргументов.

person lapk    schedule 29.12.2011

EDIT: Это не совсем то, что нужно OP. ОП нуждается в общей версии этого.

Почему бы не использовать std::function полностью?

Это будет выглядеть как

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

runStdFunction будет таким же простым, как пирог

Я использую typedef std::function<void(void)> voidFunction для простоты.

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

Идея проста, все, что вы делаете, это передаете std::functions, а затем вызываете их. Оставьте все преобразования в/из функций-членов/и т.д. в std::bind.

Конечно, ваш возвращаемый тип не пуст, но это незначительная деталь.

person Lalaland    schedule 28.12.2011
comment
Посмотрите мое редактирование, почему это не идеальное решение, которое я ищу. - person Mahmoud Al-Qudsi; 29.12.2011
comment
@MahmoudAl-Qudsi О, я вижу, вам нужна универсальная версия runStdFunction. Это гораздо более интересный (и сложный) вопрос. Бьюсь об заклад, правильный ответ будет включать сложные вариативные шаблоны. - person Lalaland; 29.12.2011