Re: GSignal convenience functions



On Thu, 7 Dec 2000, James Henstridge wrote:

> I have done a little testing of these functions, and they seem to work
> well.  One bit of ugly code is putting the instance variable in the GValue
> array.  At the moment, I misuse the collect_value function for the type
> (if it is available) to set the value, or just set it as a G_TYPE_POINTER
> value.  This could probably be cleaned up a bit more.

i have those functions already (or equivalents at least), though i originally
intended to do things like hovoc, i.e. implement g_object_emit_signal().
after some initial starring, i adopted your idea of "misusing" collect_value
though, and in lack of set_as_pointer() i went for a more general implementation
of that as g_value_set_instance() (my function is a bit more complicated though,
to preserve collect_value() constrains). the G_TYPE_POINTER hack is not viable
though, we need to hold reference counts across signal emissions.
FYI, here's the implementation:

void
g_value_set_instance (GValue  *value,
                      gpointer instance)
{
  g_return_if_fail (G_IS_VALUE (value));

  g_value_reset (value);
  if (instance)
    {
      GType g_type = G_VALUE_TYPE (value);
      GTypeValueTable *value_table = g_type_value_table_peek (g_type);
      GTypeCValue cvalue = { 0, };
      guint nth_value = 0;
      guint collect_type = value_table->collect_type;
      gchar *error_msg;

      g_return_if_fail (G_TYPE_CHECK_INSTANCE (instance));
      g_return_if_fail (g_type_is_a (G_TYPE_FROM_INSTANCE (instance), G_VALUE_TYPE (value)));
      g_return_if_fail (value_table->collect_type == G_VALUE_COLLECT_POINTER);

      cvalue.v_pointer = instance;
      error_msg = value_table->collect_value (value, nth_value++, &collect_type, &cvalue);

      /* this shouldn't be triggered, instance types should collect just one pointer,
       * but since we have to follow the calling conventions for collect_value(),
       * we can attempt to feed them with 0s if they insist on extra args.
       */
      while (collect_type && !error_msg)
        {
          memset (&cvalue, 0, sizeof (cvalue));
          error_msg = value_table->collect_value (value, nth_value++, &collect_type, &cvalue);
        }

      if (error_msg)
        {
          g_warning ("%s: %s", G_STRLOC, error_msg);
          g_free (error_msg);

          /* we purposely leak the value here, it might not be
           * in a sane state if an error condition occoured
           */
          memset (value, 0, sizeof (*value));
          value->g_type = g_type;
          value_table->value_init (value);
        }
    }
}

> We may want to change the name of g_signal_connect_cclosure
> though.  Alternatively, with a few #defines, we could provide APIs more
> similar to gtk_signal_connect, _after, _object and _object_after in terms 
> of connect_cclosure.

hm, your g_signal_connect_cclosure() doesn't actually take a closure
argument, so the name is indeed a bit odd. as for your emit functions,
what we basically needed was:

void    g_signal_emit_valist                  (gpointer           instance,
                                               guint              signal_id,
                                               GQuark             detail,
                                               va_list            var_args);

> There are probably a few problems with this patch, but it is probably a
> good start for these functions.

on the implementation:

> +guint
> +g_signal_new (const gchar	 *signal_name,
> +	      GType		  itype,
> +	      GSignalFlags	  signal_flags,
> +	      guint		  function_offset,
> +	      GSignalAccumulator  accumulator,
> +	      GSignalCMarshaller  c_marshaller,
> +	      GType		  return_type,
> +	      guint		  n_params,
> +	      ...)
> +{
[...]
> +}

this is basically what i have, though g_signal_new() currently
takes a closure:
guint
g_signal_new (const gchar       *signal_name,
              GType              itype,
              GSignalFlags       signal_flags,
              GClosure          *class_closure,
              GSignalAccumulator accumulator,
              GSignalCMarshaller c_marshaller,
              GType              return_type,
              guint              n_params,
              ...);
the class_closure argument will probably vanish when i commit the neccessary
bits to allow LB chaining of class signal handlers (because a signal can
then have multiple class_closures).

> +guint
> +g_signal_connect_cclosure (gpointer           instance,
> +			   gchar             *signal,
> +			   GQuark             detail,

if you take the signal as a string, the detail is encoded in that.

> +			   GCallback          func,
> +			   gpointer           user_data,
> +			   gboolean           after,
> +			   gboolean           swap)

> +static gboolean
> +g_signal_collect_params (GValue      *instance_and_params,
> +			 guint        n_params,
> +			 GType        itype,
> +			 const GType *param_types,
> +			 gpointer     instance,
> +			 va_list      var_args)
> +{
> +  GTypeValueTable *value_table;
> +  register GValue *last_param;
> +  register gboolean failed = FALSE;
> +
> +  /* set instance member.  Misuse the collect_value method if
> +   * available. Otherwise, just use G_TYPE_POINTER. */
> +  value_table = g_type_value_table_peek (itype);
> +  if (value_table->collect_value)
> +    {
> +      GType collect_type;
> +      GTypeCValue collect_value;
> +      gchar *error;
> +
> +      g_value_init (instance_and_params, itype);
> +      collect_value.v_pointer = instance;
> +      error = (value_table->collect_value) (instance_and_params, 0,
> +					    &collect_type, &collect_value);
> +      if (error)
> +	{
> +	  g_warning("g_signal_collect_params(): %s", error);
> +	  g_free(error);
> +	  failed = TRUE;
> +	}
> +    }
> +  else
> +    {
> +      /* not perfect, but will work with the generated marshalers */
> +      g_value_init (instance_and_params, G_TYPE_POINTER);
> +      g_value_set_pointer (instance_and_params, instance);
> +    }
> +
> +  instance_and_params++;
> +  for (last_param = instance_and_params + n_params;
> +       instance_and_params < last_param; instance_and_params++)
> +    {
> +      gchar *error;
> +
> +      g_value_init (instance_and_params, *(param_types++));
> +      G_VALUE_COLLECT (instance_and_params, var_args, &error);
> +      if (error)
> +	{
> +	  failed = TRUE;
> +	  g_warning ("g_signal_collect_params(): %s", error);
> +	  g_free (error);

here, you need to abort the loop, since if collection of a var args list
failed somewhere in the middle, you cannot rely on the rest being
collectable (at least not without crashes ;)
also, you're leaking the correctly collected values if you abort.

> +	}
> +    }
> +
> +  return failed;
> +}
> +
> +void
> +g_signal_emit (gpointer		  instance,
> +	       guint		  signal_id,
> +	       GQuark		  detail,
> +	       ...)
> +{
> +  GValue *instance_and_params;
> +  GValue return_value = { 0, };
> +  GSignalQuery query;
> +  gboolean abort = FALSE;
> +  va_list var_args;
> +  guint i;
> +
> +  g_signal_query (signal_id, &query);
> +  g_return_if_fail (query.signal_id != 0);
> +
> +  instance_and_params = g_new0 (GValue, query.n_params + 1);
> +
> +  va_start (var_args, detail);
> +  abort = g_signal_collect_params (instance_and_params,
> +				   query.n_params,
> +				   query.itype,
> +				   query.param_types,
> +				   instance,
> +				   var_args);
> +  if (!abort)
> +    {
> +      if (query.return_type != G_TYPE_NONE)
> +	g_value_init (&return_value, query.return_type);
> +
> +      g_signal_emitv (instance_and_params, query.signal_id,
> +		      detail, &return_value);
> +
> +      if (query.return_type != G_TYPE_NONE)
> +	{
> +	  gchar *error;
> +	  G_VALUE_LCOPY (&return_value, var_args, &error);

gtk_signal_emit() has a certain "feature" that needs to be preserved
here, that is, if gtk_signal_emit (obj, &ret_val); did not result
in any handlers being called, ret_val's contents are left untouched.
granted, you couldn't apropriately conditionalize G_VALUE_LCOPY() without
modifying signal_emit_R().

> +void
> +g_signal_emit_by_name (gpointer		  instance,
> +		       gchar		 *name,
> +		       GQuark		  detail,
> +		       ...)
> +{
[...]
> +}

as with connect, the detail is encoded in the signal name here, and if
you had implemented emit_valist(), you could have avoided quite some code
duplication here ;)

in general, you could (should) have also used internal gsignal.c functions,
e.g. looking up the signal ids etc, but then again, you're of course not
as familiar with that code as i am ;)

thanks for taking a stab at this though, we probably need more convenience
functions on top of what i have now, and for those i'd actually like to
see patches (they are much easier to implement than monsters like emit_valist,
some maybe even via plain #defines).

to avoid duplication and to let you look for more required convenience,
here's my new API (i'll commit later this day after i compiled the
ChangeLog entries):

guint   g_signal_new                          (const gchar       *signal_name,
                                               GType              itype,
                                               GSignalFlags       signal_flags,
                                               GClosure          *class_closure,
                                               GSignalAccumulator accumulator,
                                               GSignalCMarshaller c_marshaller,
                                               GType              return_type,
                                               guint              n_params,
                                               ...);
guint   g_signal_newv                         (const gchar       *signal_name,
                                               GType              itype,
                                               GSignalFlags       signal_flags,
                                               GClosure          *class_closure,
                                               GSignalAccumulator accumulator,
                                               GSignalCMarshaller c_marshaller,
                                               GType              return_type,
                                               guint              n_params,
                                               GType             *param_types);
void    g_signal_emitv                        (const GValue      *instance_and_params,
                                               guint              signal_id,
                                               GQuark             detail,
                                               GValue            *return_value);
void    g_signal_emit_valist                  (gpointer           instance,
                                               guint              signal_id,
                                               GQuark             detail,
                                               va_list            var_args);
void    g_signal_emit                         (gpointer           instance,
                                               guint              signal_id,
                                               GQuark             detail,
                                               ...);
void    g_signal_emit_by_name                 (gpointer           instance,
                                               const gchar       *detailed_signal,
                                               ...);
guint    g_signal_connect_closure_by_id       (gpointer           instance,
                                               guint              signal_id,
                                               GQuark             detail,
                                               GClosure          *closure,
                                               gboolean           after);
guint    g_signal_connect_closure             (gpointer           instance,
                                               const gchar       *detailed_signal,
                                               GClosure          *closure,
                                               gboolean           after);
guint    g_signal_connect_data                (gpointer           instance,
                                               const gchar       *detailed_signal,
                                               GCallback          c_handler,
                                               gpointer           data,
                                               GClosureNotify     destroy_data,
                                               gboolean           swapped,
                                               gboolean           after);
guint       g_signal_connect_object           (gpointer        instance,
                                               const gchar    *detailed_signal,
                                               GCallback       c_handler,
                                               gpointer        gobject,
                                               gboolean        swapped,
                                               gboolean        after);

g_signal_connect_object() actually resides in gobject.h as it'll only
work with gobject arguments of type GObject* (it has while_alive
semantics out of the box).

> 
> James.
> 

---
ciaoTJ





[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]