OOP programming in C?
In this article, we're going to take a look at the Glib object system, also called "GObject", which up until recently used to be part of GTK+. But before taking a look at the new object system in Glib 2.0, we need to address a more fundamental issue -- what exactly is an "object system" and why does it exist? After all, isn't C a non-object oriented language? It is possible to write object-oriented programs in C, or must one use C++ instead?
The answer is that it is possible to write object-oriented programs in C. However, because the concept of object isn't a part of the C language specification, an external library is required to provide this support. In this article, we use the term "object system" to describe a library that provides the foundation required for OOP programming, and Glib is an example of such a library. Glib provides a C implementation of classes, inheritance, reference counting, signals, interfaces, and object properties. By using Glib, C programmers can write object-oriented programs with ease.
So, it is possible to write object-oriented programs in C. However, you may be wondering why the GTK+ developers didn't just go ahead and use C++ instead. Without covering every possible explanation, here are a few reasons why it makes sense to have an object system for C. For one, there are many developers who simply prefer C to C++. In other situations, using a C++ compiler may not be an option due to project or platform constraints. Whatever the reason, having an object system for C opens up OOP programming (and GNOME programming, in particular) to a wider number of potential developers, and for that we're grateful.
That being said, all those C++ fanatics out there shouldn't be worried -- you can write GNOME programs in C++ too. Since C++ is a superset of the C language, you can easily combine C-style Glib/GTK+ code with your existing C++ projects. Alternatively, you can use a Glib/GTK+ C++ wrapper instead. A Glib/GTK+ C++ wrapper will allow you to interact with Glib objects using native C++ classes and objects.
Looking at GObject
Okay, so now you know what an object system is and what it's used for. Now, we'll take a look inside the Glib object system and explore some foundational GObject programming concepts. Up until recently, the GObject system was implemented in the GTK+ 1.2.x library. Now, with the imminent release of GTK+ 2, the object system has been moved down one layer to the new Glib 2 library. Thanks to this move, the Glib object system is now completely independent of GUI-related trappings. This is great news for those of us who would like to use classes and objects in non GTK+-based programs such as console-based applications and tools.
Now, let's see what the Glib object system (also called "GObject") looks like in comparison to the object system of C++ and the Java language. First, let's do a syntax comparison. In C++, you'd invoke a method via an object pointer as follows:
Using a C++ object reference, you'd type:
object.function (arg_a, arg_b);
The Java language only has references, not pointers. The Java method call syntax is identical to a C++ referenced object method call:
object.function (arg_a, arg_b);
In contrast, GObject uses standard C function call syntax. The object pointer is passed as the first argument to the function. Also note that the function name is prefixed with the class name in order to prevent namespace collisions:
classname_function (object, arg_a, arg_b);
You may be wondering exactly how calling a GObject method differs from calling a simple C function. Well, from the perspective of the C programming language itself, there is no difference. As far as your C compiler is concerned, it is simply calling a function, and the first argument of that function happens to be a pointer to a standard C structure.
So your C compiler doesn't even know that we are writing an object-oriented program. However, don't let this fact fool you into thinking that GObject OOP programming is just a bunch of hokey technojargon for good old-fashioned C programming. GObject does do a lot of stuff behind the scenes, allowing you to create subclasses of existing classes, interfaces to classes (which we will discuss later in this article) and more. Yet all this OOP functionality has been designed to be fully compatible with standard C programming constructs.
Here's another important thing to know about GObject method calls. When passing the object to a class function (when you're invoking a method), you need to cast the object to match the expected type of the function you're calling. For example, if you have a GtkButton object that you want to pass to a function that takes a GtkWidget argument, you would write:
gtk_widget_show (GTK_WIDGET (button));
In addition to the many improvements to GObject, Glib 2 introduces an OOP concept called "interfaces." To understand what interfaces are, let's look at an example.
Let's say that we wanted to create a Pegasus object for an application. Since a pegasus is a mythological winged horse, the natural OOP thing to do would be to create a new Pegasus class that uses the Horse class as its parent. Then, we could add the necessary code to our new class to support "wings." In this case, creating Pegasus as a subclass of Horse makes sense because it expresses a relationship between the two classes.
However, let's say we had a variety of unrelated classes, such as Horse, Car, and House, and we wanted to make it possible for all of them to talk. This new ability has nothing to do with their relationship to one another -- in fact, these three classes are not related in any way that is meaningful to us. Yet we want all of them to support new abilities related to talking. What would we do?
Interfaces provide a solution to this problem, allowing us to add common capabilities to disparate classes. So going back to the example above, we could simply write a "Talk" interface for the Horse, Car, and House classes. Suddenly, our three unrelated classes are "talk-enabled" and are able to work with new talk-related functions that we create. And these new talk-related functions happily interact with our objects by using the "Talk" interface themselves. So, thanks to interfaces, our three unrelated classes now speak the same language.
In Glib, you can create any number of interfaces for a class. So, if
we created a Talk interface for our House class, we could define a
function as follows:
void say (Talk *mytalk, const char *myphrase);
Then, we could call the
say() function, casting our
myhouse variable to the "Talk" interface:
say (TALK(myhouse), "hello there!");
A more down-to-earth, authentic example of interfaces can be found in the GtkEditable interface in GTK+ 2 (see Resources for a link). Both the text widget and the entry widget implement this interface.
Generally, event-driven GUI programs consist of one main loop. In this loop, the program continually waits for new messages to be sent from the X server. These messages, called events, are interpreted by the program, and allow the program to respond to the user selecting a menu item, clicking on a button, and so on.
Signals are a lot like events, except that they connect objects to one another. They allow objects to automatically "react" to changes in the state of another object without requiring an explicit event loop. The signal of one object simply needs to be connected to the method of another. Then, when the first object "emits" the signal (due to an internal change in state), the second object "catches" this change and reacts appopriately. No event loop is needed because signals are implemented using callbacks -- the emission of a signal simply results in a C function being called. Signals are the efficient and flexible glue that tie the objects in your program together.
In fact, if you look at the previous GNOMEnclature column, you'll see that we had quite a few example code lines that looked like this:
g_signal_connect (G_OBJECT (window), "destroy", gtk_main_quit, NULL);
In the above code snippet, we connect the
"destroy" signal of
our main window object to the
gtk_main_quit() function. Thanks to
g_signal_connect(), when our window emits a
"destroy" signal (when the
window is closed), our program will automatically quit. If you've ever
done any event-driven programming in the past, I think you'll find signals
to be a refreshing change from the common practice of setting up an
explicit event processing loop.
So, we've seen how to connect a signal to a standard C function. But how would we connect this same signal to the method of a particular object? Easy -- simply use the following template:
g_signal_connect (G_OBJECT (window), "destroy", classname_function, object);
classname_function with the method to be called, and
object with the object pointer upon which the method should
operate. To learn more about signals, check out the Resources section of this article for some good
However, note that Glib and GTK+ 2.0 bring some changes to the area of signals. While the underlying concepts haven't changed, the signal system has been moved from GTK+ 2.0 to Glib. This has the handy side-effect of making it possible to use signals in non-GUI applications. If you are writing new GNOME 2.0 code, you should use Glib's new GSignal class; GTK+ 2.0 signals are still around, but simply consist of a "wrapper" that uses Glib's GSignal behind the scenes.
Despite all the changes, Glib signals (prefixed by
generally very similar to their GTK+ counterparts (which are prefixed by
gtk_signal_). However, if you've created or are planning to create your
own custom signals, you should read the GObject GSignal reference
documentation (see Resources) to catch up
on some of the changes that may apply to you.
Now that we've taken a look at GObject fundamentals, you should be equipped with the necessary fundamental concepts to begin your GNOME programming career. I'll see you in the next "GNOMEnclature" column, as we continue our preparation for the GNOME 2 platform. See you then!
- To learn more about signals, check out the GTK+ 1.2 tutorial, particularly the Theory of Signals and Callbacks.
- For a much more in-depth treatment, check out the online version of GTK+/GNOME Application Development, by Havoc Pennington (New Riders Publishing).
- Check out the GObject GSignal reference documentation.
- To learn more about the GTK+ 2 API, be sure to read the online GTK+2 Reference Manual. In particular, you can see how the GtkEntry and GtkText widgets both implement the GtkEditable interface.
- You'll find FAQs, discussion forums, and kindred GNOME-developing spirits at the GNOME Developer's Site and GNOME Web site.
- Read the first "Getting ready for GNOME 2" article (developerWorks, January 2002).
- The GTK+ homepage is a good place to start for all things GTK+-related.
- If you're interested in GNOME's new text handling capabilities, be sure to visit the Pango project homepage.
- You can also learn more about Pango in The Pango connection: Part 1 (developerWorks, March 2001) and The Pango connection: Part 2 (developerWorks, April 2001).
- To learn more about accessibility under GNOME, visit the GNOME Accessibility Project Web page.
- Find more Linux articles in the developerWorks Linux zone.