GProperty -- a proposal on top of GParamSpec (with code)



Hello,

I've written out so many gobject property handlers in the last few
years, and they are always looking similar, so the desire to prune some
boilerplate code has been growing. The idea behind GProperty (actually
G<Foo>Property) is to subclass a GParamSpec<Foo> and add data and code
so the property can update the object it applies to itself. For e.g. a
string property this would of course include freeing the previous value.

What does GProperty offer over GParamSpec?
* Remove the need for implementing GObject::get_property()
and ::set_property() on your object (see "Boxed property access").
* Remove the need for an enum of property-ids.
* Remove the need for explicit property accessors with just a small
amount of extra boilerplate code (see "Unboxed property access").
* By subclassing GParamSpec, full compatibility is retained.

My prototype [1] is currently using the Egg namespace, implementing
EggBooleanProperty, EggDoubleProperty, EggIntProperty and
EggStringProperty with examples that demonstrate usage. The following
code snippets will use EggStringProperty for being easier to read than
"<Foo>" all over the place.

If this approach is adjudged positively I would like to import it into
git.gnome.org's libegg module for hashing out the details and finishing
up the implementation. Feedback would be appreciated.


Boxed property access
=====================

Let's look at how the API is going to be used first:

    typedef struct {
      gchar *string_foo;
    } TestOffsetPrivate;

    static void
    test_offset_class_init (TestOffsetClass *klass)
    {
      GObjectClass  *object_class = G_OBJECT_CLASS (klass);
      gpointer       pspec;
      guint          id = 0;

      g_type_class_add_private (klass, sizeof (TestOffsetPrivate));

      object_class->get_property = egg_object_get_property_impl;
      object_class->set_property = egg_object_set_property_impl;

      pspec = egg_string_property_for_field (
                                  "string-foo",
                                   G_STRUCT_OFFSET (TestOffsetPrivate,
                                                    string_foo),
                                   NULL,
                                   G_PARAM_READWRITE);
      g_object_class_install_property (object_class, ++id, pspec);
    }

* EggStringProperty is a subclass of GParamSpecString and implements
EggProperty:

    typedef struct {
      GTypeInterface parent;
      void (*set_value) (EggProperty *p, GObject *o, const GValue *v);
      void (*get_value) (EggProperty *p, GObject *o, GValue *v);
    } EggPropertyInterface;

* The generic GObject::get_property() and ::set_property()
implementations egg_object_{get,set}_property_impl() are dispatching
access through g_object_{get,set}() to the EggProperty interface. This
makes per-class overrides and property-id enums obsolete.

* The egg_string_property_for_field() constructor takes the offset of
the "string_foo" member in the private data blob so it knows where to
look for the actual value in the implementation of the EggProperty
interface methods. (Alternative property constructors are discussed
further below.)


Unboxed property access
=======================

The code above covers boxed property access through the
g_object_{get,set}() API. In addition, explicit property accessors are
very common for their compile-time traits, as well as avoiding the
boxing overhead. To that end, every property-class also implements
unboxed accessors:

    typedef struct {
      GParamSpecClass parent_class;
      const gchar * (*get) (EggStringProperty *p, GObject *o);
      void (*set) (EggStringProperty *p, GObject *o, const gchar *v);
    } EggStringPropertyClass;

Together with unboxed accessors on GObject level we can retain a certain
level of type safety (exactly how should become clearer just a bit
further down):

    const gchar * egg_object_get_string (gpointer            object,
                                         EggStringProperty  *property);

    void          egg_object_set_string (gpointer            object,
                                         EggStringProperty  *property,
                                         const gchar        *val);


Putting it all together
=======================

For a complete example how to use the GProperty API please look at [2]
(files matching *unboxed-offset*).

Again, code snippet first, for a GObject called "TestUnboxedOffset":

  /* test-unboxed-offset.h */

    #define TEST_UNBOXED_OFFSET_AND_PROPERTY(obj, prop) \
      (TEST_UNBOXED_OFFSET (obj)),                      \
      (TEST_UNBOXED_OFFSET_GET_CLASS (obj)->prop)

    typedef struct {
      GObjectClass parent;
      EggStringProperty *string_foo;
    } TestUnboxedOffsetClass;

  /* test-unboxed-offset.c */

    static void
    test_unboxed_offset_class_init (TestUnboxedOffsetClass *klass)
    {
    ...
      klass->string_foo = egg_string_property_for_field ("string-foo",
    ...
      g_object_class_install_property (object_class,
                                       ++id,
                                       klass->string_foo);

  /* test-unboxed-offset.c */

    egg_object_set_string (
                TEST_UNBOXED_OFFSET_AND_PROPERTY (object, string_foo),
                "foo");

In addition to g_object_class_install_property(), I would recommend to
put a pointer to the property into the class structure. This makes sure
the property is immediately at hand with an object instance. A (slightly
quirky) new boilerplate macro like TEST_UNBOXED_OFFSET_AND_PROPERTY that
produces two values (object and property) should help keeping the RSI
under control. Accessing a property like this checks property name and
type at compile time, so it's pretty much as good as an explicit setter
test_unboxed_offset_set_string_foo() would be.

Notes
=====

== Overriding properties

I have not investigated this in depth yet, but it should be possible to
override a property by also overriding the property pointer in the
parent's class, in addition to g_object_class_override_property().

== Alternative property constructors

At this point there are three flavours of properties in place:

* Properties operating on a field in the object's private data blob --
see the *unboxed-offset* and *offset* examples.

* Properties proxying a property of an aggregate object -- see the
*proxy* examples.

* Properties based on custom accessors, getter and setter function have
to be passed to the property constructor -- see the *accessors*
examples.

== Code snippets in this mail

A few instances of "const" qualifiers have been dropped and variable
names have been changed for more compact formatting.


[1] http://gitorious.org/egg-gobject
[2] http://gitorious.org/egg-gobject/egg-gobject/trees/master/examples/

-- 
Intel Open Source Technology Centre
http://oss.intel.com/



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