0

I've been researching how to use lambdas as callbacks, but I do not seem to be getting my syntax correct. I am working in C++ via Visual Studio 2013 Community, & I am pretty sure this type of lambda use is supported. I am working on a video library which is based on the FFMPEG libav libraries.

The basic issue is the logging callback provided to library clients of FFMPEG's libav. It has this calling structure:

void logging_callback( void *ptr, int level, const char *fmt, va_list vargs  );

This is fine for C or fine for C++ when the logging callback is a static member function, but I want to install a class member function, which needs the this parameter per the C++ nature of calling method functions.

What I have so far looks like: (unrelated portions removed)

class my_libav
{ 
   // logging messages callback for use by library client, any log messages generated 
   // by the library will be sent thru this to the lib client's logic:
   typedef void(*STREAM_LOGGING_CALLBACK_CB) (std::string msg, void* p_object);

   // my lib's logger has a void* param so clients can redirect to member functs easier
   void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object);

   // member function to be installed as FFMPEG's av_log_set_callback():
   void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );

   STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback; // client's callback
   void*                       mp_stream_logging_object;   // client's data

};

void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object)
{
  mp_stream_logging_callback = p_stream_logger;
  mp_stream_logging_object = p_object;
}

void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
{
  // logic that resolves the message and sends through client's callback
  // installed int mp_stream_logging_callback
}

my_libav::my_libav()
{
  // this is the old static member function I deleted:
  // av_log_set_callback( &my_libav::logging_callback );

  // I tried using std::bind & placeholders, but this syntax is wrong too:
  // namespace ph = std::placeholders;
  // auto callback = std::bind( &my_libav::RedirectLoggingOutputs, this, ph::_1, ph::_2, ph::_3, ph::_4 );
  // 
  // av_log_set_callback( callback ); // <- 'callback' is not the correct type

  // this is my try at a lambda. I know that the *this* cannot be 
  // in the capture, but I don't know the right syntax to work around 
  // this issue:
  std::function<void(void *ptr, int level, const char *fmt, va_list vargs )> 
        f2 = [this](void *ptr, int level, const char *fmt, va_list vargs ){
                        RedirectLoggingOutputs( ptr, level, fmt, vargs ); };

    av_log_set_callback( f2 ); // <- 'f2' is not the correct type, how to fix? 

The syntax I am trying to fix is in the last 4 lines. What is the correct form?

Blake Senftner
  • 756
  • 1
  • 8
  • 24
  • I am not sure how to implement using a global variable or TLS storage. I can't see how it can be done with a global variable; but in the case of TLS, would that be creating a separate class that only handles this callback? Then my_lib's constructor does the "new" on this callback_redirecting_class which is using TLS to hold/hide the 'this' pointer to the class that created the callback_redirecting_class insrtance? – Blake Senftner Feb 13 '18 at 01:35

1 Answers1

1

FFMPEG is a C style library. You can't use a capturing lambda, or a std::function, where a C-style function pointer is expected. So, in this situation, you will have to stick with using a standalone function, or a static class method, for your av_log_set_callback() callback.

av_log_set_callback() does not allow you to pass a user-defined value to the callback, and you can only have 1 callback active at a time. Since you want to use your class's this pointer with your callback, you will be able to use only 1 instance of your class at a time, and you will have to use a global variable to pass your this pointer to your callback.

Try something like this:

class my_libav
{ 
public:
    typedef void (*STREAM_LOGGING_CALLBACK_CB)(std::string msg, void* p_object);

    my_libav();
    my_libav(const my_libav &) = delete;
    ~my_libav();
    my_libav& operator=(const my_libav &) = delete;

    void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object);

private:
    STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback = nullptr;
    void*                       mp_stream_logging_object = nullptr;

    static void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );
};

static my_libav *g_mylibav = nullptr;

my_libav::my_libav()
{
    if (g_mylibav) throw std::runtime_error("Only 1 instance of my_libav is allowed at a time!");
    g_mylibav = this;
    av_log_set_callback( &my_libav::RedirectLoggingOutputs );
}

my_libav::~my_libav()
{
    av_log_set_callback( nullptr );
    g_mylibav = nullptr;
}

void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object)
{
    mp_stream_logging_callback = p_stream_logger;
    mp_stream_logging_object = p_object;
}

void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
{
    std::string msg;
    ...
    if (g_mylibav->mp_stream_logging_callback)
        g_mylibav->mp_stream_logging_callback(msg, g_mylibav->mp_stream_logging_object);
}

Or, use a singleton pattern to ensure only 1 instance exists:

class my_libav
{ 
public:
    typedef void (*STREAM_LOGGING_CALLBACK_CB)(std::string msg, void* p_object);

    ~my_libav();    

    static my_libav& getInstance();

    void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object);

private:
    STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback = nullptr;
    void*                       mp_stream_logging_object = nullptr;

    my_libav();
    my_libav(const my_libav &) = delete;
    my_libav& operator=(const my_libav &) = delete;

    static void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );
};

static my_libav *g_mylibav = nullptr;

my_libav::my_libav()
{
    g_mylibav = this;
    av_log_set_callback( &my_libav::RedirectLoggingOutputs );
}

my_libav::~my_libav()
{
    av_log_set_callback( nullptr );
    g_mylibav = nullptr;
}

my_libav& my_libav::getInstance()
{
    static my_libav inst;
    return inst;
}

void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger, void* p_object)
{
    mp_stream_logging_callback = p_stream_logger;
    mp_stream_logging_object = p_object;
}

void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
{
    std::string msg;
    ...
    if (g_mylibav->mp_stream_logging_callback)
        g_mylibav->mp_stream_logging_callback(msg, g_mylibav->mp_stream_logging_object);
}

On a side note, since you are using C++11 or later anyway, you might consider using std::function for your client callback, then a client can use "any Callable target -- function, lambda expression, bind expression, or other function object, as well as a pointer to member function and pointer to data member", and not have to pass around a separate void* back and forth:

class my_libav
{ 
public:
    typedef std::function<void(std::string)> STREAM_LOGGING_CALLBACK_CB;

    ...

    void SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger);

private:
    STREAM_LOGGING_CALLBACK_CB  mp_stream_logging_callback;

    ...

    static void RedirectLoggingOutputs( void *ptr, int level, const char *fmt, va_list vargs );
};

void my_libav::SetStreamLoggingCallback(STREAM_LOGGING_CALLBACK_CB p_stream_logger)
{
    mp_stream_logging_callback = p_stream_logger;
}

void my_libav::RedirectLoggingOutputs(void *ptr, int level, const char *fmt, va_list vargs )
{
    std::string msg;
    ...
    if (g_mylibav->mp_stream_logging_callback)
        g_mylibav->mp_stream_logging_callback(msg);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Seem like there should be some way to use C++11 or std::bind to create a function with the required call/return signature from a member function. That is was I was trying to do here: namespace ph = std::placeholders; auto callback = std::bind( &my_libav::RedirectLoggingOutputs, this, ph::_1, ph::_2, ph::_3, ph::_4 ); – Blake Senftner Feb 13 '18 at 03:16
  • "*Seem like there should be some way to use C++11 or std::bind to create a function with the required call/return signature from a member function.*" - [NO, because C is involved](https://stackoverflow.com/questions/13238050/). The C callback you are dealing with only works with a **pointer-to-function**. In C++, the only ways to get one is 1) a standalone function, 2) a static class method, or 3) a *manually crafted* proxy function that uses inline assembly (NOT `std::bind()`) to call a non-static class method. This last one is possible, but it an advanced technique that C++ can't do for you. – Remy Lebeau Feb 13 '18 at 03:55
  • 1
    Seems like a major design oversight of the FFMPEG authors to have a client callback with no way to identify the caller or to have non-global client data in your callback. – Blake Senftner Feb 13 '18 at 14:43