Steve May 5, 2022
boost-the-power-of-c-with-these-open-source-libraries

The GLib Object System (GObject) is a library providing a flexible and extensible object-oriented framework for C. In this article, I demonstrate using the 2.4 version of the library.

Programming and development

The GObject libraries extend the ANSI C standard, with typedefs for common types such as:

  • gchar: a character type
  • guchar: an unsigned character type
  • gunichar: a fixed 32 bit width unichar type
  • gboolean: a boolean type
  • gint8, gint16, gint32, gint64: 8, 16, 32, and 64 bit integers
  • guint8, guint16, guint32, guint64: unsigned 8, 16, 32, and 64 bit integers
  • gfloat: an IEEE Standard 754 single precision floating point number
  • gdouble: an IEEE Standard 754 double precision floating point number
  • gpointer: a generic pointer type

Function pointers

GObject also introduces a type and object system with classes and interfaces. This is possible because the ANSI C language understands function pointers.

To declare a function pointer, you can do this:

 

void (*my_callback)(gpointer data);

But first, you need to assign the my_callback variable:

 

void my_callback_func(gpointer data)

{

  //do something

}

my_callback = my_callback_func;

The function pointer my_callback can be invoked like this:

 

gpointer data;

data = g_malloc(512 * sizeof(gint16));

my_callback(data);

Object classes

The GObject base class consists of 2 structs (GObject and GObjectClass) which you inherit to implement your very own objects.

You embed GObject and GObjectClass as the first struct field:

 

struct _MyObject

{

  GObject gobject;

  //your fields

};

struct _MyObjectClass

{

  GObjectClass gobject;

  //your class methods

};

GType my_object_get_type(void);

The object’s implementation contains fields, which might be exposed as properties. GObject provides a solution to private fields, too. This is actually a struct in the C source file, instead of the header file. The class usually contains function pointers only.

An interface can’t be derived from another interface and is implemented as following:

 

struct _MyInterface

{

  GInterface ginterface;

  //your interface methods

};

Properties are accessed by g_object_get() and g_object_set() function calls. To get a property, you must provide the return location of the specific type. It’s recommended that you initialize the return location first:

 

gchar *str

str = NULL;

g_object_get(gobject,

  "my-name", &str,

  NULL);

Or you might want to set the property:

 

g_object_set(gobject,

  "my-name", "Anderson",

  NULL);

The libsoup HTTP library

The libsoup project provides an HTTP client and server library for GNOME. It uses GObjects and the glib main loop to integrate with GNOME applications, and also has a synchronous API for use in command-line tools. First, create a libsoup session with an authentication callback specified. You can also make use of cookies.

 

SoupSession *soup_session;

SoupCookieJar *jar;

soup_session = soup_session_new_with_options(SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_BASIC,

  SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_DIGEST,

  NULL);

jar = soup_cookie_jar_text_new("cookies.txt",

  FALSE);    

soup_session_add_feature(soup_session, jar);

g_signal_connect(soup_session, "authenticate",

  G_CALLBACK(my_authenticate_callback), NULL);

Then you can create a HTTP GET request like the following:

 

SoupMessage *msg;

SoupMessageHeaders *response_headers;

SoupMessageBody *response_body;

guint status;

GError *error;

msg = soup_form_request_new("GET",

  "http://127.0.0.1:8080/my-xmlrpc",

  NULL);

status = soup_session_send_message(soup_session,

  msg);

response_headers = NULL;

response_body = NULL;

g_object_get(msg,

  "response-headers", &response_headers,

  "response-body", &response_body,

  NULL);

g_message("status %d", status);

cookie = NULL;

soup_message_headers_iter_init(&iter,

response_headers);

while(soup_message_headers_iter_next(&iter, &name, &value)){    

  g_message("%s: %s", name, value);

}

g_message("%s", response_body->data);

if(status == 200){

  cookie = soup_cookies_from_response(msg);

  while(cookie != NULL){

    char *cookie_name;

    cookie_name = soup_cookie_get_name(cookie->data);

    //parse cookies

    cookie = cookie->next;

  }

}

The authentication callback is called as the web server asks for authentication.

Here’s a function signature:

 

#define MY_AUTHENTICATE_LOGIN "my-username"

#define MY_AUTHENTICATE_PASSWORD "my-password"

void my_authenticate_callback(SoupSession *session,

  SoupMessage *msg,

  SoupAuth *auth,

  gboolean retrying,

  gpointer user_data)

{

  g_message("authenticate: ****");

  soup_auth_authenticate(auth,

                         MY_AUTHENTICATE_LOGIN,

                         MY_AUTHENTICATE_PASSWORD);

}

A libsoup server

For basic HTTP authentication to work, you must specify a callback and server context path. Then you add a handler with another callback.

This example listens to any IPv4 address on localhost port 8080:

 

SoupServer *soup_server;

SoupAuthDomain *auth_domain;

GSocket *ip4_socket;

GSocketAddress *ip4_address;

MyObject *my_object;

GError *error;

soup_server = soup_server_new(NULL);

auth_domain = soup_auth_domain_basic_new(SOUP_AUTH_DOMAIN_REALM, "my-realm",

  SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, my_xmlrpc_server_auth_callback,

  SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA, my_object,

  SOUP_AUTH_DOMAIN_ADD_PATH, "my-xmlrpc",

  NULL);

soup_server_add_auth_domain(soup_server, auth_domain);

soup_server_add_handler(soup_server,

  "my-xmlrpc",

  my_xmlrpc_server_callback,

  my_object,

  NULL);

ip4_socket = g_socket_new(G_SOCKET_FAMILY_IPV4,

  G_SOCKET_TYPE_STREAM,

  G_SOCKET_PROTOCOL_TCP,

  &error);

ip4_address = g_inet_socket_address_new(g_inet_address_new_any(G_SOCKET_FAMILY_IPV4),

  8080);

error = NULL;

g_socket_bind(ip4_socket,

  ip4_address,

  TRUE,

  &error);

error = NULL;

g_socket_listen(ip4_socket, &error);

error = NULL;

soup_server_listen_socket(soup_server,

  ip4_socket, 0, &error);

In this example code, there are two callbacks. One handles authentication, and the other handles the request itself.

Suppose you want a web server to allow a login with the credentials username my-username and the password my-password, and to set a session cookie with a random unique user ID (UUID) string.

 

gboolean my_xmlrpc_server_auth_callback(SoupAuthDomain *domain,

  SoupMessage *msg,

  const char *username,

  const char *password,

  MyObject *my_object)

{

  if(username == NULL || password == NULL){

    return(FALSE);

  }

  if(!strcmp(username, "my-username") &&

     !strcmp(password, "my-password")){

    SoupCookie *session_cookie;

    GSList *cookie;

    gchar *security_token;

    cookie = NULL;

    security_token = g_uuid_string_random();

    session_cookie = soup_cookie_new("my-srv-security-token",

      security_token,

      "localhost",

      "my-xmlrpc",

      -1);

     cookie = g_slist_prepend(cookie,

       session_cookie);  

     soup_cookies_to_request(cookie,

       msg);

    return(TRUE);

  }

  return(FALSE);

}

A handler for the context path my-xmlrpc:

 

void my_xmlrpc_server_callback(SoupServer *soup_server,

  SoupMessage *msg,

  const char *path,

  GHashTable *query,

  SoupClientContext *client,

  MyObject *my_object)

{

  GSList *cookie;

  cookie = soup_cookies_from_request(msg);

  //check cookies

}

A more powerful C

I hope my examples show how the GObject and libsoup projects give C a very real boost. Libraries like these extend C in a literal sense, and by doing so they make C more approachable. They do a lot of work for you, so you can turn your attention to inventing amazing applications in the simple, direct, and timeless C language.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.

Read More