c++-gtk-utils

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

Callbacks with C linkage

Any function connecting to a GObject signal, such as any GTK+ signal, must have C linkage. In C++, function pointers have a linkage specification (C or C++) in the same way that functions do, and although name mangling would not be an issue with respect to function pointers, the calling convention is. Thus, functions with C linkage specification have arguments passed on the stack, whereas functions with C++ linkage specification can optimise by passing arguments in registers or by manipulating the stack frame 'in situ'. Different calling conventions also vary in the way they allocate responsibility, as between caller and called, for resetting stack pointers when a function call exits. With some compilers such as gcc, static class member functions adopt the C calling convention and so can be passed as GObject callbacks, but this is not required by the standard and some compilers (for example, some of the Intel compilers) do not.

This means that functions to be passed as GObject callbacks should be declared as having C language linkage (that is, be declared 'extern "C"'). The WinBase example does this with respect to the message_button_clicked() callback. In that example, this function has to be declared in the header file so that it can be made a friend of the class and so call the protected function Cgu::WinBase::close(). This means it will appear in the global namespace. Putting it within a C++ namespace is of limited effect as at link time C++ namespaces are ignored in the case of functions with C linkage. (So far as linkage is concerned, it could be given internal linkage by declaring it static, but that would cause any other translation unit incorporating the header file to issue an albeit harmless warning about a static function being declared without a definition.)

Where a particular class implementation uses a number of GObject signal callbacks, it can be inconvenient to have several callback functions in global scope, and pollutes the global namespace. One useful technique to avoid this is the friend helper class, which can also be a nested class. Take a class Dialog derived from Cgu::WinBase which connects to two GTK+ signals, say "clicked" and "expose-event", for which both callbacks need to access private or protected data or methods of the class. Complete hiding can be achieved in this way:

// dialog.h header file
class Dialog: public Cgu::WinBase {
[ ... ]
public:
struct CB; // declaration of nested helper class
friend struct CB; // this explicit friendship declaration for the nested class is not required
// in C++11 or with compilers which implement defect report #45
Dialog();
[ ... ]
};
// dialog.cpp implementation file
struct Dialog::CB {
// these static member functions implement the callbacks
static void clicked(GtkWidget* w, void* data) {
Dialog* dialog = static_cast<Dialog*>(data);
[ ... callback implementation which can use the dialog pointer ... ]
}
static gboolean expose_event(GtkWidget* w, GdkEventExpose* e, void* data) {
Dialog* dialog = static_cast<Dialog*>(data);
[ ... callback implementation which can use the dialog pointer ... ]
}
};
// wrapper functions which have both C language linkage and internal linkage
extern "C" {
static void clicked_cb(GtkWidget* w, void* data) {
Dialog::CB::clicked(w, data);
}
static gboolean expose_event_cb(GtkWidget* w, GdkEventExpose* e, void* data) {
return Dialog::CB::expose_event(w, e, data);
}
}
Dialog::Dialog(): Cgu::WinBase("Dialog", 0, true) {
...
GtkWidget* button = gtk_button_new_from_stock(GTK_STOCK_OK);
g_signal_connect(G_OBJECT(button), "clicked",
G_CALLBACK(clicked_cb), this);
...
GtkWidget* drawing_area = gtk_drawing_area_new();
g_signal_connect(G_OBJECT(drawing_area), "expose-event",
G_CALLBACK(expose_event_cb), this);
...
gtk_widget_show_all(GTK_WIDGET(get_win()));
}

Because the static member functions of Dialog::CB in this example are implicitly inline, there should be no efficiency implications.

Another approach is to use Callback::CallbackArg objects with g_signal_connect_data():

// dialog.h header file
class Dialog: public Cgu::WinBase {
public:
Dialog();
[ ... ]
private:
void clicked(GtkWidget*);
void expose(GtkWidget*, GdkEventExpose*);
[ ... ]
};
// dialog.cpp implementation file
using namespace Cgu;
extern "C" {
static void clicked_cb(GtkWidget* w, void* data) {
static_cast<const Callback::CallbackArg<GtkWidget*>*>(data)->dispatch(w);
}
static void delete_clicked_cb(void* data, GClosure*) {
delete static_cast<const Callback::CallbackArg<GtkWidget*>*>(data);
}
static gboolean expose_cb(GtkWidget* w, GdkEventExpose* e, void* data) {
static_cast<const Callback::CallbackArg<GtkWidget*, GdkEventExpose*>*>(data)->dispatch(w, e);
return true; // processing stops here
}
static void delete_expose_cb(void* data, GClosure*) {
delete static_cast<const Callback::CallbackArg<GtkWidget*, GdkEventExpose*>*>(data);
}
}
Dialog::Dialog(): WinBase("Dialog", 0, true) {
...
GtkWidget* button = gtk_button_new_from_stock(GTK_STOCK_OK);
g_signal_connect_data(G_OBJECT(button), "clicked",
G_CALLBACK(clicked_cb),
Callback::make(*this, &Dialog::clicked),
delete_clicked_cb, GConnectFlags(0));
...
GtkWidget* drawing_area = gtk_drawing_area_new();
g_signal_connect_data(G_OBJECT(drawing_area), "expose-event",
G_CALLBACK(expose_cb),
Callback::make(*this, &Dialog::expose),
delete_expose_cb, GConnectFlags(0));
...
gtk_widget_show_all(GTK_WIDGET(get_win()));
}

This approach can be particularly useful where other data which is not a class member is to be bound to the callback object at connect time, such as a button number. (That is not the case in these examples.)