31

I am developing a C++ application using a C library. I have to send a pointer to function to the C library.

This is my class:

 class MainWindow : public QMainWindow {  
     Q_OBJECT  
     public:  
     explicit MainWindow(QWidget *parent = 0);  
     private:  
     Ui::MainWindow *ui;
     void f(int*);

 private slots:
     void on_btn_clicked(); 
};

This is my on_btn_clicked function:

void MainWindow::on_btn_clicked()
{
    void (MainWindow::* ptfptr) (int*) = &MainWindow::f;

    c_library_function(static_cast<void()(int*)>(ptfptr), NULL);

}

The C function should get a pointer to a such function : void f(int*). But the code above doesn't work, I cannot succeed to convert my f member function to the desired pointer.

Can anybody please help?

6
  • functions inside a class are not recommended for accessing. Commented Nov 6, 2013 at 9:21
  • possible duplicate of Function pointer to class member function problems Commented Nov 6, 2013 at 9:24
  • 6
    Your question makes no sense. C++ function pointers and C function pointers are the same thing (except for some minor linkage details). But you must understand that MainWindow::f is not a function and &MainWindow::f is not a function pointer -- it's a member function (or respectively a pointer to a member function). Commented Nov 6, 2013 at 9:27
  • For a start - in C++ a member function to be used as a function pointer would have to be static. Bringing C into the equation wouldn't remove that requirement. Commented Nov 6, 2013 at 9:28
  • To be 100% standards-compliant (which could be relevant on exotic hardware), you should use an extern "C" function as the callback. Commented Nov 6, 2013 at 9:29

7 Answers 7

28

You can't pass a non-static member function pointer as an ordinary function pointer. They're not the same thing, and probably not even the same size.

You can however (usually) pass a pointer to a static member function through C. Usually when registering a callback in a C API, you also get to pass a "user data" pointer which gets passed back to your registered function. So you can do something like:

class MyClass
{

    void non_static_func(/* args */);

public:
    static void static_func(MyClass *ptr, /* other args */) {
        ptr->non_static_func(/* other args */);
    }
};

Then register your callback as

c_library_function(MyClass::static_func, this);

i.e. pass the instance pointer to the static method, and use that as a forwarding function.

Strictly speaking for total portability you need to use a free function declared extern "C" rather than a static member to do your forwarding (declared as a friend if necessary), but practically speaking I've never had any problems using this method to interface C++ code with GObject code, which is C callback-heavy.

Sign up to request clarification or add additional context in comments.

Comments

20

You can't pass a function pointer to a non-static member function. What you can do is to create a static or global function that makes the call with an instance parameter.

Here's an example I find useful which uses a helper class with two members: a function wrapper and a callback function that calls the wrapper.

template <typename T>
struct Callback;

template <typename Ret, typename... Params>
struct Callback<Ret(Params...)> {
    template <typename... Args>
    static Ret callback(Args... args) { return func(args...); }
    static std::function<Ret(Params...)> func;
};

// Initialize the static member.
template <typename Ret, typename... Params>
std::function<Ret(Params...)> Callback<Ret(Params...)>::func;

Using this you can store any callable, even non-static member functions (using std::bind) and convert to a c-pointer using the Callback::callback function. E.g:

struct Foo {
    void print(int* x) { // Some member function.
        std::cout << *x << std::endl;
    }
};

int main() {
    Foo foo; // Create instance of Foo.

    // Store member function and the instance using std::bind.
    Callback<void(int*)>::func = std::bind(&Foo::print, foo, std::placeholders::_1);

    // Convert callback-function to c-pointer.
    void (*c_func)(int*) = static_cast<decltype(c_func)>(Callback<void(int*)>::callback);

    // Use in any way you wish.
    std::unique_ptr<int> iptr{new int(5)};
    c_func(iptr.get());
}

4 Comments

Nice. Small bug: static void callback should be static Ret callback.
This approach only works if you want to convert one member function to plain c function pointer right? If you have two or three, you need two or three function wrapper class, because each wrapper class stores function as static member variable.
How would one make this work for multithreading? I've posted a new question here : stackoverflow.com/questions/41198854/…
This answer is exactly what I needed to interface to a C library from my C++ code. Unlike other answers which suggest passing class context to the callback this answer allows you to use C libraries as is without modification of callback semantics.
13

If I recall it correctly, Only static methods of a class can be accessed via "normal" C pointer to function syntax. So try to make it static. The pointer to a method of a class needs extra information, such as the "object" (this) which has no meaning for a pure C method.

The FAQ shown here has good explanation and a possible (ugly) solution for your problem.

1 Comment

This stackoverflow.com/questions/1000663/… is a nicer solution.
7

@Snps answer is great. I extended it with a maker function that creates a callback, as I always use void callbacks without parameters:

typedef void (*voidCCallback)();
template<typename T>
voidCCallback makeCCallback(void (T::*method)(),T* r){
  Callback<void()>::func = std::bind(method, r);
  void (*c_function_pointer)() = static_cast<decltype(c_function_pointer)>(Callback<void()>::callback);
  return c_function_pointer;
}

From then on, you can create your plain C callback from within the class or anywhere else and have the member called:

voidCCallback callback = makeCCallback(&Foo::print, this);
plainOldCFunction(callback);

Comments

3

@Snps answer is perfect! But as @DXM mentioned it can hold only one callback. I've improved it a little, now it can keep many callbacks of the same type. It's a little bit strange, but works perfect:

 #include <type_traits>

template<typename T>
struct ActualType {
    typedef T type;
};
template<typename T>
struct ActualType<T*> {
    typedef typename ActualType<T>::type type;
};

template<typename T, unsigned int n,typename CallerType>
struct Callback;

template<typename Ret, typename ... Params, unsigned int n,typename CallerType>
struct Callback<Ret(Params...), n,CallerType> {
    typedef Ret (*ret_cb)(Params...);
    template<typename ... Args>
    static Ret callback(Args ... args) {
        func(args...);
    }

    static ret_cb getCallback(std::function<Ret(Params...)> fn) {
        func = fn;
        return static_cast<ret_cb>(Callback<Ret(Params...), n,CallerType>::callback);
    }

    static std::function<Ret(Params...)> func;

};

template<typename Ret, typename ... Params, unsigned int n,typename CallerType>
std::function<Ret(Params...)> Callback<Ret(Params...), n,CallerType>::func;

#define GETCB(ptrtype,callertype) Callback<ActualType<ptrtype>::type,__COUNTER__,callertype>::getCallback

Now you can just do something like this:

typedef void (cb_type)(uint8_t, uint8_t);
class testfunc {
public:
    void test(int x) {
        std::cout << "in testfunc.test " <<x<< std::endl;
    }

    void test1(int x) {
        std::cout << "in testfunc.test1 " <<x<< std::endl;
    }

};

cb_type* f = GETCB(cb_type, testfunc)(std::bind(&testfunc::test, tf, std::placeholders::_2));

cb_type* f1 = GETCB(cb_type, testfunc)(
                std::bind(&testfunc::test1, tf, std::placeholders::_2));


f(5, 4);
f1(5, 7);

2 Comments

update: for use in multiple files you need to pass TypeName of caller, because COUNTER macro is only per-file and doesn't generate unique numbers per project.
Is there also a solution for multiple objects? I tried this sample and it does not work properly if I have multiple instances of "tf"
2

The short answer is: you can convert a member function pointer to an ordinary C function pointer using std::mem_fn.

That is the answer to the question as given, but this question seems to have a confused premise, as the asker expects C code to be able to call an instance method of MainWindow without having a MainWindow*, which is simply impossible.

If you use mem_fn to cast MainWindow::on_btn_clicked to a C function pointer, then you still a function that takes a MainWindow* as its first argument.

void (*window_callback)(MainWindow*,int*) = std::mem_fn(&MainWindow::on_btn_clicked);

That is the answer to the question as given, but it doesn't match the interface. You would have to write a C function to wrap the call to a specific instance (after all, your C API code knows nothing about MainWindow or any specific instance of it):

void window_button_click_wrapper(int* arg)
{
    MainWindow::inst()->on_btn_clicked(arg);
}

This is considered an OO anti-pattern, but since the C API knows nothing about your object, it's the only way.

Comments

1

I've got an idea (not entirely standard-compliant, as extern "C" is missing):

class MainWindow;

static MainWindow* instance;

class MainWindow
{
public:
  MainWindow()
  {
    instance = this;

    registerCallback([](int* arg){instance->...});
  }
};

You will have problems if multiple instances of MainWindow are instantiated.

6 Comments

What does the + in registerCallback(+[](int* arg){instance->...}); mean? Never seen that before.
It converts the lambda object to a function pointer. The lambda helps prevent namespace pollution (one function definition & declaration less).
Isn't that unnecessary? I thought a non-capturing lambda is implicitly convertible to a suitable function pointer.
@Snps There's no need to capture a global variable. Why would it? As far as conversion is concerned, you may be right, but the + does no harm AFAIK.
I didn't see that it was a global at first.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.