How can I pass a class member function as a callback?

前端 未结 12 1735
忘掉有多难
忘掉有多难 2020-11-22 04:58

I\'m using an API that requires me to pass a function pointer as a callback. I\'m trying to use this API from my class but I\'m getting compilation errors.

Here is

相关标签:
12条回答
  • 2020-11-22 05:49

    A simple solution "workaround" still is to create a class of virtual functions "interface" and inherit it in the caller class. Then pass it as a parameter "could be in the constructor" of the other class that you want to call your caller class back.

    DEFINE Interface:

    class CallBack 
    {
       virtual callMeBack () {};
    };
    

    This is the class that you want to call you back:

    class AnotherClass ()
    {
         public void RegisterMe(CallBack *callback)
         {
             m_callback = callback;
         }
    
         public void DoSomething ()
         {
            // DO STUFF
            // .....
            // then call
            if (m_callback) m_callback->callMeBack();
         }
         private CallBack *m_callback = NULL;
    };
    

    And this is the class that will be called back.

    class Caller : public CallBack
    {
        void DoSomthing ()
        {
        }
    
        void callMeBack()
        {
           std::cout << "I got your message" << std::endl;
        }
    };
    
    0 讨论(0)
  • 2020-11-22 05:50

    A pointer to a class member function is not the same as a pointer to a function. A class member takes an implicit extra argument (the this pointer), and uses a different calling convention.

    If your API expects a nonmember callback function, that's what you have to pass to it.

    0 讨论(0)
  • 2020-11-22 05:51

    That doesn't work because a member function pointer cannot be handled like a normal function pointer, because it expects a "this" object argument.

    Instead you can pass a static member function as follows, which are like normal non-member functions in this regard:

    m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);
    

    The function can be defined as follows

    static void Callback(int other_arg, void * this_pointer) {
        CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
        self->RedundencyManagerCallBack(other_arg);
    }
    
    0 讨论(0)
  • 2020-11-22 05:53

    What argument does Init take? What is the new error message?

    Method pointers in C++ are a bit difficult to use. Besides the method pointer itself, you also need to provide an instance pointer (in your case this). Maybe Init expects it as a separate argument?

    0 讨论(0)
  • 2020-11-22 05:55

    Necromancing.
    I think the answers to date are a little unclear.

    Let's make an example:

    Supposed you have an array of pixels (array of ARGB int8_t values)

    // A RGB image
    int8_t* pixels = new int8_t[1024*768*4];
    

    Now you want to generate a PNG. To do so, you call the function toJpeg

    bool ok = toJpeg(writeByte, pixels, width, height);
    

    where writeByte is a callback-function

    void writeByte(unsigned char oneByte)
    {
        fputc(oneByte, output);
    }
    

    The problem here: FILE* output has to be a global variable.
    Very bad if you're in a multithreaded environment (e.g. a http-server).

    So you need some way to make output a non-global variable, while retaining the callback signature.

    The immediate solution that springs into mind is a closure, which we can emulate using a class with a member function.

    class BadIdea {
    private:
        FILE* m_stream;
    public:
        BadIdea(FILE* stream)  {
            this->m_stream = stream;
        }
    
        void writeByte(unsigned char oneByte){
                fputc(oneByte, this->m_stream);
        }
    
    };
    

    And then do

    FILE *fp = fopen(filename, "wb");
    BadIdea* foobar = new BadIdea(fp);
    
    bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
    delete foobar;
    fflush(fp);
    fclose(fp);
    

    However, contrary to expectations, this does not work.

    The reason is, C++ member functions are kinda implemented like C# extension functions.

    So you have

    class/struct BadIdea
    {
        FILE* m_stream;
    }
    

    and

    static class BadIdeaExtensions
    {
        public static writeByte(this BadIdea instance, unsigned char oneByte)
        {
             fputc(oneByte, instance->m_stream);
        }
    
    }
    

    So when you want to call writeByte, you need pass not only the address of writeByte, but also the address of the BadIdea-instance.

    So when you have a typedef for the writeByte procedure, and it looks like this

    typedef void (*WRITE_ONE_BYTE)(unsigned char);
    

    And you have a writeJpeg signature that looks like this

    bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t 
     width, uint32_t height))
        { ... }
    

    it's fundamentally impossible to pass a two-address member function to a one-address function pointer (without modifying writeJpeg), and there's no way around it.

    The next best thing that you can do in C++, is using a lambda-function:

    FILE *fp = fopen(filename, "wb");
    auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp);  };
    bool ok = TooJpeg::writeJpeg(lambda, image, width, height);
    

    However, because lambda is doing nothing different, than passing an instance to a hidden class (such as the "BadIdea"-class), you need to modify the signature of writeJpeg.

    The advantage of lambda over a manual class, is that you just need to change one typedef

    typedef void (*WRITE_ONE_BYTE)(unsigned char);
    

    to

    using WRITE_ONE_BYTE = std::function<void(unsigned char)>; 
    

    And then you can leave everything else untouched.

    You could also use std::bind

    auto f = std::bind(&BadIdea::writeByte, &foobar);
    

    But this, behind the scene, just creates a lambda function, which then also needs the change in typedef.

    So no, there is no way to pass a member function to a method that requires a static function-pointer.

    But lambdas are the easy way around, provided that you have control over the source.
    Otherwise, you're out of luck.
    There's nothing you can do with C++.

    Note:
    std::function requires #include <functional>

    However, since C++ allows you to use C as well, you can do this with libffcall in plain C, if you don't mind linking a dependency.

    Download libffcall from GNU (at least on ubuntu, don't use the distro-provided package - it is broken), unzip.

    ./configure
    make
    make install
    
    gcc main.c -l:libffcall.a -o ma
    

    main.c:

    #include <callback.h>
    
    // this is the closure function to be allocated 
    void function (void* data, va_alist alist)
    {
         int abc = va_arg_int(alist);
    
         printf("data: %08p\n", data); // hex 0x14 = 20
         printf("abc: %d\n", abc);
    
         // va_start_type(alist[, return_type]);
         // arg = va_arg_type(alist[, arg_type]);
         // va_return_type(alist[[, return_type], return_value]);
    
        // va_start_int(alist);
        // int r = 666;
        // va_return_int(alist, r);
    }
    
    
    
    int main(int argc, char* argv[])
    {
        int in1 = 10;
    
        void * data = (void*) 20;
        void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
        // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
        // void(*incrementer1)(int abc) starts to throw errors...
        incrementer1(123);
        // free_callback(callback);
        return EXIT_SUCCESS;
    }
    

    And if you use CMake, add the linker library after add_executable

    add_library(libffcall STATIC IMPORTED)
    set_target_properties(libffcall PROPERTIES
            IMPORTED_LOCATION /usr/local/lib/libffcall.a)
    target_link_libraries(BitmapLion libffcall)
    

    or you could just dynamically link libffcall

    target_link_libraries(BitmapLion ffcall)
    

    Note:
    You might want to include the libffcall headers and libraries, or create a cmake project with the contents of libffcall.

    0 讨论(0)
  • 2020-11-22 05:57

    Looks like std::mem_fn (C++11) does exactly what you need:

    Function template std::mem_fn generates wrapper objects for pointers to members, which can store, copy, and invoke a pointer to member. Both references and pointers (including smart pointers) to an object can be used when invoking a std::mem_fn.

    0 讨论(0)
提交回复
热议问题