The GObject messaging systemClosures
Closures are central to the concept of asynchronous signal delivery
which is widely used throughout GTK+ and GNOME applications. A closure is an
abstraction, a generic representation of a callback. It is a small structure
which contains three objects:
a function pointer (the callback itself) whose prototype looks like:
return_type function_callback (… , gpointer user_data);
the user_data pointer which is passed to the callback upon invocation of the closure
a function pointer which represents the destructor of the closure: whenever the
closure's refcount reaches zero, this function will be called before the closure
structure is freed.
The GClosure structure represents the common functionality of all
closure implementations: there exists a different closure implementation for
each separate runtime which wants to use the GObject type system.
In practice, closures sit at the boundary of language runtimes: if you are
writing Python code and one of your Python callbacks receives a signal from
a GTK+ widget, the C code in GTK+ needs to execute your Python
code. The closure invoked by the GTK+ object invokes the Python callback:
it behaves as a normal C object for GTK+ and as a normal Python object for
Python code.
The GObject library provides a simple GCClosure type which
is a specific implementation of closures to be used with C/C++ callbacks.
A GClosure provides simple services:
Invocation (g_closure_invoke): this is what closures
were created for: they hide the details of callback invocation from the
callback invoker.
Notification: the closure notifies listeners of certain events such as
closure invocation, closure invalidation and closure finalization. Listeners
can be registered with g_closure_add_finalize_notifier
(finalization notification), g_closure_add_invalidate_notifier
(invalidation notification) and
g_closure_add_marshal_guards (invocation notification).
There exist symmetric deregistration functions for finalization and invalidation
events (g_closure_remove_finalize_notifier and
g_closure_remove_invalidate_notifier) but not for the invocation
process.
Closures are reference counted and notify listeners of their destruction in a two-stage
process: the invalidation notifiers are invoked before the finalization notifiers.
C Closures
If you are using C or C++
to connect a callback to a given event, you will either use simple GCClosures
which have a pretty minimal API or the even simpler g_signal_connect
functions (which will be presented a bit later).
g_cclosure_new will create a new closure which can invoke the
user-provided callback_func with the user-provided
user_data as its last parameter. When the closure
is finalized (second stage of the destruction process), it will invoke
the destroy_data function if the user has
supplied one.
g_cclosure_new_swap will create a new closure which can invoke the
user-provided callback_func with the
user-provided user_data as its first parameter
(instead of being the
last parameter as with g_cclosure_new). When the closure
is finalized (second stage of the destruction process), it will invoke
the destroy_data function if the user has
supplied one.
Non-C closures (for the fearless)
As was explained above, closures hide the details of callback invocation. In C,
callback invocation is just like function invocation: it is a matter of creating
the correct stack frame for the called function and executing a call
assembly instruction.
C closure marshallers transform the array of GValues which represent
the parameters to the target function into a C-style function parameter list, invoke
the user-supplied C function with this new parameter list, get the return value of the
function, transform it into a GValue and return this GValue to the marshaller caller.
A generic C closure marshaller is available as
g_cclosure_marshal_generic
which implements marshalling for all function types using libffi. Custom
marshallers for different types are not needed apart from performance
critical code where the libffi-based marshaller may be too slow.
An example of a custom marshaller is given below, illustrating how
GValues can be converted to a C function call. The
marshaller is for a C function which takes an integer as its first
parameter and returns void.
g_cclosure_marshal_VOID__INT (GClosure *closure,
GValue *return_value,
guint n_param_values,
const GValue *param_values,
gpointer invocation_hint,
gpointer marshal_data)
{
typedef void (*GMarshalFunc_VOID__INT) (gpointer data1,
gint arg_1,
gpointer data2);
register GMarshalFunc_VOID__INT callback;
register GCClosure *cc = (GCClosure*) closure;
register gpointer data1, data2;
g_return_if_fail (n_param_values == 2);
data1 = g_value_peek_pointer (param_values + 0);
data2 = closure->data;
callback = (GMarshalFunc_VOID__INT) (marshal_data ? marshal_data : cc->callback);
callback (data1,
g_marshal_value_peek_int (param_values + 1),
data2);
}
There exist other kinds of marshallers, for example there is a generic
Python marshaller which is used by all Python closures (a Python closure
is used to invoke a callback written in Python). This Python marshaller
transforms the input GValue list representing the function parameters
into a Python tuple which is the equivalent structure in Python.
Signals
GObject's signals have nothing to do with standard UNIX signals: they connect
arbitrary application-specific events with any number of listeners.
For example, in GTK+, every user event (keystroke or mouse move) is received
from the windowing system and generates a GTK+ event in the form of a signal emission
on the widget object instance.
Each signal is registered in the type system together with the type on which
it can be emitted: users of the type are said to connect
to the signal on a given type instance when they register a closure to be
invoked upon the signal emission. The closure will be called synchronously on emission.
Users can also emit the signal by themselves or stop the emission of the signal from
within one of the closures connected to the signal.
When a signal is emitted on a given type instance, all the closures
connected to this signal on this type instance will be invoked. All the closures
connected to such a signal represent callbacks whose signature looks like:
return_type function_callback (gpointer instance, …, gpointer user_data);
Signal registration
To register a new signal on an existing type, we can use any of g_signal_newv,
g_signal_new_valist or g_signal_new functions:
guint g_signal_newv (const gchar *signal_name,
GType itype,
GSignalFlags signal_flags,
GClosure *class_closure,
GSignalAccumulator accumulator,
gpointer accu_data,
GSignalCMarshaller c_marshaller,
GType return_type,
guint n_params,
GType *param_types);
The number of parameters to these functions is a bit intimidating but they are relatively
simple:
signal_name: is a string which can be used to uniquely identify a given signal.
itype: is the instance type on which this signal can be emitted.
signal_flags: partly defines the order in which closures which were connected to the
signal are invoked.
class_closure: this is the default closure for the signal: if it is not NULL upon
the signal emission, it will be invoked upon this emission of the signal. The
moment where this closure is invoked compared to other closures connected to that
signal depends partly on the signal_flags.
accumulator: this is a function pointer which is invoked after each closure
has been invoked. If it returns FALSE, signal emission is stopped. If it returns
TRUE, signal emission proceeds normally. It is also used to compute the return
value of the signal based on the return value of all the invoked closures.
For example, an accumulator could ignore
NULL returns from closures; or it
could build a list of the values returned by the
closures.
accu_data: this pointer will be passed down to each invocation of the
accumulator during emission.
c_marshaller: this is the default C marshaller for any closure which is connected to
this signal.
return_type: this is the type of the return value of the signal.
n_params: this is the number of parameters this signal takes.
param_types: this is an array of GTypes which indicate the type of each parameter
of the signal. The length of this array is indicated by n_params.
As you can see from the above definition, a signal is basically a description
of the closures which can be connected to this signal and a description of the
order in which the closures connected to this signal will be invoked.
Signal connection
If you want to connect to a signal with a closure, you have three possibilities:
You can register a class closure at signal registration: this is a
system-wide operation. i.e.: the class closure will be invoked during each emission
of a given signal on any of the instances of the type which supports that signal.
You can use g_signal_override_class_closure which
overrides the class closure of a given type. It is possible to call this function
only on a derived type of the type on which the signal was registered.
This function is of use only to language bindings.
You can register a closure with the g_signal_connect
family of functions. This is an instance-specific operation: the closure
will be invoked only during emission of a given signal on a given instance.
It is also possible to connect a different kind of callback on a given signal:
emission hooks are invoked whenever a given signal is emitted whatever the instance on
which it is emitted. Emission hooks are used for example to get all mouse_clicked
emissions in an application to be able to emit the small mouse click sound.
Emission hooks are connected with g_signal_add_emission_hook
and removed with g_signal_remove_emission_hook.
Signal emission
Signal emission is done through the use of the g_signal_emit family
of functions.
void g_signal_emitv (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value);
The instance_and_params array of GValues contains the list of input
parameters to the signal. The first element of the array is the
instance pointer on which to invoke the signal. The following elements of
the array contain the list of parameters to the signal.
signal_id identifies the signal to invoke.
detail identifies the specific detail of the signal to invoke. A detail is a kind of
magic token/argument which is passed around during signal emission and which is used
by closures connected to the signal to filter out unwanted signal emissions. In most
cases, you can safely set this value to zero. See for
more details about this parameter.
return_value holds the return value of the last closure invoked during emission if
no accumulator was specified. If an accumulator was specified during signal creation,
this accumulator is used to calculate the return value as a function of the return
values of all the closures invoked during emission.
If no closure is invoked during
emission, the return_value is nonetheless initialized to zero/null.
Signal emission is done synchronously and can be decomposed in 5 steps:
RUN_FIRST: if the
G_SIGNAL_RUN_FIRST flag was used
during signal registration and if there exists a class closure for this signal,
the class closure is invoked.
EMISSION_HOOK: if any emission hook was added to
the signal, they are invoked from first to last added. Accumulate return values.
HANDLER_RUN_FIRST: if any closure were connected
with the g_signal_connect family of
functions, and if they are not blocked (with the g_signal_handler_block
family of functions) they are run here, from first to last connected.
RUN_LAST: if the G_SIGNAL_RUN_LAST
flag was set during registration and if a class closure
was set, it is invoked here.
HANDLER_RUN_LAST: if any closure were connected
with the g_signal_connect_after family of
functions, if they were not invoked during HANDLER_RUN_FIRST and if they
are not blocked, they are run here, from first to last connected.
RUN_CLEANUP: if the G_SIGNAL_RUN_CLEANUP flag
was set during registration and if a class closure was set,
it is invoked here. Signal emission is completed here.
If, at any point during emission (except in RUN_CLEANUP or
EMISSION_HOOK state), one of the closures stops the signal emission with
g_signal_stop_emission,
emission jumps to RUN_CLEANUP state.
If, at any point during emission, one of the closures or emission hook
emits the same signal on the same instance, emission is restarted from
the RUN_FIRST state.
The accumulator function is invoked in all states, after invocation
of each closure (except in RUN_EMISSION_HOOK and
RUN_CLEANUP). It accumulates
the closure return value into the signal return value and returns TRUE or
FALSE. If, at any point, it does not return TRUE, emission jumps
to RUN_CLEANUP state.
If no accumulator function was provided, the value returned by the last handler
run will be returned by g_signal_emit.
The detail argumentAll the functions related to signal emission or signal connection have a parameter
named the detail. Sometimes, this parameter is hidden by the API
but it is always there, in one form or another.
Of the three main connection functions,
only one has an explicit detail parameter as a GQuark:
g_signal_connect_closure_by_id.
A GQuark is an integer which uniquely represents a string. It is possible to transform
back and forth between the integer and string representations with the functions
g_quark_from_string and g_quark_to_string.
The two other functions,
g_signal_connect_closure and
g_signal_connect_data
hide the detail parameter in the signal name identification.
Their detailed_signal parameter is a
string which identifies the name of the signal to connect to.
The format of this string should match
signal_name::detail_name. For example,
connecting to the signal named
notify::cursor_position will actually
connect to the signal named notify with the
cursor_position detail.
Internally, the detail string is transformed to a GQuark if it is present.
Of the four main signal emission functions, one hides it in its
signal name parameter:
g_signal_connect.
The other three have an explicit detail parameter as a
GQuark again:
g_signal_emit,
g_signal_emitv and
g_signal_emit_valist.
If a detail is provided by the user to the emission function, it is used during emission to match
against the closures which also provide a detail.
If a closure's detail does not match the detail provided by the user, it
will not be invoked (even though it is connected to a signal which is
being emitted).
This completely optional filtering mechanism is mainly used as an optimization for signals
which are often emitted for many different reasons: the clients can filter out which events they are
interested in before the closure's marshalling code runs. For example, this is used extensively
by the notify signal of GObject: whenever a property is modified on a GObject,
instead of just emitting the notify signal, GObject associates as a detail to this
signal emission the name of the property modified. This allows clients who wish to be notified of changes
to only one property to filter most events before receiving them.
As a simple rule, users can and should set the detail parameter to zero: this will disable completely
this optional filtering for that signal.