GTK+ / Gnome Application Development | |||
---|---|---|---|
<<< Previous | Home | Next >>> |
glib is a C portability and utility library for UNIX-like systems and Windows. This chapter covers some of the most commonly-used library features in GTK+ and Gnome applications. glib is simple, and the concepts are familiar; so we'll move quickly. For more complete coverage of glib, see glib.h or the free glib reference manual that comes with the library. (By the way: don't be afraid of using the glib, GTK+, or Gnome header files; they are very clean and easy to read, and are handy as a quick reference. For that matter, don't be afraid to look at the source code, if you have very specific questions about the implementation.)
glib's various facilities are intended to have a consistent interface; the coding style is semi-object-oriented, and identifiers are prefixed with "g" to create a kind of namespace.
glib has a single header file, glib.h.
glib provides substitutes for many standard and commonly-used C language constructs. This section describes glib's fundamental type definitions, macros, memory allocation routines, and string utility functions.
Rather than using C's standard types (int, long, etc.) glib defines its own. These serve a variety of purposes. For example, gint32 is guaranteed to be 32 bits wide, something no standard C type can ensure. guint is simply easier to type than unsigned. A few of the typedefs exist only for consistency; for example, gchar is always equivalent to the standard char.
The following primitive types are defined by glib:
gint8, guint8, gint16, guint16, gint32, guint32, gint64, guint64---these give you integers of a guaranteed size. Not all platforms provide 64-bit integers; if a platform has them, glib will define G_HAVE_GINT64. (If it isn't obvious, the guint types are unsigned, the gint types are signed.)
gboolean is useful to make your code more readable, since C has no bool type.
gchar, gshort, glong, gint, gfloat, gdouble are purely cosmetic.
gpointer may be more convenient to type than void *. gconstpointer gives you const void*. (const gpointer will not do what you typically mean it to; spend some time with a good book on C if you don't see why.)
glib defines a number of familiar macros used in many C programs, shown in Figure 1. All of these should be self-explanatory. MIN()/MAX() return the smaller or larger of their arguments. ABS() returns the absolute value of its argument. CLAMP(x, low, high) means x, unless x is outside the range [low, high]; if x is below the range, low is returned; if x is above the range, high is returned. In addition to the macros shown in Figure 1, TRUE/FALSE/NULL are defined as the usual 1/0/((void*)0).
There are also many macros unique to glib, such as the portable gpointer-to-gint and gpointer-to-guint conversions shown in Figure 2.
Most of glib's data structures are designed to store a gpointer. If you want to store pointers to dynamically allocated objects, this is the right thing. However, sometimes you want to store a simple list of integers without having to dynamically allocate them. Though the C standard does not strictly guarantee it, it is possible to store a gint or guint in a gpointer variable on the wide range of platforms glib has been ported to; in some cases, an intermediate cast is required. The macros in Figure 2 abstract the presence of the cast.
Here's an example:
gint my_int; gpointer my_pointer; my_int = 5; my_pointer = GINT_TO_POINTER(my_int); printf("We are storing %d\n", GPOINTER_TO_INT(my_pointer)); |
Be careful, though; these macros allow you to store an integer in a pointer, but storing a pointer in an integer will not work. To do that portably, you must store the pointer in a long. (It's undoubtedly a bad idea to do so, however.)
glib has a nice set of macros you can use to enforce invariants and preconditions in your code. GTK+ uses these liberally---one of the reasons it's so stable and easy to use. They all disappear when you define G_DISABLE_CHECKS or G_DISABLE_ASSERT, so there's no performance penalty in production code. Using these liberally is a very, very good idea. You'll find bugs much faster if you do. You can even add assertions and checks whenever you find a bug to be sure the bug doesn't reappear in future versions---this complements a regression suite. Checks are especially useful when the code you're writing will be used as a black box by other programmers; users will immediately know when and how they've misused your code.
Of course you should be very careful to ensure your code isn't subtly dependent on debug-only statements to function correctly. Statements that will disappear in production code should never have side effects.
Figure 3 shows glib's precondition checks. g_return_if_fail() prints a warning and immediately returns from the current function if condition is FALSE. g_return_val_if_fail() is similar but allows you to return some retval. These macros are incredibly useful---if you use them liberally, especially in combination with GTK+'s runtime type checking, you'll halve the time you spend looking for bad pointers and type errors.
Using these functions is simple; here's an example from the glib hash table implementation:
void g_hash_table_foreach (GHashTable *hash_table, GHFunc func, gpointer user_data) { GHashNode *node; gint i; g_return_if_fail (hash_table != NULL); g_return_if_fail (func != NULL); for (i = 0; i < hash_table->size; i++) for (node = hash_table->nodes[i]; node; node = node->next) (* func) (node->key, node->value, user_data); } |
Without the checks, passing NULL as a parameter to this function would result in a mysterious segmentation fault. The person using the library would have to figure out where the error occurred with a debugger, and maybe even dig in to the glib code to see what was wrong. With the checks, they'll get a nice error message telling them that NULL arguments are not allowed.
glib also has more traditional assertion macros, shown in Figure 4. g_assert() is basically identical to assert(), but responds to G_DISABLE_ASSERT and behaves consistently across all platforms. g_assert_not_reached() is also provided; this is an assertion which always fails. Assertions call abort() to exit the program and (if your environment supports it) dump a core file for debugging purposes.
Fatal assertions should be used to check internal consistency of a function or library, while g_return_if_fail() is intended to ensure sane values are passed to the public interfaces of a program module. That is, if an assertion fails, you typically look for a bug in the module containing the assertion; if a g_return_if_fail() check fails, you typically look for the bug in the code which invokes the module.
This code from glib's calendrical calculations module shows the difference:
GDate* g_date_new_dmy (GDateDay day, GDateMonth m, GDateYear y) { GDate *d; g_return_val_if_fail (g_date_valid_dmy (day, m, y), NULL); d = g_new (GDate, 1); d->julian = FALSE; d->dmy = TRUE; d->month = m; d->day = day; d->year = y; g_assert (g_date_valid (d)); return d; } |
The precondition check at the beginning ensures the user passes in reasonable values for the day, month and year; the assertion at the end ensures that glib constructed a sane object, given sane values.
g_assert_not_reached() should be used to mark "impossible" situations; a common use is to detect switch statements that don't handle all possible values of an enumeration:
switch (val) { case FOO_ONE: break; case FOO_TWO: break; default: /* Invalid enumeration value */ g_assert_not_reached(); break; } |
All of the debugging macros print a warning using glib's g_log() facility, which means the warning includes the name of the originating application or library, and you can optionally install a replacement warning-printing routine. For example, you might send all warnings to a dialog box or log file instead of printing them on the console.
glib wraps the standard malloc() and free() with its own g_ variants, g_malloc() and g_free(), shown in Figure 5. These are nice in several small ways:
g_malloc() always returns a gpointer, never a char*, so there's no need to cast the return value.
g_malloc() aborts the program if the underlying malloc() fails, so you don't have to check for a NULL return value.
g_malloc() gracefully handles a size of 0, by returning NULL.
g_free() will ignore any NULL pointers you pass to it.
In addition to these minor conveniences, g_malloc() and g_free() can support various kinds of memory debugging and profiling. If you pass the --enable-mem-check option to glib's configure script, the compiled g_free() will warn you whenever you free the same pointer twice. The --enable-mem-profile option enables code which keeps memory use statistics; when you call g_mem_profile() they are printed to the console. Finally, you can define USE_DMALLOC and the glib memory wrappers will use the MALLOC(), etc. debugging macros available in dmalloc.h on some platforms.
#include <glib.h> |
gpointer g_malloc
(gulong size);
void g_free
(gpointer mem);
gpointer g_realloc
(gpointer mem, gulong size);
gpointer g_memdup
(gconstpointer mem, guint bytesize);
Figure 5. glib memory allocation
It's important to match g_malloc() with g_free(), plain malloc() with free(), and (if you're using C++) new with delete. Otherwise bad things can happen, since these allocators may use different memory pools (and new/delete call constructors and destructors).
Of course there's a g_realloc() equivalent to realloc(). There's also a convenient g_malloc0() which fills allocated memory with 0s, and g_memdup() which returns a copy of bytesize bytes starting at mem. g_realloc() and g_malloc0() will both accept a size of 0, for consistency with g_malloc(). However, g_memdup() will not.
If it isn't obvious: g_malloc0() fills raw memory with unset bits, not the value 0 for whatever type you intend to put there. Occasionally someone expects to get an array of floating point numbers initialized to 0.0; this will not work.
Finally, there are type-aware allocation macros, shown in Figure 6. The type argument to each of these is the name of a type, and the count argument is the number of type-size blocks to allocate. These macros save you some typing and multiplication, and are thus less error-prone. They automatically cast to the target pointer type, so attempting to assign the allocated memory to the wrong kind of pointer should trigger a compiler warning. (If you have warnings turned on, as a responsible programmer should!)
glib provides a number of functions for string handling; some are unique to glib, and some solve portability concerns. They all interoperate nicely with the glib memory allocation routines.
For those interested in a better string than gchar*, there's also a GString type. It isn't covered in this book, but documentation is available at http://www.gtk.org/.
#include <glib.h> |
gint g_snprintf
(gchar* buf, gulong n, const gchar* format, ...);
gint g_strcasecmp
(const gchar*
s1, const gchar*
s2);
gint g_strncasecmp
(const gchar*
s1, const gchar*
s2, guint n);
Figure 7. Portability Wrappers
Figure 7 shows some substitutes glib provides for commonly-implemented but unportable extensions to ANSI C.
One of the annoying things about C is that it provides the crash-causing, security-hole-creating, generally evil sprintf(), but the relatively safe and widely implemented snprintf() is a vendor extension. g_snprintf() wraps native snprintf() on platforms that have it, and provides an implementation on those that don't. So you can say goodbye to sprintf() forever. Even better: classically, snprintf() does not guarantee that it will NULL-terminate the buffer it fills. g_snprintf() does.
g_strcasecmp() and g_strncasecmp() perform a case-insensitive comparison of two strings, optionally with a maximum length. strcasecmp() is available on many platforms but not universally, so using glib instead is advisable.
The functions in Figure 8 modify a string in-place: the first two convert the string to lowercase or uppercase, respectively, while g_strreverse() reverses its characters. g_strchug() and g_strchomp() "chug" the string (remove leading spaces), or "chomp" it (remove trailing spaces). These last two return the string, in addition to modifying it in-place; in some cases it may be convenient to use the return value. There is a macro, g_strstrip(), which combines both functions to remove both leading and trailing spaces; it is used just as the individual functions are.
#include <glib.h> |
void g_strdown
(gchar* string);
void g_strup
(gchar* string);
void g_strreverse
(gchar* string);
gchar* g_strchug
(gchar* string);
gchar* g_strchomp
(gchar* string);
Figure 8. In-place string modifications
Figure 9 shows a few more semi-standard functions glib wraps. g_strtod is like strtod()---it converts string nptr to a double---with the exception that it will also attempt to convert the double in the "C" locale if it fails to convert it in the user's default locale. *endptr is set to the first unconverted character, i.e. any text after the number representation. If conversion fails, *endptr is set to nptr. endptr may be NULL, causing it to be ignored.
g_strerror() and g_strsignal() are like their non-g_ equivalents, but portable. (They return a string representation for an errno or a signal number.)
#include <glib.h> |
gdouble g_strtod
(const gchar* nptr, gchar** endptr);
gchar* g_strerror
(gint errnum);
gchar* g_strsignal
(gint signum);
Figure 9. String Conversions
Figure 10 shows glib's rich array of functions for allocating strings. Unsurprisingly, g_strdup() and g_strndup() produce an allocated copy of str or the first n characters of str. For consistency with the glib memory allocation functions, they return NULL if passed a NULL pointer. The printf() variants return a formatted string. g_strescape escapes any \ characters in its argument by inserting another \ before them, returning the escaped string. g_strnfill()returns a string of size length filled with fill_char.
g_strdup_printf() deserves a special mention; it is a simpler way to handle this common piece of code:
gchar* str = g_malloc(256); g_snprintf(str, 256, "%d printf-style %s", 1, "format"); |
Instead you could say this, and avoid having to figure out the proper length of the buffer to boot:
gchar* str = g_strdup_printf("%d printf-style %s", 1, "format"); |
#include <glib.h> |
gchar* g_strdup
(const gchar* str);
gchar* g_strndup
(const gchar* format, guint n);
gchar* g_strdup_printf
(const gchar*
format, ...);
gchar* g_strdup_vprintf
(const
gchar* format,
va_list
args);
gchar* g_strescape
(gchar* string);
gchar* g_strnfill
(guint length, gchar fill_char);
Figure 10. Allocating Strings
glib provides some convenient functions for concatenating strings, shown in Figure 11. g_strconcat() returns a newly-allocated string created by concatenating each of the strings in the argument list. The last argument must be NULL, so g_strconcat() knows when to stop. g_strjoin() is similar, but separator is inserted between each string. If separator is NULL, no separator is used.
#include <glib.h> |
gchar* g_strconcat
(const gchar* string1, ...);
gchar* g_strjoin
(const gchar* separator, ...);
Figure 11. Concatenating strings
Finally, Figure 12 summarizes a few routines which manipulate NULL-terminated arrays of strings. g_strsplit() breaks string at each delimiter, returning a newly-allocated array. g_strjoinv() concatenates each string in the array with an optional separator, returning an allocated string. g_strfreev() frees each string in the array and then the array itself.