Writing Object Oriented Programs in C
A guide to creating custom GObjects in C and beginning object oriented programming
If you are programming in C and want to follow object oriented principles or simply want to create objects to encapsulate data and functions, you have the option of use GObjects. This tutorial will help you write you own GObjects/
We will start with the basic structure of a GObject that defines the prerequisites in the header and source file. Once we have the structure, we will add properties, signals and functions to make it complete.
We are creating a class named MyClass which has2 private members for storing 2 integers. MyClass adds or multiples two numbers a and b and produces the result.
Let’s begin.
Here are the steps we will follow
Start with the basic structure of a GObject
Add Properties
Add Signals
Add Functions
Write a test application
Let’s begin
Step 1: Start with the basic structure
You will find the basic structure of a GObject in the directory basic/ under writings/gobject_oop_c/. Get the code from the GitHub repository.
git clone https://www.github.com/mndar/writings
Here is the header file myclass.h the defines the macros and structs for our class.
#ifndef __MYCLASS_H__
#define __MYCLASS_H__
#include <glib.h>
#include <glib-object.h>
#define MYCLASS_TYPE (myclass_get_type ())
#define MYCLASS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MYCLASS_TYPE, MyClass))
#define MYCLASS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MYCLASS_TYPE, MyClassClass))
#define MYCLASS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MYCLASS_TYPE, MyClassClass))
typedef struct _MyClass MyClass;
typedef struct _MyClassClass MyClassClass;
typedef struct {
gint a, b;
}MyClassPrivate;
struct _MyClass {
GObject parent;
};
struct _MyClassClass {
GObjectClass parent;
};
MyClass *myclass_new();
void myclass_set_a_b (MyClass *, int, int);
#endif
Here is the definition file myclass.c.
#include <glib.h>
#include "myclass.h"
G_DEFINE_TYPE_WITH_PRIVATE(MyClass, myclass, G_TYPE_OBJECT);
static void
myclass_init (MyClass *self) {
}
static void
myclass_class_init (MyClassClass *klass) {
//GObjectClass *object_class = G_OBJECT_CLASS (klass);
}
static void
myclass_init (MyClass *self) {
g_print ("Myclass Constructor\n");
}
MyClass *
myclass_new () {
MyClass *obj;
g_print ("Creating object of MyClass\n");
obj = g_object_new (MYCLASS_TYPE, NULL);
return obj;
}
void
myclass_set_a_b (MyClass *myclass, int a, int b) {
g_print ("Setting a and b\n");
}
Step 2: Add Properties
Properties allow you to set variables inside the class and Signals give you a way of communicating with objects of the class.
Below are snippets are code for reference from the code in the repository. The best way to learn how to create GObjects in C is to follow this guide and link the understanding from the snippets to the code in the repository. Create GObjects require lot of boiler plate code. Using a template like the one on the basic directory and using it as a starting point makes things easier.
enum
{
PROP_OPERATION = 1,
N_PROPERTIES
};
static GParamSpec *myclass_properties[N_PROPERTIES] = { NULL, };
typedef struct {
enum {OP_ADD, OP_MUL} operation;
...
}MyClassPrivate;
static void
myclass_class_init (MyClassClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = myclass_set_property;
object_class->get_property = myclass_get_property;
myclass_properties[PROP_OPERATION] = g_param_spec_int ("operation", "Operation",
"Operation to be performed on the values.", -1, 10,
-1, G_PARAM_READWRITE);
g_object_class_install_properties (object_class, N_PROPERTIES, myclass_properties);
}
And here are the get and set functions for the properties.
myclass_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
MyClass *self = MYCLASS (object);
MyClassPrivate *priv = myclass_get_instance_private (self);
switch (property_id) {
case PROP_OPERATION:
priv->operation = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
myclass_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
MyClass *self = MYCLASS (object);
MyClassPrivate *priv = myclass_get_instance_private (self);
switch (property_id) {
case PROP_OPERATION:
g_value_set_int (value, priv->operation);
break;
}
}
Step 3: Add Signals
Signals allow the object to communicate an event to the rest of the program. Let’s define a signal name “operation-completed” that will indicate that the addition or multiplication operation is complete.
When a signal is emitted by an instance of the class, you can link it to a function i.e. an action that takes place when the signal is emitted. Signals can be emitted with g_signal_emit_by_name(…) by providing it an instance of the class.
//myclass.h
enum {
SIGNAL_OPERATION_COMPLETE,
N_SIGNALS
};
static guint myclass_signals[N_SIGNALS] = { 0 };
//myclass.c
static void
myclass_class_init (MyClassClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS (klass);
...
...
//signals
myclass_signals[SIGNAL_OPERATION_COMPLETE] = g_signal_new
("operation-complete",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
1, G_TYPE_INT);
}
The last two parameters to g_signal_new(…) indicate that there is 1 parameter and it is an integer. We will place the result in this parameter.
Step 4: Add Functions
Let’s add functions that will operate on the data in the class. All these function will have their first parameter as an instance of the class.
//myclass.h
void myclass_set_a_b (MyClass *, int, int);
void myclass_perform_op (MyClass *);
//myclass.c
void
myclass_set_a_b (MyClass *myclass, int a, int b) {
g_print ("Setting a and b\n");
MyClassPrivate *priv = myclass_get_instance_private (myclass);
priv->a = a;
priv->b = b;
}
void
myclass_perform_op (MyClass *myclass) {
MyClassPrivate *priv = myclass_get_instance_private (myclass);
switch (priv->operation) {
case 0: //add
int result_add = priv->a + priv->b;
g_signal_emit_by_name (myclass, "operation-complete", result_add);
break;
case 1:
int result_mul = priv->a * priv->b;
g_signal_emit_by_name (myclass, "operation-complete", result_mul);
break;
}
}
Step 5: Write a test application
Finally let’s write a test application that sets the operation to add (0) and adds two numbers 10 and 20.
//testclass.c
#include "myclass.h"
static void operation_complete (MyClass *object, gint result, gpointer data) {
g_print("Result: %d\n", result);
}
int main (int argc, char *argv[]) {
MyClass *object;
object = myclass_new ();
g_signal_connect (object, "operation-complete", G_CALLBACK(operation_complete), NULL);
myclass_set_a_b (object, 10, 20);
g_object_set (object, "operation", 0, NULL);
myclass_perform_op (object);
return 0;
}
Compile the program with the following command
gcc -Werror -o testclass testclass.c myclass.c `pkg-config --cflags --libs glib-2.0 gobject-2.0`
When you run the application, you will get the result as 30
$ ./testclass
Creating object of MyClass
Myclass Constructor
Setting a and b
Result: 30
If you want to change the operation to multiplication change g_object_set(…) to the code below and your result will change to 200.
g_object_set (object, "operation", 1, NULL);
$ ./testclass
Creating object of MyClass
Myclass Constructor
Setting a and b
Result: 200
Conclusion
Creating objects in C using GObject is certainly doable as you saw in this article. If you follow a template having the fair bit of boiler plate code, you are good to go. Properties and Signals give you C program a new dimension and it is certainly useful especially is you are using other GObject based libraries like GStreamer.
That’s it for this article. Hope you enjoyed object oriented programming in C.