Указатель функции-члена C++ на глобальный указатель функции

Мне нужно решить, по крайней мере для меня, каверзную задачу на C++. Есть dll, которую я не могу изменить. Он получает указатель на функцию в качестве аргумента. Если я передаю указатель на глобальную функцию, все работает нормально. К сожалению, существует список объектов того же класса для передачи в dll. В С# я решил это с помощью делегатов. Как это можно сделать на С++? Использование std::function не работает. При этом во время выполнения возникают ошибки соглашения о кодировании. Дальнейшее использование MSVC2010 было бы оптимальным. Я написал образец, который описывает проблему:

#include <stdio.h>


// global function which works
void __stdcall task_global(float x, float y) { printf("Called global function with: %f %f\n", x, y); }
typedef void(__stdcall *f_pointer)(float, float);



// try of using a member function
class BaseTask {
public:
    virtual void __stdcall task(float x, float y) = 0;
};


class ListeningTask :public BaseTask {
public:
    void __stdcall task(float x, float y) { printf("Called this member function with: %f %f\n", x, y); }
};

typedef void (BaseTask::*member_f_pointer)(float, float);



// the dll to use
class RegisterTask {
public:
    // no posibility to access or modify!
    void __stdcall subscribe(f_pointer fp) { fp(1.0f, 2.0f); }
    // just for demonstration how to use a member function pointer
    void __stdcall subscribeMemberDemo(member_f_pointer mfp) {  /*how to use mfp?*/};
};



int main() {

    RegisterTask register_task{};

    // use global function
    f_pointer pointer_to_global_task = task_global;
    register_task.subscribe(pointer_to_global_task);


    /*---------------------------------------------------------------*/
    // use member function?
    std::list<ListeningTask> listening_task_list;
    for(int i = 0; i < 10; i++) {
        listening_task_list.push_back(ListeningTask lt);
        member_f_pointer pointer_to_member_task = &listening_task_list.back().task; //error C2276: '&': illegal operation on bound member function expression
        register_task.subscribeMemberDemo(pointer_to_member_task);

        // the tricky and important one to solve
        // how to pass the member function to this subscribe(f_pointer)?
        register_task.subscribe(pointer_to_member_task);
    }

    getchar();
    return 0;
}

Важный вопрос заключается в том, как передать указатель функции-члена в RegisterTask::subscribe(f_pointer)?

Вопрос в скобках: как передать функцию-член в RegisterTask::subscribeMemberDemo(member_f_pointer)?

Я надеюсь, что кто-то может помочь мне решить эту проблему? Я работаю над этим со дня.

Изменить: я изменил вопрос, чтобы подчеркнуть проблему со списком ListenerTask. Как передать указатель функции-члена, теперь мне ясно из ответов @pokey909 и @AndyG. Оба они предоставляют указатель на один объект или, скорее, на список объектов. Если обратный вызов вызывается, то один ListenerTask или все std::list<*ListenerTask> вызываются одновременно. Но как сделать, чтобы вызывался только один ListenerTask из списка. Передача нескольких обратных вызовов в dll. Он (RegisterTask) может это сделать, потому что работает следующий пример с глобальными функциями.

void __stdcall task_global_1(float x, float y) { printf("Called global function 1 with: %f %f\n", x, y); }
void __stdcall task_global_2(float x, float y) { printf("Called global function 2 with: %f %f\n", x, y); }
void __stdcall task_global_3(float x, float y) { printf("Called global function 3 with: %f %f\n", x, y); }

typedef void(__stdcall *f_pointer)(float, float);

int main() {
    // give the functions to the dll.
    f_pointer pointer_to_global_task_1 = task_global_1;
    register_task.subscribe(pointer_to_global_task_1);

    f_pointer pointer_to_global_task_2 = task_global_2;
    register_task.subscribe(pointer_to_global_task_2);

    f_pointer pointer_to_global_task_3 = task_global_3;
    register_task.subscribe(pointer_to_global_task_3);
}

Есть три глобальных указателя функций. Все они отданы dll. Теперь, если у dll есть задача на task_global_2, она уведомляет только об этом! Как добиться этого различия с помощью указателя на функцию-член?

Примечание. Я получил источник dll. Надеюсь это поможет. К сожалению модификация, сборка невозможна. Вот определение обратного вызова:

type TCallback = procedure( x : single; y : single; ); stdcall;

procedure subscribe(aCallback: TCallback ); StdCall;
begin
  TaskSocket.addTask( aCallback );
end;

procedure TSocket.addTask( aCallback : TCallback);
var newTask : TTask;
begin
  newTask := TTask.Create(aCallback);
  TaskList.addItem(newTask);
end;

person solarisx    schedule 19.09.2016    source источник
comment
Без каких-либо функций C++11 это будет на удивление сложно для вас. В основном вы захотите реализовать форму std::bind   -  person AndyG    schedule 20.09.2016
comment
Вы не можете. Скрытый параметр this доставит вас. Вместо этого передайте статический метод, который вызывает метод, который вы хотите выполнить, в dll. Вам придется найти какой-то способ узнать, какой объект вызывать в статическом методе. Если у вас есть только один экземпляр объекта, это будет легко. Если не...   -  person user4581301    schedule 20.09.2016
comment
Почему RegisterTask::subscribe не объявлен статическим? Он не использует this.   -  person Barmar    schedule 20.09.2016
comment
Я подозреваю, что вы абстрагировались от подробностей об указателе функции обратного вызова. Пожалуйста, укажите точную подпись, если вы еще этого не сделали. Не то, что вы думаете, это importsnt, точная подпись переданного указателя функции. И функция, которая его принимает. (Минимизация — это хорошо, но сложно!)   -  person Yakk - Adam Nevraumont    schedule 20.09.2016
comment
Якк имеет большое значение. Часто регистрации обратного вызова принимают void *, который будет использоваться в качестве параметра функции обратного вызова, чтобы можно было мультиплексировать несколько получателей. Это быстро решает вопрос Какой объект я должен вызывать? проблема.   -  person user4581301    schedule 20.09.2016
comment
@Barmar У меня пока мало подробностей о классе RegisterTask У меня есть только dll.   -  person solarisx    schedule 20.09.2016
comment
@solarisx Неважно, я неправильно понял, какую функцию вы пытались передать. Я думал, вы пытаетесь превратить subscribe в указатель на функцию, а не передавать ему указатель на функцию в качестве аргумента.   -  person Barmar    schedule 20.09.2016
comment
@Yakk Судя по тому, что он опубликовал, функция обратного вызова должна принимать два аргумента float. Таким образом, нет аргумента void *, который может содержать указатель на объект.   -  person Barmar    schedule 20.09.2016
comment
@Yakk Вы имеете в виду, можно ли передать void(*)(float,float) в subscribe? Я думаю, что это. В настоящее время я вне офиса, поэтому я буду тестировать его завтра. Как использовать его с void(*)(float, float)?   -  person solarisx    schedule 20.09.2016
comment
@solarisx Он предполагал, что подпись может быть void (*f)(void*, float, float), где аргумент void* является непрозрачным указателем, который будет использоваться функцией обратного вызова.   -  person Barmar    schedule 20.09.2016
comment
@Barmar Я думаю, что должен быть непрозрачный указатель. Потому что я могу использовать эту dll в C# с такими делегатами, как public delegate void TaskDelegate(float x, float y);, которые указывают на объекты.   -  person solarisx    schedule 20.09.2016


Ответы (2)


Вы можете использовать автономную функцию, которая вызывает оболочку, которая привязывает к ней ваш экземпляр. Вот грубый пример

#include <iostream>
#include <string>
#include <functional>

// global function which works
std::function<void(float, float)> memberCb;
void task_global(float x, float y) { memberCb(x, y); }
typedef void(*f_pointer)(float, float);


// try of using a member function
class BaseTask {
public:
    virtual void  task(float x, float y) = 0;
};


class ListeningTask :public BaseTask {
public:
    void  task(float x, float y) { printf("Called this member function with: %f %f\n", x, y); }
};

typedef void (BaseTask::*member_f_pointer)(float, float);

void callbackWrapper(BaseTask* t, float x, float y) { t->task(x, y); }

// the dll to use
class RegisterTask {
public:
    // no posibility to access or modify!
    void  subscribe(f_pointer fp) { 
        fp(1.0f, 2.0f); 
    }
    // just for demonstration how to use a member function pointer
    void  subscribeMemberDemo(member_f_pointer mfp) {  /*???*/ };
};



int main() {

    RegisterTask register_task{};

    ListeningTask listening_task{};
    memberCb = std::bind(&callbackWrapper, &listening_task, std::placeholders::_1, std::placeholders::_2);
    register_task.subscribe(task_global);

    return 0;
}

Примечание

Основываясь на комментарии, я не уверен, что все это работает в MSVC2010, так как у меня нет этой версии компилятора. Но элементарная поддержка C++11 должна быть.

Изменить

Я не уверен, что это то, что вам нужно, но решит ли это вашу проблему?

void callbackWrapper(const std::list<BaseTask*> &taskList, float x, float y) {
    for (auto t : taskList)
        t->task(x, y); 
}

int main() {

    RegisterTask register_task{};

    std::list<BaseTask*> taskList;
    for (int i = 0; i < 4; ++i)
        taskList.push_back(new ListeningTask);
    memberCb = std::bind(&callbackWrapper, taskList, std::placeholders::_1, std::placeholders::_2);
    register_task.subscribe(task_global);

    return 0;
}

Редактировать 2 Хорошо, я понял, что вы хотите. Лучшее, что я могу придумать, не разбрызгивая ваш код глобальными функциями вручную, — это магия шаблонов. Однако обратите внимание, что он не такой гибкий, как вам хотелось бы, потому что вам нужно привязать эти методы во время компиляции. Если вам нужно добавить их во время выполнения, вы, вероятно, можете использовать тот же прием, но без шаблонов. Просто поместите все объекты std::function в вектор и заверните его в синглтон или что-то подобное.

#include <iostream>
#include <string>
#include <functional>
#include <list>


/* Simulated DLL */
typedef void(*f_pointer)(float, float);
class RegisterTask {
public:
    void  subscribe(f_pointer fp) {
        fp(1.0f, 2.0f);
    }
};



/* Static function generator to ease the pain to define all of them manually */
template<unsigned int T>
std::function<void(float, float)> &getWrapper() {
    static std::function<void(float, float)> fnc;
    return fnc;
}

/* Same here */
template<unsigned int T>
void task_global(float x, float y) { getWrapper<T>()(x, y); }



class BaseTask {
public:
    virtual void  task(float x, float y) = 0;
};
class ListeningTask :public BaseTask {
public:
    ListeningTask(int taskNum) : m_taskNum(taskNum) {}
    void  task(float x, float y) { printf("Called this member of task %d function with: %f %f\n", getTaskNum(), x, y); }
    int getTaskNum() const { return m_taskNum; }
private:
    int m_taskNum;
};


/* Context injector */
void callbackWrapper(BaseTask* t, float x, float y) {
    t->task(x, y);
}

/* Convenience function to bind an instance to a task */
template<unsigned int T>
void bindTask(ListeningTask* t) {
    getWrapper<T>() = std::bind(&callbackWrapper, t, std::placeholders::_1, std::placeholders::_2);
}

int main() {

    RegisterTask register_task{};

    auto task0 = new ListeningTask(1337);
    auto task1 = new ListeningTask(1984);
    auto task2 = new ListeningTask(42);

    bindTask<0>(task0);
    register_task.subscribe(task_global<0>);

    bindTask<1>(task1);
    register_task.subscribe(task_global<1>);

    bindTask<2>(task2);
    register_task.subscribe(task_global<2>);

    return 0;
}

Запустите демонстрацию кода

person pokey909    schedule 19.09.2016
comment
Я думал, он сказал, что использует версию C++, предшествующую std::bind. - person Barmar; 20.09.2016
comment
@Barmar std::function и std::bind находятся в Visual Studio 2010. Не уверен, насколько хорошо они работают, поскольку VS2010 нацелен на пока еще нестандартизированный стандарт, но они там есть. Компилятор споткнется об RegisterTask register_task{};, но это легко исправить. - person user4581301; 20.09.2016
comment
В VS2010 bind находится в пространстве имен tr1 (tr1::bind). - person 1201ProgramAlarm; 20.09.2016
comment
Спасибо за напоминание. Я действительно думал, что он может использовать С++ 11, поскольку он использовал инициализацию в стиле С++ 11. Но я обновил свой ответ только для записей. - person pokey909; 20.09.2016
comment
Только что протестировано. Компилируется и запускается. Просто потеряйте {} в объявлениях переменных. - person user4581301; 20.09.2016
comment
@pokey909 Спасибо, это отлично работает. Но это не решает мою проблему полностью, используя список задач. Поскольку memberCb и task_global являются глобальными, я могу привязать к ним только объект Task. Как я могу сделать это с помощью std::list<ListenerTask>? - person solarisx; 20.09.2016
comment
Вы можете использовать лямбды? - person pokey909; 20.09.2016
comment
@ pokey909 Если лямбда-выражения - единственный способ решить эту проблему, я попытаюсь использовать более новый компилятор, чем MSVC 2010. - person solarisx; 20.09.2016
comment
@ pokey909 Нет, здесь используется один обратный вызов для списка. Мне нужен один обратный вызов для каждой записи списка. Я расширил описание моего вопроса. Был бы рад, если бы вы могли взглянуть на него еще раз. - person solarisx; 20.09.2016
comment
@pokey909 Еще раз спасибо за вашу обширную помощь! Я не знаю число ListenerTask во время компиляции, и ваш код не работает с динамическим числом. Забавно, что эта проблема кажется неразрешимой в C++, но легко решается с помощью делегатов в C#. - person solarisx; 21.09.2016
comment
Это. Как я сказал в предыдущем абзаце, если вам нужно добавить их во время выполнения, вы, вероятно, можете использовать тот же прием, но без шаблонов. Просто поместите все объекты std::function в вектор и заверните его в синглтон. - person pokey909; 22.09.2016

ответ pokey909 совершенно великолепен, но если у вас даже нет доступа к std::function и std::bind, мы можем взломать наш путь Это.

Суть подхода в том, что мы собираемся определить класс-шаблон с неявным преобразованием в желаемый тип функции. Недостатком является то, что каждая новая дополнительная оболочка требует объявления нового типа.

// assumes two function arguments
template<class Ret, class Mem, class Arg1, class Arg2, int>
struct MemBind
{
    typedef Ret(Mem::*mem_fn_type)(Arg1, Arg2);
    static void Set(mem_fn_type _fn, Mem* _instance)
    {
        fn = _fn;
        instance = _instance;
    }

    static Ret DoTheThing(Arg1 first, Arg2 second)
    {
        return ((*instance).*fn)(first, second);
    }

    typedef Ret(*fn_type)(Arg1, Arg2); 
    operator fn_type ()
    {
        return DoTheThing;
    }

    static mem_fn_type fn;
    static Mem* instance;
};

Учитывая некоторую структуру Foo с нашим желаемым обратным вызовом:

struct Foo
{
    void Bar(float a, float b)
    {
        std::cout << "Foo::Bar(float, float) " << a << " , " << b << std::endl;
    }
};

Мы должны определить наши статические члены:

typedef MemBind<void, Foo, float, float, 0> MemBindZero;
template<> Foo* MemBindZero::instance = nullptr;
template<>  void(Foo::*MemBindZero::fn)(float, float)  = nullptr;

У нас может быть вызывающий объект, который принимает указатель на функцию:

void Caller(void(*_fn)(float, float))
{
    _fn(42.0, 1337.0);
}

Ключевым моментом здесь является то, что MemBind имеет неявное преобразование в желаемый тип функции. 0 в typedef для MemBindZero позволяет нам повторно использовать те же типы для других аргументов, но увеличивать счетчик до 1 при использовании. Я думаю, что вы, вероятно, могли бы заменить его макросом __COUNTER__ или чем-то подобным, но это было бы нестандартно, поэтому я сделал это вручную.

Теперь следующим шагом будет создание экземпляра MemBindZero, затем установка элементов static и, наконец, передача нашего экземпляра в Caller:

 Foo f;
 MemBindZero bound;
 bound.Set(&Foo::Bar, &f);
 Caller(bound);

Демо


В демо я завернул инициализацию статического члена в более удобный макрос:

#define MEMBIND(RET, CLASS, ARG1, ARG2, COUNT, ALIAS) \
typedef MemBind<RET, CLASS, ARG1, ARG2, COUNT> ALIAS; \
template<> CLASS * ALIAS::instance = nullptr; \
template<>  RET(CLASS::*ALIAS::fn)(ARG1, ARG2)  = nullptr;

Чтобы я мог назвать это так:

MEMBIND(void, Foo, float, float, 0, MemBindZero)
MEMBIND(void, OtherFoo, float, float, 1, MemBindOne)
person AndyG    schedule 19.09.2016
comment
Я совершенно усложняю вещи здесь, я знаю. В основном я играю с шаблонами. - person AndyG; 20.09.2016
comment
Большое спасибо! Это работает для одного обратного вызова. Как я могу использовать std::list<Foo> и связать обратный вызов с каждой из его записей? Я расширил свой вопрос, чтобы более четко описать этот момент. Может быть, вы можете взглянуть на него? - person solarisx; 20.09.2016
comment
Я не совсем понимаю ваше редактирование. Вы хотите отключить некоторые обратные вызовы? Или похоже, что вы спрашиваете, как сделать так, чтобы подписчик вызывал только один из обратных вызовов? - person AndyG; 20.09.2016
comment
Я хочу один обратный вызов для каждого ListenerTask, который я передаю в dll через subscribe(). Как и в моем глобальном примере, если dll нужно что-то сделать для task_global_2, она уведомляет только об этом. Мне нужно передать нестатическую функцию-член для каждого объекта в dll. Таким образом, dll может различать переданные указатели функций. Какую еще информацию я могу предоставить? - person solarisx; 20.09.2016
comment
@solarisx: Как сейчас DLL различает обратные вызовы? Вы упомянули, что не можете изменить его, поэтому мы застряли на той логике, которую он использует. - person AndyG; 20.09.2016
comment
DLL получает уникальные/разные указатели функций и сохраняет их. Мне было бы легко, если бы в DLL можно было передать идентификатор, а затем DLL перезвонила бы с включенным этим идентификатором, но это не так. - person solarisx; 21.09.2016