You should set and retrieve attribute data on your GObject instances using the property system so that other programmers can get at data through a uniform interface, rather than going through a series of custom access functions.
Properties have names and descriptions, so they are self-documenting to a certain extent. In addition, exposing a GObject's data with properties allows you to employ an object design tool (such as Glade, see Chapter 5).
You'll encounter properties ad infinitum in GTK+ when you read Chapter 3, primarily when manipulating widget settings. [2]
You must define each property in a class as a GObject parameter . To get started, you should obtain a GParamSpec structure in your class initialization function (described in Section 2.2.3).
You'll need the following information to create a GParamSpec structure:
An identifier . A short string will do, such as inventory-id .
A nickname . The full name of the parameter, such as inventory number .
A description . A concise explanation, such as number on the inventory label .
Options , such as read-write access.
Type-specific information, such as
Minimum value
Maximum value
Default value
A secondary type if you're encapsulating parameters (for example, G_TYPE_BOXED , G_TYPE_ENUM , G_TYPE_FLAGS , G_TYPE_OBJECT , G_TYPE_PARAM ).
Sizes of arrays.
Because it is somewhat complicated, you don't create a GParamSpec structure by hand. Instead, use one of the g_param_spec_ functions in the following table to allocate the structure and set its fields.
Function | Type |
---|---|
g_param_spec_boolean() | gboolean |
g_param_spec_boxed() | GBoxed |
g_param_spec_char() | gchar |
g_param_spec_double() | gdouble |
g_param_spec_enum() | GEnumClass, GEnumValue |
g_param_spec_flags() | GFlagsClass |
g_param_spec_float() | gfloat |
g_param_spec_int() | gint |
g_param_spec_int64() | gint64 |
g_param_spec_long() | glong |
g_param_spec_object() | GObject |
g_param_spec_param() | GParamSpec |
g_param_spec_pointer() | gpointer |
g_param_spec_string() | gchar * |
g_param_spec_uchar() | guchar |
g_param_spec_uint() | guint |
g_param_spec_uint64() | guint64 |
g_param_spec_unichar() | gunichar |
g_param_spec_value_array() | Array of some other type |
Typically, you place the call to a g_param_spec_ function in your class initializer function. You may recall that the class initializer for the Media example is media_class_init() . Therefore, using parameters for inventory-id and orig-package , media_class_init() would look something like this:
static void media_class_init(MediaClass *class) { GParamSpec *inv_nr_param; GParamSpec *orig_package_param; << ... >> /* create GParamSpec descriptions for properties */ inv_nr_param = g_param_spec_uint("inventory-id", /* identifier */ "inventory number", /* nickname */ "number on inventory label", /* description */ 0, /* minimum */ UINT_MAX, /* maximum */ 0, /* default */ G_PARAM_READWRITE); /* flags */ orig_package_param = g_param_spec_boolean("orig-package", "original package?", "is item in its original package?", FALSE, G_PARAM_READWRITE); << ... >> }
Although the actual inv_nr and orig_package fields from the Media instance structure aren't in this function, you will need to come back to them when you actually install the property in the class. The GParamSpec structure serves to describe the property: its purpose, type, and permissible values.
The last parameter to a g_param_spec_ function is a bit mask that you can specify with a bitwise OR of any of the following:
G_PARAM_CONSTRUCT indicates that GObject will assign a value to the property upon object instantiation. At the moment, this works only in conjunction with G_PARAM_CONSTRUCT .
G_PARAM_CONSTRUCT_ONLY indicates that the property may take on a value only when an object is instantiated .
G_PARAM_LAX_VALIDATION disables type checks when you write to this property. Set this only if you know exactly what you're doing.
G_PARAM_READABLE allows read access to the property.
G_PARAM_WRITABLE allows write access to the property.
G_PARAM_READWRITE is shorthand for G_PARAM_READABLEG_PARAM_WRITABLE .
Note | As this chapter progresses, the media_class_init() function code in this section will grow. |
Before you get to Section 2.4.3, where you see how to activate a property in the class initializer, you need to familiarize yourself with how GObject moves the property values from place to place.
Untyped gpointer -style pointers normally take on the task of setting and retrieving function parameters of an arbitrary type. However, you need to store and check the type information at run time so that you don't try to do something disastrous, like attempting to copy a string into a memory location that corresponds to a random integer.
GObject has a mechanism for holding a value along with its type into a single "container," so that you can pass the container along as a parameter and pull its value and type out when necessary. This system is called GValue, with function names that start with g_value_ .
The actual container is the GValue data structure. If you need to create one, you can do it with GLib's elementary memory management utilities; there aren't any special functions just for GValues .
Warning | That said, you must use g_malloc0() , g_new0() , or some other similar allocation function that sets all of the new memory bytes to zero. You'll get an error when you try to initialize a GValue structure with random bytes. |
After creating a GValue structure gvalue , initialize it with
g_value_init( gvalue, type )
where type is an identifier such as G_TYPE_INT (see page 78 for a full list). For each type identifier, the following are available:
A verification macro, G_VALUE_HOLDS_ TYPE ( gvalue ) , that returns TRUE when gvalue contains the type.
A writer function, g_value_set_ type ( gvalue , value ) , to place value into the gvalue container.
A reader function, g_value_get_ type ( gvalue ) , to fetch the value from gvalue .
Note | If you can't decide on how often to verify the type inside a container, err on doing it too often instead of one time too few when you go to read or write a value. |
The standard types are listed below. Here is a small GValue demonstration:
/* gvaluedemo.c -- demonstrate GValue */ #include <glib-object.h> #include <stdio.h> int main(int argc, char **argv) { GValue *value; g_type_init(); /* initialize type system */ /* allocate new GValue, zero out contents */ value = g_new0(GValue, 1); /* initialize GValue type to gint */ g_value_init(value, G_TYPE_INT); /* set the value to 42 and read it out again */ g_value_set_int(value, 42); g_print("Value: %d\n", g_value_get_int(value)); /* is the type of the GValue a GObject? */ if (G_VALUE_HOLDS_OBJECT(value)) { g_print("Container holds a GObject\n"); } else { g_print("Container does not hold a GObject\n"); } /* expect "Container does not hold a GObject" */ g_free(value); return 0; }
There are two special access functions for GValue structures of G_TYPE_STRING : g_value_set_static_string( gvalue , str ) transfers a string pointer str into a gvalue , and g_value_dup_string( gvalue ) returns a copy of a string in gvalue (you will need to deallocate that copy when you're done with it, though).
Otherwise, the names are uniform. For each of the types in the following list, there is a verification macro and a reader and writer access function, as described earlier. For example, G_TYPE_CHAR comes with G_VALUE_HOLDS_CHAR() , g_value_get_char() , and g_value_set_char() .
G_TYPE_BOOLEAN | G_TYPE_FLOAT | G_TYPE_POINTER |
G_TYPE_BOXED | G_TYPE_INT | G_TYPE_STRING |
G_TYPE_CHAR | G_TYPE_INT64 | G_TYPE_UCHAR |
G_TYPE_DOUBLE | G_TYPE_LONG | G_TYPE_UINT |
G_TYPE_ENUM | G_TYPE_OBJECT | G_TYPE_UINT64 |
G_TYPE_FLAGS | G_TYPE_PARAM | G_TYPE_ULONG |
It might be prudent to note that GObject also defines a gchararray type that stands for gchar * . The advantage of using gchararray over a simple gchar pointer is in name only; when you have a gchararray array, you can be certain that it's a string and not some other type that you've cast to gchar * .
To reset a GValue to its original state ” that is, the zeroed memory that you had just before you ran g_value_init() ” use g_value_unset(gvalue) . This frees up any extra memory that gvalue is currently using and sets its bytes in memory to zero. At that point, it's ready to be used again.
Now you're ready to install some properties. Recall that in Section 2.4.1 you came up with the GParamSpec structures for the Media class properties in the media_class_init() function. The property installation continues in that function, with a call to
g_object_class_install_property( class , id , param )
where class is the class structure, param is the property's GParamSpec structure, and id is a unique identifier obtained with an enumeration type. This identifier should begin with PROP_ .
Warning | The property identifier must be greater than zero, so you will need to place a dummy value like PROP_MEDIA_0 at the head of your enumeration type. |
The code for media_class_init() should make things clear:
enum { PROP_MEDIA_0, PROP_INV_NR, PROP_ORIG_PACKAGE }; static void media_class_init(MediaClass *class) { GParamSpec *inv_nr_param; GParamSpec *orig_package_param; GObjectClass *g_object_class; /* get handle to base object */ g_object_class = G_OBJECT_CLASS(class); << param structure setup from Section 2.4.1 >> /* override base object methods */ g_object_class->set_property = media_set_property; g_object_class->get_property = media_get_property;
Warning | Notice that you must set the set_property and get_property methods before installing the class properties. |
<< ... >> /* install properties */ g_object_class_install_property(g_object_class, PROP_INV_NR, inv_nr_param); g_object_class_install_property(g_object_class, PROP_ORIG_PACKAGE, orig_package_param); << ... >> }
Note | The code in Section 2.4.1 and here accesses GParamSpec structures with the temporary variables inv_nr_param and orig_package_param before installation with g_object_class_install_property() . Programmers normally omit these temporary variables, using the entire call to g_param_spec_boolean() as a parameter when they install the property. |
To use the property system in the new class, media_class_init() must cast the new Media class structure ( class ) pointer to a GObject class structure named g_object_class . When you get a handle from a cast like this, you can override the set_property and get_property methods in the GObject base class. This is exactly what you must do to install the new properties such as inventory-id and orig-package . Remember that the base object knows nothing about the properties and instance structures of its subclasses.
The example overrides the base class methods with media_set_property() and media_get_property() , and therefore, you must supply prototypes before media_class_init() . These are straightforward, and because they are replacements for methods in the base class structure, they must conform to the prototypes in the base class structure:
static void media_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void media_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
Note | You can avoid prototypes for static functions by placing the actual functions (described below) before media_class_init() . |
Now that you've set up all of this infrastructure to handle the properties, you can write the functions that actually deal with the inv_nr and orig_package fields in the instance structure. The implementations consist of some busywork; to set a field, media_set_property() does the following:
Determines the property to set.
Removes the new property value from the GValue container from its parameter list.
Sets the field in the instance structure ( finally! ).
Here is the actual code:
static void media_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { Media *media; guint new_inv_nr; gboolean new_orig_package; media = MEDIA(object); switch(prop_id) { case PROP_INV_NR: new_inv_nr = g_value_get_uint(value); if (media->inv_nr != new_inv_nr) { media->inv_nr = new_inv_nr; } break; case PROP_ORIG_PACKAGE: new_orig_package = g_value_get_boolean(value); if (media->orig_package != new_orig_package) { media->orig_package = new_orig_package; } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } }
The media_get_property() code is similar, except that it needs to put one of the instance structure fields into the container rather than the other way around. No notification is necessary. (It would be very nosy of your program to tell the object every time someone looked at a property, but you can do it if you really want to.)
static void media_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { Media *media; media = MEDIA(object); switch(prop_id) { case PROP_INV_NR: g_value_set_uint(value, media->inv_nr); break; case PROP_ORIG_PACKAGE: g_value_set_boolean(value, media->orig_package); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } }
Take a close look at the default case in each of these functions. The default comes into play only when the function encounters an invalid property, and it runs G_OBJECT_WARN_INVALID_PROPERTY_ID() on the object, invalid property, and parameter structure. The resulting warning message will hopefully be strong enough to get you to check your property access function calls.
You may be wondering why you need such a complicated system just to set a bunch of fields in your instance structure. After all, you could write access methods to do this, or you could even just tell the programmer to set the fields. However, you want a uniform system such as properties for the following reasons:
You need a dynamic system. Subclasses can add their own properties with little effort, as you will see in Section 2.7.
You want to define behavior for when properties change. This capability is extremely important in GUI programming, where you want the program to react to changes in buttons , check boxes, and other elements. If you haphazardly set instance structure fields instead of using properties, you would need to define a "reaction" function and make sure that any code that sets a field also calls that function. Even with access methods, this can get out of hand very quickly, especially if your "reaction" function needs to set other fields.
You want a system that's easy to document. It's easy to list property names with their possible values and descriptions. Access method APIs are considerably harder to describe, especially if the methods aren't uniform.
In practice, you might see access methods for a class that correspond to properties in the class, especially with older code. Functionally, there is no difference; it's just one more layer of indirection.
Let's say that you have this to set the Media orig-package property:
void media_set_orig_package(Media *object, gboolean new_value) { Media *media; g_return_if_fail(IS_MEDIA(object)); media = MEDIA(object); if (media->orig_package != new_value) { media->orig_package = new_value; g_object_notify(G_OBJECT(media), "orig-package"); } }
This does all of the work that you see under case PROP_ORIG_PACKAGE: in media_set_property() from page 81. Therefore, you can rewrite that part:
case PROP_ORIG_PACKAGE: new_orig_package = g_value_set_boolean(value); media_set_orig_package(media, new_orig_package); break;
This is primarily a matter of convention and a function of the age of the code. When reading API documentation, you may see an object with more properties than access function pairs, indicating that someone may have added more properties to some older code without bothering to use access functions (developers don't usually remove access functions for fear of breaking third-party applications). In that case, class _set_ property () contains a mix of access function calls and field assignments/notifications.
Direct access function calls are slightly faster because they do not have to look up the property identifier and encapsulate any values. However, this speedup usually doesn't matter.
[2] Unfortunately, some GTK+ classes do not have property interfaces; hopefully, that's not a permanent situation.