NAME

Glib/Gtk2 binding how-to


ABSTRACT

This document describes the process by which the Glib and Gtk2 modules bind the rather sizable and complex GLib and GTK+ C libraries to Perl via Perl's extension mechanism, and the tools and concepts they provide for binding other libraries in a similar fashion.


INTRODUCTION

Here's the situation: you've fallen in love with gtk2-perl because it's about a bazillion times faster and easier to write your application and GUI logic in perl; but you have a GLib- or GTK+-based C library that doesn't have perl bindings. What do you do?

The gtk2-perl project has already solved the tough problems of how to deal with GTypes, GObjects and GtkWidgets, and how to build extension modules that extend other extension modules. In fact, we make available to you all the tools that we use to create the Glib, Gtk2, and Gnome2 modules so you can do the same thing.

What's so hard about that?

What's so hard about binding Gtk-based libraries to perl? It essentially boils down to two problems: the differences between the runtime type and object systems in GLib and Perl, and the logistics of getting the proper code in the proper places.

GLib's GObject (confusingly, both a datatype and a library) includes a sophisticated type system which allows for deep-derived inheritance trees with virtual methods and value tables and type-safe generic values and lots and lots of other goodies... Perl provides analogous functions for each that work in ways just different enough to be annoying. We have to map the C behavior to perl in a reasonable, unsurprising, and consistent way.

And once you figure out how to do all that, how to you get it built? There are practical issues such as sharing functions and macros in the very non-C-like world of the Perl library; where do headers go? How do you link against libraries?

What have you done for me lately?

We have a lot of tools. Check out the SEE ALSO section at the end for a laundry list of reference links, but here's the quick run-down:

ExtUtils::Depends
Paolo Moloro wrote ExtUtils::Depends for Gtk-Perl to solve the logistical problems of how to install and track dependencies between XS extensions. This is a tool for use in a Makefile.PL that looks up information on the modules on which your extension depends, creates the proper ExtUtils::MakeMaker data structure, and then saves that information for other modules to use.

ExtUtils::PkgConfig
The pkg-config utility is a command-line tool used to maintain meta-information about the installed libraries of the Gnome/GTK+ family of projects. ExtUtils::PkgConfig simplifies and unifies the error-handling and usage of pkg-config for finding the compiler and linker flags needed to build your extension.

gperl.h
The Glib module exports several utility functions that perform all the important work for the bindings, and which are critical for use by any of the XS modules which use the bindings. All of this is installed in the gperl.h header file.

Gtk2::CodeGen
Gtk2 maps over three hundred types to Perl; we'd have to be mad to code all that by hand. Therefore we have a couple of very useful code generation utilities to automate the castmacro and typemap madness that makes the XS developer's life so easy.

We'll encounter each of these in the following sections as they become relevant.


Binding a function-only library (no new objects)

Let's get started with something simple. Suppose you have a very simple C library which just defines some extra functions for a set of existing GTK+ objects. A real-life example is GtkSpell and the corresponding Gtk2::Spell module. For the sake of our example, let's suppose that our little library, libmup, defines a handful of functions like

 /* new methods for existing objects */
 void        mup_gtk_window_shake   (GtkWindow    * window,
                                     double         vigor);
 void        mup_gtk_table_add_row  (GtkTable     * table,
                                     int            row, 
                                     GtkWidget    * col1,
                                     GtkWidget    * col2);
 /* convenience functions */
 GtkWidget * mup_create_option_menu (const gchar ** items);

Nothing earthshattering, no new types to manage; but we do have to have access to the typemaps for GtkWindow, GtkTable, and GtkWigdet. If you've read the description of ExtUtils::Depends, you probably already guessed that we can get all of that without a problem.

File Layout

libmup is quite small; its public interface consists of only one C header file. Thus, this will be easy. We need a place to put the xsubs, a perl module to load them and hold the documentation, and a Makefile.PL to coordinate the build. Of course, it should all go into a new directory; let's use 'Mup'.

 Mup
    Makefile.PL  something to build it all
    Mup.pm       perl entry point
    Mup.xs       home for the xsubs

Whereas the MakeMaker manpage says always to start with h2xs, this doesn't apply to modules based on Glib. In practice, you'd find the module that has the same basic layout as you want and edit to taste, but since this is a tutorial I'll walk you through from the ground up.

Let's start from the inside out, with Mup.xs.

  /* we depend directly on the Gtk2 module; its installed header
   * is gtk2perl.h.  this will get gtk/gtk.h, all the gtk2perl 
   * cast macros, and gperl.h. */
  #include <gtk2perl.h>
  /* now we need the actual library we're binding. */
  #include <libmup.h>
  MODULE = Mup  PACKAGE = Gtk2::Window  PREFIX = mup_gtk_window_
  ## call as $window->shake ($vigor)
  void mup_gtk_window_shake (GtkWindow * window, double vigor);
  MODULE = Mup  PACKAGE = Gtk2::Table   PREFIX = mup_gtk_table_
  ## call as $table->add_row ($row, $col1, $col2), where $col1
  ## and $col2 are allowed to be undef.
  void mup_gtk_table_add_row (GtkTable * table, int row, GtkWidget_ornull * col1, GtkWidget_ornull * col2)
  MODULE = Mup  PACKAGE = Mup   PREFIX = mup_
  ## call as $widget = Mup->create_option_menu (@strs);
  GtkWidget *
  mup_create_option_menu (SV * class, ...)
      PREINIT:
          const gchar ** strs;
          int i;
      CODE:
          /* we're ignoring class, which is item 0,
           * but we need a NULL at the end of the array
           * of items-1 strings, so items-1+1 = items */
          strs = g_new0 (gchar*, items);
          for (i = 1 ; i < items ; i++)
                  /* use SvGChar where you have a gchar -- it 
                   * adds UTF-8 conversion to SvPV. */
                  strs[i-0] = SvGChar (ST (i));
          RETVAL = mup_create_option_menu (strs);
          g_free (strs);
      OUTPUT:
          RETVAL

Notice that there was more than just one MODULE line? Each MODULE line is like a 'package' statement in a perl module, it changes the current package, used here so that the methods defined therein work on a different object. It's worth pointing out that i made sure to end the file in the same package as the file defines, otherwise strange things happen in startup.

Also notice that the extension methods for the Window and Table widgets have been transparently added to the objects.

The typemap magic has made this very easy, indeed. The two GtkWidget arguments of mup_gtk_table_add_row may be undef by virtue of using the ``_ornull'' variant of the GtkWidget type. Variants are described in the Glib::devel manpage, and also later in this document.

The xsub for mup_create_option_menu is not so simple as the others; we had to turn a list of strings on the argument stack into a NULL-terminated C string vector. This is mostly straightforward, run-of-the-mill XS, except for the use of g_new0, g_free, and SvGChar.

SvGChar (described in the Glib::xsapi manpage) converts perl scalars to gchar* strings. By convention, a char* is considered to be plain old string data, but a gchar* is considered to be UTF-8 string data. SvGChar uses SvPV_nolen to extract the C string, and then does the proper perl magic to upgrade the string to UTF-8. It's the same function used by the typemaps to convert scalars to gchar* strings; the priciple illustrated here is that the bindings provide a pair of SvMyType / newSVMyType functions for each datatype bound to perl.

Now that we have some XS code, we need a perl module to use it. The minimum would be something like:

  package Mup;
  use Gtk2;
  require DynaLoader;
  our @ISA = qw(DynaLoader);
  our $VERSION = '1.00';
  sub dl_load_flags {0x01};
  bootstrap Mup $VERSION;
  1;
  __END__
  # pod omitted for brevity, but you mustn't forget it!

Pretty much a straightforward, minimal XS-loading perl module. Note that we used DynaLoader directly, rather than using XSLoader, which is the simplified front-end used by recent versions of h2xs. This is because XSLoader doesn't honor dl_load_flags, a special trick that allows us to make available to other modules the C functions defined in this module. [FIXME this is somewhat broken on win32, since you can't even link with unresolved symbols.]

This boilerplate is actually very important -- your XS functions are not available to perl until the bootstrap statement is finished! Also, the 'use Gtk2;' line ensures that all the proper dynamic objects are loaded before you attempt to bootstrap the new module.

Next, we need a way to build the module so we can use it.

setting up Makefile.PL

use ExtUtils::PkgConfig to find the CFLAGS and LIBS for the module

use ExtUtils::Depends to find the attributes of Gtk2

add the new CFLAGS and LIBS

choosing installation locations manually

in this usage of ExtUtils::Depends we are just a client.


Binding a small library

Now suppose you have a library that exports some datatypes, and you need to use those from perl. For this example, let's add a couple of new datatypes to libmup. (libzvt and glib itself are examples of real-world libraries that could be handled in the way described in this section.)

Suppose libmup.h also #includes two other files, mup_dirsel.h and mup_frob.h, which look like this:

  /* mup_dirsel.h */
  typedef struct _MupDirSel      MupDirSel;
  typedef struct _MupDirSelClass MupDirSelClass;
  #define MUP_TYPE_DIR_SEL   (mup_dir_sel_get_type ())
  /* ... the rest of the type macros */
  struct _MupDirSel {
          GtkDialog dialog;
          ...
  };
  struct _MupDirSelClass {
          GtkDialogClass parent_class;
          ...
  };
  /* constructor and methods, yadda yadda yadda... */
  /* 
   * mup_frob.h
   */
  typedef struct _MupFrobInfo  MupFrobInfo;
  typedef enum   _MupFrobStyle MupFrobStyle;
  typedef enum   _MupFrobFlags MupFrobFlags;
  typedef struct _MupFrob      MupFrob;
  typedef struct _MupFrobClass MupFrobClass;
  struct _MupFrobInfo {
          MupFrobStyle   style;
          int            count;
          gchar        * name;
  };

  /* GBoxed support for frob info. */
  #define MUP_TYPE_FROB_INFO (mup_frob_info_get_type ())
  void mup_frob_info_get_type (void) G_GNUC_CONST;
  enum _MupFrobStyle {
          MUP_FROB_LEFT,
          MUP_FROB_RIGHT,
          MUP_FROB_UP,
          MUP_FROB_DOWN,
          MUP_FROB_IN,
          MUP_FROB_OUT,
          MUP_FROB_LOOSE
  };
  enum _MupFrobFlags {
          MUP_FROB_HOT    = (1<<0),
          MUP_FROB_WET    = (1<<1),
          MUP_FROB_STICKY = (1<<2),
          MUP_FROB_SWEET  = (1<<3)
  };
  /* type system support for the enums and flags.
   * this is usually taken care of by the glib-mkenums
   * tool and some special makefile rules and placed
   * in a separate file, but this is an example and
   * it's just here for illustration. */
  #define MUP_TYPE_FROB_STYLE (mup_frob_style_get_type ())
  #define MUP_TYPE_FROB_FLAGS (mup_frob_flags_get_type ())
  ...
  #define MUP_TYPE_FROB   (mup_frob_get_type ())
  /* ... the rest of the type macros */
  struct _MupFrob {
          GObject object;
          ...
  };
  struct _MupFrobClass {
          GObjectClass parent_class;
          ...
  };
  GObject *     mup_frob_new            (void);
  GObject *     mup_frob_new_with_style (MupFrobStyle   style);
  MupFrobInfo * mup_frob_get_info       (MupFrob      * frob);
  /* more methods... */

Indeed, this is a bit more involved. Amidst all the GLib boilerplate we have a GtkWidget, a GObject, a boxed type, and flag and enum types. (A little contrived, but i wanted to toss them all in.)

File Layout

The convention in gtk2-perl is to have a corresponding XS file for each public header in the wrapped library. This takes out the guesswork for developers and maintainers when figuring out where to look for the xsub for a given function. So we'll need to add two more XS files. (This has repercussions for getting boot code to run; see Booting the other XS modules.)

We'll also need a typemap to tell xsubpp how to generate code for the new types we're using.

And finally, we'll need a header in which to define preprocessor macros that perform type-checking and casting (``castmacros'') and typedefs that will allow us to use the types we need to wrap. By convention, the header takes the name of the package, all lower-case, with the suffix ``perl'', for example, ``gtk2perl.h'' for Gtk2. (Glib's header is gperl.h because the module was originally named ``G''.)

So now, our extension has these files:

 Mup
   Makefile.PL
   Mup.pm
   Mup.xs          stuff in libmup.h
   MupDirSel.xs    stuff in mupdirsel.h
   MupFrob.xs      stuff in mupfrob.h
   mupperl.h       XS definitions for our module
   typemap         typemaps for our types

Wrapping GObjects

Let's hit the heavy one first. As previously mentioned, GObjects and Perl objects have slightly different semantics on reference counting that require some crafty handling. To keep you from ever having to worry about it, gperl.h provides you with two indispensible functions:

  SV * gperl_new_object (GObject * object, gboolean own);
  GObject * gperl_get_object_check (SV * sv, GType gtype);

The commentary in gperl.h gives you the full story on these, so let me just give you the condensed version: gperl_new_object() wraps a GObject in an SV (with according type lookup and wrapper caching magic), and gperl_get_object_check() extracts the GObject from the Perl object, and croaks if the types are incompatible. The own parameter tells gperl_new_object() whether the perl object should claim ownership of the GObject. If you are returning a brand-new GObject from a constructor, this should be TRUE, so that the object gets destroyed at the proper time.

By convention, however, you will never directly call gperl_get_object_check() or gperl_new_object() in your xsubs! We'll create some castmacros for that, so that we can follow the convention of the perl API. We'll also add some ``variants'', which handle a couple of special cases.

So let's add this code to mupperl.h:

 /* mupperl.h */
 #include <gtk2perl.h>
 #include <libmup.h>
 typedef MupFrob MupFrob_ornull;
 typedef MupFrob MupFrob_noinc;

 #define SvMupFrob(sv)            ((MupFrob*) gperl_get_object_check ((sv), MUP_TYPE_FROB))
 #define SvMupFrob_ornull(sv)     (SvTRUE ((sv)) ? SvMupFrob ((sv)) : NULL)
 #define newSVMupFrob(obj)        (gperl_new_object ((obj), FALSE))
 #define newSVMupFrob_noinc(obj)  (gperl_new_object ((obj), TRUE))

These should be rather obvious; the naming convention parallels the Perl API, which uses things like SvIV / newSViv and SvPV_nolen / newSVpv. The SvMyType form gets the value out of an SV, and the newSVMyType form wraps an SV around a C value; the variants modify the way in which the macro works.

Now that we have those definitions in mupperl.h, we can put them to use in the typemap file. We need a typemap for each type that we typedef'd; the _noinc variant is used only for OUTPUT, and the _ornull variant is used only for INPUT.

 # typemap
 TYPEMAP
 MupFrob *           T_MUP_TYPE_FROB
 MupFrob_ornull *    T_MUP_TYPE_FROB_ORNULL
 MupFrob_noinc *     T_MUP_TYPE_FROB_NOINC
 INPUT
 T_MUP_TYPE_FROB
         $arg = SvMupFrob ($var);
 T_MUP_TYPE_FROB_ORNULL
         $arg = SvMupFrob_ornull ($var);
 OUTPUT
 T_MUP_TYPE_FROB
         $var = newSVMupFrob ($arg);
 T_MUP_TYPE_FROB_NOINC
         $var = newSVMupFrob_noinc ($arg);

If you seem to be thinking that those typemaps could be replaced with something that creates them for you, you're right; Glib provides a typemap called T_GPERL_GENERIC_WRAPPER, which creates an INPUT and OUTPUT section appropriate for the types following the SvMyType/newSVMyType convention, so that we can reduce this typemap file to nothing more than

 # typemap
 TYPEMAP
 MupFrob *           T_GPERL_GENERIC_WRAPPER
 MupFrob_ornull *    T_GPERL_GENERIC_WRAPPER
 MupFrob_noinc *     T_GPERL_GENERIC_WRAPPER

Next we can actually start writing the xsubs in MupFrob.xs.

  /* MupFrob.xs */
  /* this gets everything for us. */
  #include "mupperl.h"
  MODULE = Mup::Frob    PACKAGE = Mup::Frob     PREFIX = mup_frob_
  BOOT:
          /* this absolutely critical statement tells
           * the bindings about the new type.
           * it will also look through the ancestry of
           * MUP_TYPE_FROB and add the parent class to
           * @Mup::Frob::ISA.  more information is 
           * available in the Glib xs api reference. */
          gperl_register_object (MUP_TYPE_FROB, "Mup::Frob");
  # we're returning a new object, so do no increase the
  # object's reference count when wrapping it.  in other
  # words, the wrapper will claim ownership of the new
  # GObject.
  # call as $frob = Mup::Frob->new;
  GObject_noinc * mup_frob_new (SV * class)
      C_ARGS:
          /*void*/

As a subtle point, i used GObject_noinc on the output typemap, but i could just as easily have put MupFrob_noinc; the type machinery blesses the output object into the bottommost known class in the hierarchy regardless of the typemap. In practice, however, we usually use whatever type the C prototype used.

The use of C_ARGS to ignore the class parameter is a point of style; for functions which do not operate on objects, we use a class method syntax. This allows the functions to be inherited (otherwise you have to specify a hard-coded fully qualified name) and lets us avoid the need to export functions.

I don't know if you've noticed, but we now have a working xsub and functional wrapper for the GObject type, and we haven't actually touched the Perl API yet.

However, we can't wrap the rest of the methods until we have typemaps for their parameters.

Wrapping GEnum and GFlags types

These are really easy, as the GLib type system does all the work for us. We need typemap entries and castmacros, of course:

  /* add to mupperl.h */
  #define SvMupFrobStyle(sv)   (gperl_convert_enum (MUP_TYPE_FROB_STYLE, (sv))
  #define newSVMupFrobStyle(e)   (gperl_convert_back_enum (MUP_TYPE_FROB_STYLE, (e))
  #define SvMupFrobFlags(sv)   (gperl_convert_flags (MUP_TYPE_FROB_FLAGS, (sv))
  #define newSVMupFrobFlags(e)   (gperl_convert_back_flags (MUP_TYPE_FROB_FLAGS, (e))
 # add to typemap's TYPEMAP section
 MupFrobStyle   T_GPERL_GENERIC_WRAPPER
 MupFrobFlags   T_GPERL_GENERIC_WRAPPER

That's it. If we need to convert a flags or enum value by hand, we use the cast macros, and otherwise the typemap does all the work. Note that the typemap uses exactly the same code that we'd write by hand; this is no coincidence.

Wrapping Boxed Types

The last piece of the puzzle for this file is the structure MupFrobInfo. The C library provides us with a boxed type declaration for this strucure, so that we can associate a type id with it, and copy and free it without knowing anything about its internals. Opaque structures don't have reference counts, so our only lifetime concern is whether the boxed object should be destroyed along with its perl wrapper.

Again, gperl.h defines some useful functions:

  SV * gperl_new_boxed (gpointer boxed, GType gtype, gboolean own);
  SV * gperl_new_boxed_copy (gpointer boxed, GType gtype);
  gpointer gperl_get_boxed_check (SV * sv, GType gtype);

Again, we will use castmacros to make the API clean and unified:

  /* add to mupperl.h */
  /* GBoxed MupFrobInfo */
  typedef MupFrobInfo MupFrobInfo_ornull;
  #define SvMupFrobInfo(sv)     (gperl_get_boxed_check ((sv), MUP_TYPE_FROB_INFO))
  #define SvMupFrobInfo_ornull(sv)      (((sv) && SvTRUE (sv)) ? SvMupFrobInfo (sv) : NULL)
  typedef MupFrobInfo MupFrobInfo_own;
  typedef MupFrobInfo MupFrobInfo_copy;
  typedef MupFrobInfo MupFrobInfo_own_ornull;
  #define newSVMupFrobInfo(val) (gperl_new_boxed ((val), MUP_TYPE_FROB_INFO, FALSE))
  #define newSVMupFrobInfo_own(val)     (gperl_new_boxed ((val), MUP_TYPE_FROB_INFO, TRUE))
  #define newSVMupFrobInfo_copy(val)    (gperl_new_boxed_copy ((val), MUP_TYPE_FROB_INFO))
  #define newSVMupFrobInfo_own_ornull(val)      ((val) ? newSVMupFrobInfo_own(val) : &PL_sv_undef)

Apart from some new variants, these should look pretty familiar.

Next the typemaps:

 # add to typemaps TYPEMAP section
 MupFrobInfo *             T_GPERL_GENERIC_WRAPPER
 MupFrobInfo_ornull *      T_GPERL_GENERIC_WRAPPER
 MupFrobInfo_own *         T_GPERL_GENERIC_WRAPPER
 MupFrobInfo_own_ornull *  T_GPERL_GENERIC_WRAPPER
 MupFrobInfo_copy *        T_GPERL_GENERIC_WRAPPER

Note that we need a typemap for each type that we typedef'd, as well.

And now we can use those declarations:

  /* add to MupFrob.xs */
  MODULE = Mup::Frob    PACKAGE = Mup::Frob::Info       PREFIX = mup_frob_info_
  BOOT:
          /* another critical statement!  this tells
           * the bindings that we have a new boxed type;
           * the NULL tells the bindings to use the
           * default wrapper class.  more on that, later. */
          gperl_register_boxed (MUP_TYPE_FROB_INFO,
                                "Mup::Frob::Info",
                                NULL);
  # here's a function to provide read-only access to the
  # FrobInfo's members.  they are aliased into one to
  # save some code space.
  SV *
  members (MupFrobInfo * frobinfo)
      ALIAS:
          style = 0
          count = 1
          name  = 2
      CODE:
          switch (ix) {
              case 0: RETVAL = newSVMupFrobStyle (frobinfo->style); break;
              case 1: RETVAL = newSVivp (frobinfo->count); break;
              case 2: RETVAL = newSVGChar (frobinfo->name); break;
          }
      OUTPUT:
          RETVAL
  MODULE = Mup::Frob    PACKAGE = Mup::Frob     PREFIX = mup_frob_
  GObject_own * mup_frob_new_with_style (SV * class, MupFrobStyle style);
      C_ARGS:
          style
  # this function's API docs say that the caller owns
  # the returned value (or at least, they would -- it's
  # an example, remember? :-)
  MupFrobInfo_own * mup_frob_get_info (MupFrob * frob);

All the rest of the methods could be implemented in the same way.

Note that we do not write xsubs to bind the *_get_type() functions to perl; since Perl package names completely replace the concept of GTypes, these functions are not necessary and should be hidden from the Perl developer altogether.

Wrapping a GtkWidget

The remaining file, MupDirSel.xs, must wrap a GtkWidget. You've already seen the hard part, so this will seem very easy, but first let's review the difference between GObject and GtkObjects.

A GtkWidget is a GtkObject; a GtkObject is a GObject with special semantics for ownership. When you create a GObject, it has a reference count of one. When you create a GtkObject, its reference count is one, but a flag is set denoting that the object is ``floating'', meaning that nobody owns it. When you add a widget to a container, the container increases the widget's refcount and then ``sinks'' it. If the object was floating, this combination results in no change in the reference count, but the object is now owned; if the object was not floating, the sinking does nothing and the refcount increases.

The Gtk2 module installs gtk_object_sink() as the function to be called when gperl_new_object() attempts to take ownership of a GtkObject.

The upshot of all this is that it is safe always to pass TRUE for gperl_new_object()'s own parameter, and we never have to worry about a _noinc variant for a GtkObject.

This makes writing bindings for GtkObjects very easy.

 /* in mupperl.h */
 /* GtkObject MupDirSel */
 typedef MupDirSel MupDirSel_ornull;
 #define SvMupDirSel(sv)        ((MupDirSel*) gperl_get_object_check ((sv), MUP_TYPE_DIR_SEL))
 #define SvMupDirSel_ornull(sv) (SvTRUE ((sv)) ? SvMupDirSel ((sv)) : NULL)
 #define newSVMupDirSel(obj)    (gperl_new_object ((obj), TRUE)
 # in typemap's TYPEMAP section
 MupDirSel *        T_GPERL_GENERIC_WRAPPER
 MupDirSel_ornull * T_GPERL_GENERIC_WRAPPER

and now...

 /* MupDirSel.xs */
 #include "mupperl.h"
 MODULE = Mup::DirSel   PACKAGE = Mup::DirSel   PREFIX = mup_dir_sel_
 BOOT:
         gperl_register_object (MUP_TYPE_DIR_SEL, "Mup::DirSel");
 GtkWidget * mup_dir_sel_new (SV * class, const gchar * title)
     C_ARGS:
         title
 ...

Booting the other XS modules

Since the XS code for our extension is now spread across three XS files, we now have a problem --- only one of them is going to be bootstrapped! There are two solutions: we could put into Mup.pm an extra bootstrap line for each package that needs to be booted; or we could have the boot code of the Mup package in Mup.xs call the other boot code functions. In practice, we take the second option, because it can be automated and then hidden from the perl developer (i.e., it keeps all the complexity in XS).

So, we'd add this to Mup.xs:

  BOOT:
          /* we need to call all the boot code in all the
           * *other* XS files. */
          GPERL_CALL_BOOT(boot_Mup__Frob);
          GPERL_CALL_BOOT(boot_Mup__Frob__Info);
          GPERL_CALL_BOOT(boot_Mup__DirSel);

Where did i get the boot_Mup__Frob nonsense, you ask? Simple; look through each of the XS files for a BOOT: section, and note the PACKAGE directive on that section. Replace all the : with _, and then prepend boot_. (If you're thinking this sounds like something that should be automated, then you will want to read the Gtk2::CodeGen manpage.)

We must take care not to call the boot code for the current file, or the module will get stuck in an infinite recursive loop and eventually smash the C stack and crash perl.

But that's all there is to that.

setting up Makefile.PL

Since we have added some files to our extension, we'll have to modify Makefile.PL to take advantage of them.

To the line

 $depends->add_xs ('Mup.xs');

we must add the other XS files:

 $depends->add_xs (qw/Mup.xs MupDirSel.xs MupFrob.xs/);

After that line, let's tell depends that we're using a typemap. The pull path to the typemap is required; for portability, we'll use File::Spec. For ease in adding typemaps in the future, we'll use a map.

 use File::Spec;
 use Cwd;
 my $cwd = cwd();
 $depends->add_typemaps (map {File::Spec->catfile ($cwd, $_)}
                         qw/typemap/);

And finally, we must save all of this information so other modules can get to it (we are using Depends as client and provider):

 $depends->add_headers ('"mupperl.h"');
 $depends->install (qw/mupperl.h/);
 mkdir "build", 0777;
 $depends->save_config ("build/IFiles.pm");

The files typemap and mupperl.h will be installed along with IFiles.pm and the rest of the module in the perl library tree, for later use by other modules, if need be. IFiles.pm is used internally by ExtUtils::Depends to store the configuration data that client modules will need.

Other special code tricks

writing a custom GPerlBoxedWrapperClass -- suppose you have some data structure that you really wish perl didn't see as an opaque reference, but rather as a native hash or list. This is easy when you're writing the function, but how do you get $object->get (property) to do something different? Simply provide GPerl with functions to wrap and unwrap your boxed type via the GPerlBoxedWrapperClass when registering your boxed type with the bindings. The ``wrap'' function is supposed to ``wrap up'' a C pointer in a perl SV, and the ``unwrap'' function goes the other way. Whereas the default implementation (what you get when you pass NULL for the wrapper class) just creates opaque wrappers which know their types, you can do all sorts of crazy magic in these hooks; the source for Gtk2's handling of GdkEvents and Gnome2::Canvas' handling of GnomeCanvasPoints may serve as inspiration.

using gperl_set_isa for manual ascenstry management

using gperl_type_from_package when you need to pass a GType to a C function.


Binding a large library

You've already seen everything you need to know to get going on writing perl bindings for any library, but you may be daunted by how much work it will be. Larger libraries can have tens or hundreds of types and headers and the XS project can get really cumbersome. Let's look at how the Gtk2 module solves some of these problems.

[write more here]

But really it's the tricks of file layout and autogeneration that save the day.

File Layout

lots of XS files in a subdirectory to reduce clutter

Makefile.PL does

  $depends->add_xs (<xs/*.xs>);

makemaker has a bug that causes the -o directive to the compiler not to be generated properly. fix that with a postamble in makefile.pl.

for lots of PM files you may want to create a pm subdirectory as well.

Autogeneration

why do autogeneration? because you'll go nuts writing all those castmacros and typemaps!

writing a genmaps.pl script
just need something that creates a tab-delimited text file
containined descriptions of every type you want to bind.
has all the info that Gtk2::CodeGen needs to write the
stuff for you.
start with genmaps.pl as included with Gtk2, hack it up
for your library, save the output as maps.
you will only need to do this again if you add types to
the bound library.

using Gtk2::CodeGen in Makefile.PL using parse_maps to write register.xsh, castmacros and typemap using write_boot to handle boot code

in the BOOT section for the module that will be bootstrapped by your PM file, do this:

 BOOT:
 #include "build/register.xsh"
 #include "build/boot.xsh"

we do the registration first so that the various BOOT sections in your XS code can override the registration created by the autogenerated stuff. this is important if you're going to fiddle with @ISA or install a custom GBoxedWrapperClass.


MORE COOL CODE TRICKS

Temporary memory

gperl_alloc_temp

Callbacks

gperl_signal_connect

GPerlClosure where closures are wanted, which unfortunately is not very many places

GPerlCallback for callback functions

GErrors

gperl_croak_gerror converts a GError into a perl exception. be sure you clean up before calling this, because the caller may be trapping the exception with an eval, in which case the interpreter may keep going. if you don't clean up properly, you'll cause memory leaks.

GValues

gperl_value_from_sv and gperl_sv_from_value are the workhorses behind all the generic value stuff in the bindings. this is where your GPerlBoxedWrapperClass functions would be called.

Esoteric stuff

use gperl_try_convert_enum, gperl_convert_enum_pass_unknown, etc when you need finer control over flag and enum conversion.

supply a sink function for a special GObject derivative with gperl_register_sink_func

use gperl_set_no_warn_unreg_subclass to hush runtime warnings about unregistered private subclasses, e.g. for platform-specific implementation classes --- GtkStyle/BlueCurveStyle, GdkGC/GdkGCX11, etc

Dealing with GTypes

don't bind the _get_type() functions to perl

avoid GTypes at all costs

where they can't be avoided, use the most-specific package_to_type converter possible. the type-to-package mappings are hashed separately because they types are incompatible. in some places, though, you need a GType for *any* package, just as column types on a GtkTreeModel.


SEE ALSO

Perl's Online Reference Manual

   perlxstut    Perl XS tutorial
   perlxs       Perl XS application programming interface
   perlguts     Perl internal functions for those doing extensions
   perlapi      Perl API listing (autogenerated)

For the Glib and Gtk2 modules

   ExtUtils::Depends   Inter-extension dependency management
   ExtUtils::PkgConfig Simple, uniform interface to pkg-config
   Glib::devel         The foundation of the gtk2-perl language bindings
   Glib::xsapi         XS API reference for Glib
   Gtk2::devel         The internal workings of gtk2-perl, continued
   Gtk2::CodeGen       code generation utilities for Glib-based bindings.

I also highly recommend Extending and Embedding Perl, by Tim Jenness and Simon Cozens, (c) 2003 by Manning Publications Co. While the online manual is the definitive reference, this book goes a bit above and beyond, distilling into a four hundred page book what i took three years to learn on my own.


AUTHOR

muppet <scott at asofyet dot org>