Re: Exporting the Gtk+ object system to interpreters



Tim Janik <timj@gtk.org> writes:

> On 19 Dec 1998, Marius Vollmer wrote:
> 
> > Tim Janik <timj@gtk.org> writes:
> > 
> > > please read the C comments below more carefully. default handlers
> > > need to be setup in class_init() functions and from then on are
> > > considered static data.
> > 
> > Why should they be static?  Granted, most of them will be static, but
> > it would sure be handy to change a default handler at any time while
> > developing a program in a more dynamic language.
>
> i don't really understand what your first two sentences meant to
> express.

In some languages you can develop your code interactively.  You can
redefine functions while the system is running for example.  Think of
Emacs as an example.  When Gtk is part of such a system, it would be
consequent to be able to interactively change the default handlers of
existing classes.

> [...], we still have to take care to adhere to existing concepts and
> to preserve semantics and assumptions that gtk-based code currently
> makes, one of which is, that signal default handlers provide certain
> functionality and behaviour that doesn't change throughout a
> programs life time. thus, their setting occours only once and
> remains static from then on, e.g. default handler chaining becomes
> pointless otherwise.

I think you are confusing interface and implementation here.  Of
course, client code expects certain behaviours and guarantees from a
specific default handler.  That is its interface.  But it is possible
to have more than one implementation that conforms to this interface,
for example one that is buggy and one that is less so.  Furthermore,
it is possible to extend an interface in a commpatible way so that
existing code continues to run while also allowing new code to do new
things.

Changes to C code require another compile/run cycle, while in dynamic
languages like Perl or Scheme you can make these changes while the
system is running.  As long as you are careful not to break existing
code, all is fine.

You know the Gtk code base much better than I do, so which code breaks
when I install a new implementation for a default handler at run-time
that conforms to the established interface?  What breaks when I
replace the existing gtk_real_button_pressed with

    static void
    gtk_real_button_pressed_2 (GtkButton *button)
    {
      GtkStateType new_state;

      g_return_if_fail (button != NULL);
      g_return_if_fail (GTK_IS_BUTTON (button));

      button->button_down = TRUE;

      new_state = (button->in_button ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL);

      if (GTK_WIDGET_STATE (button) != new_state)
	{
	  gtk_widget_set_state (GTK_WIDGET (button), new_state);
	  gtk_widget_queue_draw (GTK_WIDGET (button));
	  fprintf (stderr, "%p: new state %d, queued for draw\n", 
                   button, new_state);
	}
    }

    ...
    klass->pressed = gtk_real_button_pressed_2;
    ...

because I want to debug a strange race condition in the redraw-queue
code?  Assuming that there actually is code that breaks because of
this, don't you think that would be a bug in that code?

Further, don't you think that one can design and implement new classes
with the possibility in mind that a default handler might be changed
at run-time?  That, just hypothetically, the widget writer who wants
to change default handlers at run-time makes sure that he does not
break his own code?  What are you afraid of?  Rogue coders that just
wait for the possibility to change default handlers at run-time to
cause trouble?  They can do that already of course.

> i'm not trying to put your suggestions down and am not attempting to
> keep gtk's facilities within some limited, or privately known,
> scope, but it appears to me that you haven't fully understood some
> of gtk's fundamental concepts and are taking comments from me
> personally, which are actually meant to be of technical merit and to
> reflect those concepts and their constrains.

I do not take your comments personally, i.e., I do not interpret your
postings as having the secret agenda "Marius made the proposal, so
it's bullshit, no need to think about it, just reply with some shallow
drivel."  But, I do think that we are having a slight personality
problem here as well.  Let me be totally open: I still think that I'm
a better hacker than you are and that most of your concerns and
argumentations are "newbieish".  There are a lot of things I do not
know and still more that I do not understand.  But in my experience I
generally know when I don't know and when I don't understand.  In this
case, I'm very much certain that I do know what's going on but I have
the feeling that I cannot get my points across because you have a
quite different--in my opinion inferiour--view of tasty design.  I do
not think that we are having a fruitful technical discussion here.

This is the bazaar, right?  It's a lot about egos.  I want to work on
Gtk+ because I think it is the single most important free software
component that's happening right now and because I think that I can do
one or two things and do them right.  Doing things the right way is
fun.  You are spoiling this fun for me, and for reasons that I cannot
follow.  You appear to me to be someone who thinks of the C language
and the existing Gtk+ code base as the epitomes of good design, and
does not see the pragmatic hacks that they contain in more quantity
than is good for them.  In my view, at least.  It is worrying to me to
see that you seem to be content with the things you think you know.

So, those last paragraph may well have spoiled all further chances of
ever having a `fruitful technical discussion' but I thought it
important to explain my view of the situation.  Make the best of it.

> rather than fighting a battle on some personal level, i'd like us to
> figure out an elegant and hopefully comprehensive way to provide the
> required functionality while keeping the API and implementation
> reasonably simple and efficient, and of course while adhering to
> existing concepts.

Very fine, of course, but I think you need to work harder if you want
to really get there.  This might be a personal comment, which you
don't want to hear, but I don't think we can leave our persons out of
this game.

> > > thus, the data argument does not need a destroy notifier.
> > 
> > Yes, it might not need one, but it is part of the "full" callback
> > specification.  We can ignore it if the data is never actually
> > destroyed, but it should be included in the interface for setting a
> > default handler.
> 
> i still don't get why a destroy notifier argument should be present,
> especially since you are suggesting that we should ignore it.

You do not seem to know about the "full" interface for passing
callbacks into Gtk+ that I was referring to.  It is used for functions
like gtk_timeout_add_full, gtk_idle_add_full, gtk_input_add_full,
gtk_signal_connect_full and gtk_container_foreach_full.  It would only
be good design to use this "existing convention" also for setting
default handlers.  The "full" interface has proven to be complete
enough and is presumably well understood by the glue guys and would be
immediatly recognized.

Being able to use the existing "full" callback convention requires two
things: including the GtkDestroyNotifier (even if it would be useless
with our current implementation), and being able to use the existing
GtkCallbackMarshall function prototype.  Both things can be done
without any contortions or compromises.

> > I don't think we need to restrict the setting of default handlers
> > to the class initializer, tho.
> 
> it will, however, in most cases be required for default handlers to
> be setup appropriatedly upon clases creation time, since they mostly
> are initialized during the first object instantiation for this type
> (or derived types), and the object initializers already rely on the
> default handlers to be setup properly.

Of course.  The class initialization function is a way to delay the
actual class initialization until it is really needed for the first
time.  It is a service provided by the type system.  I can use it, but
I can also choose to not use it.  I didn't use it in my experiments
with the Gtk+ object system that I did in Scheme and it was very
reasonable.  I just initialized the class immediatly after registering
it with gtk_type_unique.

> > The instance_type can be omitted just the same.  The first indication
> > why this is the case is that the existing default handlers don't need
> > it either.
> > 
> > Look at this code from gtkwindow.c:
> > 
> >     static void
> >     gtk_window_destroy (GtkObject *object)
> >     {
> >       [...]
> >       GTK_OBJECT_CLASS (parent_class)->destroy (object);
> >     }
> > 
> > This is the default handler of the GtkWindow widget for the "destroy"
> > signal.  It `knows' this.  It knows the type of the object it is
> > associated with, and it knows the appropriate parent_class.  It knows
> > that it is a "destroy" default handler.  This knowledge does not have
> > to be passed to it from the Gtk+ signal or type system.  It has been
> > `built in' by the writer of the code.  All this can (and has to) be
> > built into a Scheme or Perl default handler, too, by the writer of
> > that handler, not by the general glue code.
> 
> this applies to C code only. since from scheme itself or even from the glue
> code you can't invoke GTK_OBJECT_CLASS (parent_class)->destroy (object),
> because you can't dynamically build up the parameter list for the signal
> handler (most handlers take extra arguments and return values, e.g.
> gint (* button_press_event)      (GtkWidget          *widget,
>                                   GdkEventButton     *event);
> or
> void (* size_allocate)       (GtkWidget      *widget,
>                               GtkAllocation  *allocation);)
> you need gtk to do the marshalling for you (especially since the marshallers
> for gtk are already in place in the signal system).
> thus you need a function like the one i formerly proposed:
> 
> /* function to be used internally from within the signaling system,
>  * and from unmarshalled default handlers to chain the parent class'
>  * default handler. GTK_TYPE_OBJECT (object) is required to
>  * be derived from instance_type.
>  */
> void gtk_object_invoke_default_handler (GtkObject *object,
>                                         GtkType    instance_type,
>                                         guint      signal_id,
>                                         GtkArg    *params);
> 
> without `instance_type', gtk_object_invoke_default_handler() wouldn't know
> whether it is supposed to call gtk_window_destroy, gtk_bin_destroy,
> gtk_container_destroy, gtk_widget_destroy ot gtk_object_destroy, since they
> are all default handlers for the `destroy' signal of `object'.

I was not arguing that instance_type is redundant for
gtk_object_invoke_default_handler.  It is not, obviously.  I was
arguing that it does not need to be explicitely passed to the default
handler, thus making it possible to use GtkCallbackMarshall.

> > More abstractly, using artificial invoke_default_handler and
> > set_default_handler functions and signals that have no signal-specific
> > parameters:
> > 
> > Suppose you have these two functions:
> > 
> >   void
> >   set_default_handler (GtkType   klass,
> >                        gchar    *signal_name,
> >                        void    (*handler) (GtkType instance_type,
> >                                            void *data),
> >                        void     *data);
> > 
> >     Set the default handler of KLASS for the signal denoted by
> >     SIGNAL_NAME to be HANDLER, and associate it with DATA.
> 
> hu? where's the GtkObject* pointer in the default handler prototype?

It's irrelevant for the discussion at hand.
 
> >   void
> >   invoke_default_handler (GtkType  klass,
> >                           gchar   *signal_name);
> > 
> >     Invoke the default handler of KLASS for the signal denoted by
> >     SIGNAL_NAME.  This invokes the function last set via
> > 
> >       set_default_handler (klass, signal_name, data, handler);
> > 
> >     as
> > 
> >       handler (klass, data);
> > 
> >     or, when set_default_handler has never been called for the
> >     combination of KLASS and SIGNAL_NAME, it is equivalent to
> > 
> >       invoke_default_handler (type_parent (klass), signal_name);
> > 
> >     or, when KLASS has no parent type, it does nothing at all.
> > 
> > Do we agree that this is the semantic of default handlers?

Do we agree?

> > As you can see, the instance type is no longer associated with the
> > precise handler.  But, it is still associated with the call to
> > set_default_handler, and thus, DATA1 and KLASS1, as well as DATA2 and
> > KLASS2 will always be associated.  We again can avoid passing the
> > instance_type parameter because we can put it into the things hanging
> > off of the data cookie.
> 
> i don't understand this paragraph at all, since you totally omitted the
> object pointer that the default handler is to be invoked for.

Try again.

> in case you simply omitted it because of implicit inclusion like for
> c++ methods, where you'd have:
> 
> object->handler (klass1, data);
> 
> the real prototype of this handler would be:
> 
> void handler (GtkObject*, GtkObjectClass*, gpointer data);
> 
> in which case our *only* difference is that i passed the `instance_type'
> as a GtkType argument and you used a GtkObjectClass* argument.

The `formalism' thing that I exposed is not the thing that I want to
implement.  I used it only to support my argument that the
instance_type does not need to be passed at all.  And, I only did this
because I think it is important that you understand it, and to help you to
find real counter arguments.  I can live with the instance_type being
passed explicitely, as I have to install some special treatment of
default handlers in guile-gtk anyway (for unrelated reasons).  But I
do think that not using the established "full" callback interface
would be a wart on the Gtk+ API, something you expressively want to
avoid.

What worries me more is that you are making such a fuss about the
thing without showing any recognizable signs of effort to understand,
and that you insist that when you don't understand it, it is wrong.  I
have given you the framework, which I really expect you to understand
for what it is.  Now please explain why you think it is wrong.  Start
at the "Do we agree?" question.  If you don't want to put that much
work into this thing, which is ok too, please leave it alone.

> though i'm not sure that's what you are aiming at, since in your above
> example, set_default_handler() took a default handler argument of
> void    (*handler) (GtkType instance_type,
>                     void *data);
> which is of course totaly confusing class pointers with type id's in
> the way you used that prototype later on.

GtkType and class pointers are interchangeable for the purpose of this
discussion.

> > For my Scheme bindings, I wouldn't even need to do this, because the
> > Scheme procedures can do it for themselves if they want to.
> > 
> > [ The exact same argumentation can be used to eliminate the signal_id
> >   parameter, btw, and I'm sure you used it already for discovering
> >   this fact.
> > ]
> 
> well, you could probably demand that the user specifies which default
> handler should be invoked, and use that as a `instance_type' argument
> for gtk_object_invoke_default_handler(),

Exactly!

> though something like "(chain-parent)" would probably be much nicer.

No. 

I already explained why I don't want it, but a simple "No" must be
enough actually.

[ Tim, it is one thing to care about the integrity of Gtk+ and another
  to talk into other peoples designs.  I'm not going to let you decide
  what would probably be nice for my Scheme bindings.  If you need
  this usurping to justify unclean Gtk+ designs, it gets even worse.
]

> in any case, by omitting the `instance_type' and `signal_id'
> arguments, default handlers could never be totally automated
> functions since both of these parameters couldn't be figured without
> extra user intervention.

You need this extra intervention at the call of set_default_handler
and inside the invoked handler.  These two spots are closely coupled
and the code that does the set_default_handler call has complete
control over the invoked handler, too, so there is no problem in
making this coordination happening.  The advantage is that the code
that does need this intervention can do exactly what it needs to do.
We do not need to second guess it.

To clarify my point here is some code that implements default handlers
that receive the instance_type and signal_name on top of a
set_default_handler_light that does not provide this functionality.

  /* This is the existing interface for setting default handlers.
  */
  void
  set_default_handler_light (GtkType   klass,
                             gchar    *signal_name,
                             void    (*handler) (void *data),
                             void     *data);

  /* This is how to use it to set handlers that want to receive more
     arguments.
  */

  struct fat_data {
    void (*fat_handler) (GtkType, char*, void*)
    GtkType instance_type;
    char *signal_name;
    void *data;
  };

  static void
  fat_trampoline (void *data)
  {
     struct fat_data *fdata = data;
     fdata->fat_handler (fdata->instance_type, fdata->signal_id, fdata->data);
  }

  void
  set_default_handler_fat (GtkType   klass,
                           gchar    *signal_name,
                           void    (*handler) (GtkType instance_type,
                                               char *signal_name,
                                               void *data),
                           void     *data)
  {
    struct fat_data *fdata = g_new (sizeof(struct fat_data));
    fdata->fat_handler = handler;
    fdata->instance_type = klass;
    fdata->signal_name = signal_name;
    fdata->data = data;
    set_default_handler_light (klass, signal_name, fat_trampoline, fdata);
  }

> if you are so concerned about using the same prototype for
> GtkCallbackMarshal handlers and GtkUnmarshalledFunc, we can change
> that prototype to
> 
> typedef void (*GtkUnmarshalledFunc) (GtkObject   *object,
>                                      gpointer     data,
>                                      guint        n_params,
>                                      GtkArg      *params,
>                                      GtkType      instance_type,
>                                      guint        signal_id);
> so it adheres to
> typedef void (*GtkCallbackMarshal) (GtkObject *object,
>                                     gpointer   data,
>                                     guint      n_args,
>                                     GtkArg    *args);
> and you don't have to worry about different semantics.

I thought about this, too, but it's only a half-assed solution that is
no solution at all.  It would still be a different callback interface
because the C type system does not let you pass a GtkCallbackMarshal
as a GtkUnmarshalledFunc.  You would have to use a cast.  In essence,
GtkUnmarshalledFunc is not `derived' from GtkCallbackMarshal.  Anyway,
I'm now not so much concerned about code bloat (which might have been
the impression from my earlier posts) but about interface bloat.  From
an interface bloat point of view, it is actually bad to relate the two
prototypes in such a way.

Plus, I'm not sure whether it is well defined in C what happens when
you call a function thru a function pointer of a non-matching type.  I
can imagine that there are ABIs out there that cannot handle this case
(because of arcane register allocation schemes, maybe).  Are you sure
that this would work?

> > Below, you mention "(chain-parent)", which is supposed to
> > automatically invoke the default handler for the `current' signal of
> > the parent of the `current' type.  I don't want to have such a thing
> > at this stage, and in any case, implementing it is outside the scope
> > of the Gtk+ type system (or even my Scheme bindings for that matter).
> 
> a possible implementation of a gtk_object_invoke_default_handler()
> function would not be part of gtk's type system, but gives an API to
> the marshalling abilities provided by gtk's signal system.

Ok, then let's talk about what would need to be changed inside Gtk+'s
type system.

> i don't think we should omit `instance_type' and `signal_id',
> because you personally don't want to provide "(chain-parent)"
> fucntionality at this point, i'm pretty sure these parameters could
> come in handy for other interpreter bindings that want to provide
> (chain-parent) fucntionality or other generic default handler
> implementations that haven't yet been considered.

I say that we don't loose any functionality by *not* including them
(see set_default_handler_fat above), but we avoid a (gratuitous)
interface bloat that way.

> > Ahh, I think I understand now where our interpretions differ.  The
> > status quo is:
> > 
> >     Non-default handlers can be connected as plain C functions that
> >     make use of the default marshaller provided by the signal.
> >     Non-default handlers can also be connected as custom-marshalled
> >     functions that bring their own marshaller.
> > 
> >     Default handlers can be set as plain C functions that make
> >     use of the default marshaller provided by the signal.
> > 
> >     To connect a non-default handler, you use gtk_signal_connect_full
> >     which allows for both plain C functions as well as
> >     custom-marshalled ones.  You can also use gtk_signal_connect as a
> >     convenience function for plain C function.
> >     To set a default handler, you just put a pointer to the function
> >     in the appropriate class field.
> > 
> > (I gloss over how to invoke handlers here.)
> > 
> > I want to even out this unsymmetric situation so that non-default
> > handlers and default handlers are more `equal'. 
> 
> stop here, please... ;)
> 
> the situation is "unsymmetric" for good reasons actually, default
> handlers and non-default handlers are conceptually very different things,
> and that is reflected by various technical details in gtk's API and
> implementation. once you gathered the conceptual differences, i hope you
> figured why "equalizing" them is a bad idea ;)

Non-default handlers and default handlers have differences but they
also have similarities.  The differences should be there for good
reasons and the similarities should go as far as reasonable.  You list
the differences, but what we are talking about here are actually the
similarities:

 - Both are functions that need to be invoked at certain times.
 - Both should be able to be ordinary C functions.
 - Both should be able to be `interpreted functions', that is, they
   should have (at least) the "full" interface available.

With your proposed GTK_RUN_UNMARSHALLED signals, you are not enforcing
the differences between non-default handlers and default handlers, but
rather the differences between signals that provide a default
marshaller and ones that do not.  This is not a useful difference to
amplify.

> a default handler is the implementation of an object method
> associated with certain object classes. the reason for default
> handlers to be invoked through function pointers is not because it
> is a dynamic callback method like normal user provided signal
> handlers are, but to give an ability to have user provided signal
> handlers be invoked before, after or inbetween invokations of
> default handlers, and to give the ability to "chain" parent classes
> default handlers.

How is "being [directly] invoked thru a function pointer" the
prerequisite for the things you expect from default handlers?  I can
see the need for speed as a convincing argument, tho.

> whether those callbacks or default handlers are called in unmarshalled or
> in marshalled forms, are actually technical particularities, that don't
> relate to their fundamental concepts at all.

Exactly.  I don't want to have a fundamentally different signal type
for signals without a default marshaller.

> unmarshalled callback invokations have been brought up only for the
> sake of interpreter bindings anyways, and thus you should be pretty
> satisfied with my formerly proposed introduction of
> GTK_RUN_UNMARSHALLED signals

They do work but have an unnecessarily complicated interface.

> and the constrain that default handlers with a function_offset of 0
> are always called in unmarshalled form.

What about the current "user signals"?  They do have a function_offset
of 0 but do also use default marshallers.  Should their functionality
be removed?  We would have the rule that signals without a
default_marshaller must have a function_offset of 0, however (which
does not mean that it can't have a default handler).

----------------

Ok, we need to make some progress.  I don't think I can convince you
about my view of the things.  Either you go ahead and implement your
version and I cope with it (which I can, I'm sure); or you let me
implement my version without making gratuitous changes.  You decide.

You might want to consider introducing gtk_type_query_free, btw.

- Marius



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