Search code examples
c++glib

How do I pass member functions to g_source_set_callback?


I am trying to pass member function pointers to g_source_set_callback to queue them. This is a dummy code that I wrote.

#include<glib.h>

#include<iostream>
#include<thread>
#include<chrono>

using namespace std;
using namespace std::chrono_literals;

class executor
{
private:
    GMainLoop* main_loop;
    GMainContext* worker_context;
    thread worker_thread;

    void worker_loop()
    {
        g_main_context_push_thread_default(worker_context);
        cout << "Starting main loop" << endl;
        g_main_loop_run(main_loop);
        cout << "Finished main loop" << endl;
        g_main_context_pop_thread_default(worker_context);
    }

    void queue_callback(int (*callback)(void))
    {
        GSource* idle_source = g_idle_source_new();
        g_source_set_callback(idle_source, (GSourceFunc)callback, NULL, NULL);
        g_source_attach(idle_source, worker_context);
        g_source_unref(idle_source);
    }


    int func1()
    {
        cout << "func1 started" << endl;
        this_thread::sleep_for(5s);
        cout << "func1 finished waiting" << endl;
        return 0;
    }

    int func2()
    {
        cout << "func2 started" << endl;
        this_thread::sleep_for(1s);
        cout << "func2 finished waiting" << endl;
        return 0;
    }

public:
    executor()
    {
        worker_context = g_main_context_new();
        main_loop = g_main_loop_new(worker_context, false);
        worker_thread = thread(&executor::worker_loop, this);
    }

    ~executor()
    {
        cout << "Stopping main loop" << endl;
        GSource* idle_source = g_idle_source_new();
        g_source_set_callback(idle_source, (GSourceFunc)g_main_loop_quit, main_loop, NULL);
        g_source_attach(idle_source, worker_context);
        g_source_unref(idle_source);

        if (worker_thread.joinable())
        {
            worker_thread.join();
        }
        cout << "Removing references to main loop and context" << endl;

        g_main_loop_unref(main_loop);
        g_main_context_unref(worker_context);
    }

    void start()
    {
        queue_callback(func1);
        queue_callback(func2);
    }
};

int main()
{
    executor e;
    e.start();
    return 0;
}

I know that you cannot pass non-static member function like that, so, I get following compilation error as expected.

test.cpp: In member function ‘void executor::start()’:
test.cpp:79:37: error: invalid use of non-static member function ‘int executor::func1()’
                 queue_callback(func1);
                                     ^
test.cpp:35:13: note: declared here
         int func1()
             ^~~~~
test.cpp:80:37: error: invalid use of non-static member function ‘int executor::func2()’
                 queue_callback(func2);
                                     ^
test.cpp:43:13: note: declared here
         int func2()
             ^~~~~

I saw someone using function objects to wrap the member functions. So, I tried this.

#include<glib.h>

#include<iostream>
#include<thread>
#include<chrono>
#include<functional>

using namespace std;
using namespace std::chrono_literals;

class executor
{
private:
    GMainLoop* main_loop;
    GMainContext* worker_context;
    thread worker_thread;

    void worker_loop()
    {
        g_main_context_push_thread_default(worker_context);
        cout << "Starting main loop" << endl;
        g_main_loop_run(main_loop);
        cout << "Finished main loop" << endl;
        g_main_context_pop_thread_default(worker_context);
    }

    void queue_callback(const function<int()>* callback)
    {
        GSource* idle_source = g_idle_source_new();
        g_source_set_callback(idle_source, reinterpret_cast<GSourceFunc>(&on_callback), const_cast<function<int()>*>(callback), NULL);
        g_source_attach(idle_source, worker_context);
        g_source_unref(idle_source);
    }

    static int on_callback(const function<int()>* callback)
    {
        return (*callback)();
    }

    int func1()
    {
        cout << "func1 started" << endl;
        this_thread::sleep_for(5s);
        cout << "func1 finished waiting" << endl;
        return 0;
    }

    int func2()
    {
        cout << "func2 started" << endl;
        this_thread::sleep_for(1s);
        cout << "func2 finished waiting" << endl;
        return 0;
    }

public:
    executor()
    {
        worker_context = g_main_context_new();
        main_loop = g_main_loop_new(worker_context, false);
        worker_thread = thread(&executor::worker_loop, this);
    }

    ~executor()
    {
        cout << "Stopping main loop" << endl;
        GSource* idle_source = g_idle_source_new();
        g_source_set_callback(idle_source, (GSourceFunc)g_main_loop_quit, main_loop, NULL);
        g_source_attach(idle_source, worker_context);
        g_source_unref(idle_source);

        if (worker_thread.joinable())
        {
            worker_thread.join();
        }
        cout << "Removing references to main loop and context" << endl;

        g_main_loop_unref(main_loop);
        g_main_context_unref(worker_context);
    }

    void start()
    {
        cout << "Submitting func1 callback" << endl;
        function<int()> callback1 = [this]() {
            return func1();
        };
        queue_callback(&callback1);

        cout << "Submitting func2 callback" << endl;
        function<int()> callback2 = [this]() {
            return func2();
        };
        queue_callback(&callback2);
    }
};

int main()
{
    executor e;
    e.start();
    return 0;
}

This code compiles, but I keep getting either segmentation fault or bad_function_call exception.

Starting main loop
Submitting func1 callback
Submitting func2 callback
Stopping main loop
func1 started
func1 finished waiting
terminate called after throwing an instance of 'std::bad_function_call'
  what():  bad_function_call
Aborted (core dumped)

I think I am getting error because callback1 and callback2 are local objects and by the time they are executed, memory for them is freed.

How do I fix this? I thought of using unique_ptrs, but couldn't figure out how as g_source_set_callback takes int (*GSourceFunc) void as second argument and void* as third.


Solution

  • I figured it out. I create a new std::function object and pass it to g_source_set_callback. When on_callback is called, I typecast the void * to std::function<int()>*, call it and delete it. Here is the working code.

    #include<glib.h>
    
    #include<iostream>
    #include<thread>
    #include<chrono>
    #include<functional>
    
    using namespace std;
    using namespace std::chrono_literals;
    
    class executor
    {
    private:
        GMainLoop* main_loop;
        GMainContext* worker_context;
        thread worker_thread;
    
        void worker_loop()
        {
            g_main_context_push_thread_default(worker_context);
            cout << "Starting main loop" << endl;
            g_main_loop_run(main_loop);
            cout << "Finished main loop" << endl;
            g_main_context_pop_thread_default(worker_context);
        }
    
        void queue_callback(function<int()>&& callback)
        {
            GSource* idle_source = g_idle_source_new();
            g_source_set_callback(idle_source, on_callback, new function<int()>(callback), NULL);
            g_source_attach(idle_source, worker_context);
            g_source_unref(idle_source);
        }
    
        static int on_callback(void* ptr)
        {
            function<int()>* callback = static_cast<function<int()>*>(ptr);
            int ret = (*callback)();
            delete callback;
            return ret;
        }
    
        int func1()
        {
            cout << "func1 started" << endl;
            this_thread::sleep_for(5s);
            cout << "func1 finished waiting" << endl;
            return 0;
        }
    
        int func2()
        {
            cout << "func2 started" << endl;
            this_thread::sleep_for(1s);
            cout << "func2 finished waiting" << endl;
            return 0;
        }
    
    public:
        executor()
        {
            worker_context = g_main_context_new();
            main_loop = g_main_loop_new(worker_context, false);
            worker_thread = thread(&executor::worker_loop, this);
        }
    
        ~executor()
        {
            cout << "Stopping main loop" << endl;
            GSource* idle_source = g_idle_source_new();
            g_source_set_callback(idle_source, (GSourceFunc)g_main_loop_quit, main_loop, NULL);
            g_source_attach(idle_source, worker_context);
            g_source_unref(idle_source);
    
            if (worker_thread.joinable())
            {
                worker_thread.join();
            }
            cout << "Removing references to main loop and context" << endl;
    
            g_main_loop_unref(main_loop);
            g_main_context_unref(worker_context);
        }
    
        void start()
        {
            cout << "Submitting func1 callback" << endl;
            queue_callback([this]() { return func1(); });
    
            cout << "Submitting func2 callback" << endl;
            queue_callback([this]() { return func2(); });
        }
    };
    
    int main()
    {
        executor e;
        e.start();
        return 0;
    }