rework of the Gtk2-Perl type system



I've run across some problems with the way the Gtk2 bindings handle gtk
types.  I have an idea for how to rework the mechanism, but i'd like to
bounce my thoughts off of you guys and see if i've missed anything.

This will be a little lengthy, but i really want your input -- please
respond!


Disclaimers
-----------

to avoid confusion in the following, i refer to the class of a GObject
as a "class", and the corresponding perl class as a "package" (since
that's how perl classes are defined, anyway).

please understand that i mean no ill will or insult to the authors of
the code i'm about to bash.  revising both design and implementation is
part of development, and invariably the first try has problems that
weren't anticipated in the beginning.



Motivation
----------

As i have mentioned before and you are probably sick of hearing, i have
an independent, GObject-based C library that i wish to bind to perl in
such a way that i can use its objects seamlessly with gtk objects, e.g.,
binding callbacks and packing my library's widgets into windows and
other normal gobject/gtk things, similar to how the Gnome2 package
works.

In the current state of the Gtk2-Perl code, this is not possible.
[explained below]

I'd like to change the way the perl bindings handle GType class to perl
package mappings such that the system is more flexible and extensible. 
This will not benefit only me -- it will help any project that needs to
create bindings for a GObject-based library.


Problems with the current setup
-------------------------------

The current type system is implemented via a handful of functions in
gtk2-perl/Gtk2/src/_Helpers.c, namely

   gtk2_perl_check_class
      used for sanity-checking data on its way from perl to C, croaks
      if the type isn't right

   gtk2_perl_new_object
   gtk2_perl_new_object_null_ok
      wraps up a GObject subclass into a blessed perl object

   gtk2_perl_new_object_from_pointer
   gtk2_perl_new_object_from_pointer_nullok
      wrap up a non-GObject C pointer (such as a GdkRectangle or
      GdkColor other boxed type) in a blessed perl object

So far so good.  these are implemented using some utility methods:

   get_class
      given a GObject pointer, return the perl package in which
      this particular GObject's GType belongs (e.g., for a GtkWidget
      pointer, returns "Gtk2::Widget")

   get_class_from_classname
      given the name of a GType (e.g., "GtkWidget"), return the
      corresponding perl package (e.g., "Gtk2::Widget").

Firstly, these need to be renamed to avoid namespace pollution and
runtime symbol clashes, and also because their names are confusing. 
Secondly, these return malloc'd strings which are not always free'd;
granted, most of the leaks are in calls to croak(), where it would be
rather difficult to free the return value (since croak doesn't return),
but it's not safe to assume this is okay, because if the calling perl
code is wrapped in eval(), the program will not die and the leak will
consume memory resources.  These are minor details, however.

You find the real problems when you dig into build_class_name (which is
called by both get_class and get_class_name).  This function attempts to
convert a class name into a package name according to a hard-coded list
of mapping rules.  If the class cannot be mapped according to any of
these rules, the mapping fails, and get_class_from_class_name (which is
called by get_class, too) croaks.

This makes it impossible to use with Gtk2-Perl any class which does not
fit those mapping rules, so basically, anything that's not part of Gtk
or Gnome.  (the Gnome mappings are hardcoded into the Gtk bindings, even
though they are distributed separately).



On the other end, gtk2_perl_new_object_from_pointer (which is called by
all of the gtk2_perl_new_object_* functions) calls another function
named gtk2_perl_load_class, which ensures that a perl package has been
loaded.  it first checks in a cache to see if the package name exists,
and if not, builds up a path to be passed to require (e.g., to load
"Gtk2::Widget", it calls the equivalent of the perl code 'require
"Gtk2/Widget.pm";').

This serves to ensure that for any object you are about to return from C
to perl, the perl package associated with that class is loaded and
initialized.

The fundamental assumption here is that the object has been implemented
with its own *.pm, as is the case with Inline.  I could write a book on
why i think Inline is not appropriate for a project of this size, and in
fact the requirement at every class has a separate pm is one of the
reasons, but i'll reserve that argument for a future treatise.  Suffice
it to say that if we want to encourage people to use the Gtk2 bindings
we shouldn't require that they use Inline.  (i have no intention of
using Inline for my own project, because i find XS to be far more
straightforward when you already have to deal with perl's internal API.)

Basically, if you don't have a *.pm for the class you've mapped, the
script will die in the require_pv call because perl can't find the
required file.



What would be better?
---------------------

I want to make it easy to integrate any other GObject class into the
Gtk2-Perl system.

I also want to remove the requirement that every package be implemented
in its own *.pm.  It can be done; recall that the original gtk-perl
module had one .pm and one .so to implement the entire binding, and
three or four extra .pms implementing pure perl utilities.


Both of these could be accomplished by requiring class-to-package
mappings to be registered at runtime.   This registration could happen
in Gtk2->init, or even in a mass bootstrapper.  In Gtk-Perl, for
example, the toplevel bootstrap function calls the boot code for all the
other XS modules; the code for this is generated by paolo's 
ExtUtils::Depends, called from Makefile.PL.  (In fact, ExtUtils::Depends
also installs typemaps and headers and config information needed for
building client modules.  It's very cool, and i think we should use it.)


I propose something like

   void gtk2_perl_register_class_mapping (const char * class,
                                          const char * package,
                                          gboolean require);

Using a string for the class name instead of a GType allows us to
specify mappings for non GObject types (structs and boxed types).  The
"require" parameter lets you specify whether the class has a pm that
must be loaded.



For simplicity, i imagine the class mappings as being one-to-one:

   GtkWidget <=> Gtk2::Widget
   GdkWindow <=> Gtk2::Gdk::Window
   GdkEventKey <=> Gtk2::Gdk::Event::Key
   PangoFontDescription <=> Gtk2::Pango::FontDescription
   GnomeAbout <=> Gnome2::About
   MylibFooBar <=> Mylib::FooBar

With one-to-one mappings, get_class_from_classname could return a
pointer that need not be freed, and thus avoid memory leaks.

On the other hand, it might also be possible to do something like

   void gtk2_perl_register_class_mapping_rule (char * perl_code_to_eval)

which would allow you somehow to specify a substitution pattern for a
whole set of mappings, to be run as a code reference, and successful
results cached to avoid running it again.  I am willing to implement
this, but i want help designing that portion of the interface.



get_class and get_class_from_classname should be renamed to avoid both
symbol clashes (the module must be loaded with RTLD_GLOBAL!) and the
confusion of calling everything classes.  i propose:

  char * gtk2_perl_get_package_from_gobject (GObject *)
  char * gtk2_perl_get_package_from_gtype (GType)
  char * gtk2_perl_get_package_from_classname (char *)
     retrieve package names

  GType gtk2_perl_get_type_from_package (char *)
  char * gtk2_perl_get_classname_from_package (char *)
     the other way 'round


Also, we could let the GType system do a lot of work for us by
implementing @ISA as a tied variable which uses
gtk2_perl_get_package_from_class, g_type_parent, and
g_type_get_class_from_package.



Status
------

I have a rudimentary implementation of the basics of the type
registration, but i haven't gotten it working yet because the the
process of closing a loan on a house really saps away your time and
energy for everything else in life.  I plan to get a prototype working
before the end of the weekend.


I would like as much input as possible so i can do the right thing.



-- 
muppet <scott asofyet org>




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