Gnome developers' tutorial

Gergely Polonkai

This work is licenced under the Creative Commons Attribution-Share Alike 3.0 Unported License. To view a copy of this licence, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.


Table of Contents
Preface
I. GLib
Preface
1. The main event loop
1.1. Idle functions
1.2. Timeout functions
2. Memory handling
2.1. Basic memory handling
2.2. Memory slices and chunks
2.3. Caches
2.4. Trash stacks
3. Lists
3.1. Singly linked lists
3.2. Doubly linked lists
3.3. Double-ended queues
3.4. Sequences
3.5. Hash tables
3.6. Arrays
3.7. Balanced binary trees
3.8. N-ary trees
3.9. Quarks
3.10. Keyed data lists
3.11. Datasets
3.12. Relations and tuples
4. String utility functions
4.1. Basic string handling
4.2. Advanced string handling
4.3. Formatting and printing
4.4. Getting character information (character classes)
4.5. Basic string conversion
4.6. Advanced conversion
4.7. Character set conversion
4.8. Unicode handling
4.9. Base64 encoding
4.10. Checksums
4.11. URI functions
4.12. Shell related functions
4.13. Glob-style pattern matching
4.14. Simple XML parser
5. Automatic string completion
6. Command line parsing
7. Date and time
8. Timers
9. Random numbers
10. Internationalization
11. Filesystem related stuff
12. Key-value file parsing
13. Bookmark-file parsing
14. File (and others) handling
15. Process handling
16. Hook functions
17. Error handling
18. Debugging and message output
19. Message logging
20. Threads
21. Lexical scanner
22. UNIX emulation on Windows
23. Miscelannous functions
A. Some GLib types
A.1. gboolean
A.2. gpointer and gconstpointer
A.3. gchar, guchar, gint, guint, gshort, gushort, glong and gulong
A.4. gint8, guint8, gint16, guint16, gint32, guint32, gint64 and guint64
A.5. gfloat and gdouble
A.6. gsize and gssize
A.7. goffset
B. Useful macros
B.1. G_OS_WIN32, G_OS_BEOS and G_OS_UNIX
B.2. G_DIR_SEPARATOR and G_DIR_SEPARATOR_S
B.3. G_SEARCHPATH_SEPARATOR and G_SEARCHPATH_SEPARATOR_S
B.4. TRUE, FALSE and NULL
B.5. MIN(a, b) and MAX(a, b)
B.6. ABS(x)
B.7. CLAMP(x, low, high)
C. Compiling GLib programs
II. GIO
Preface
III. GObject
Preface
IV. Pango
Preface
V. GDK
Preface
VI. GTK+
Preface
VII. LibXML2 and LibXSLT
Preface
VIII. LibGlade
Preface
IX. GConf
Preface
X. LibVTE
Preface
XI. DBus-GLib
Preface
XII. GMime
Preface
XIII. GNet
Preface
XIV. Gnome Data Access
Preface
XV. GNOME Structured File Library
Preface
List of Examples
1-1. Using main loops and timeout functions (01_main_loop_timeout.c)
2-1. Allocating memory, the basic way (02_memory_allocation.c)
C-1. Compiling single-source GLib programs with pkg-config
C-2. Compiling multi-source GLib programs with pkg-config

Preface

A few weeks ago I got a new task to create a server-side software. After hours of thinking and planning, I have decided to get back to my childhood friend, GLib. This little boy, however, has gone mature since we last met.

After a few hours of re-learning, I found out that GLib doesn't have a real tutorial, like GTK has. My first thought was to write one, but after creating the skeleton of this document, I decided to make a bigger thing: a Gnome developers' tutorial.

The Gnome libraries can work together greatly. GLib for some basic programming; this is the base of the other libraries. Then we go up step by step from GLib, through GDK and GTK up to the highest levels like GnomeUI and friends. Most of these libraries are written to be highly portable, and besides their source code, many binaries exist for many different platforms.

All through this book I will talk about portability many times. I will try to write only about libraries and functions that are known to be available on more platforms. However, it is possible that some useful libraries don't exist on them. In those cases, I will say it in that part's preamble.

Also, in the examples I will try to use only the mentioned libraries' functions, trying to avoid calls to other libraries. E.g. I will use GLib's g_printf() instead of the standard I/O function printf(), which is well known, but required a library called stdc. Although this library is available on almost every platforms, I will try not to use it this time. This can be confusing sometimes, but you have been warned.

I. GLib

Table of Contents
Preface
1. The main event loop
1.1. Idle functions
1.2. Timeout functions
2. Memory handling
2.1. Basic memory handling
2.2. Memory slices and chunks
2.3. Caches
2.4. Trash stacks
3. Lists
3.1. Singly linked lists
3.2. Doubly linked lists
3.3. Double-ended queues
3.4. Sequences
3.5. Hash tables
3.6. Arrays
3.7. Balanced binary trees
3.8. N-ary trees
3.9. Quarks
3.10. Keyed data lists
3.11. Datasets
3.12. Relations and tuples
4. String utility functions
4.1. Basic string handling
4.2. Advanced string handling
4.3. Formatting and printing
4.4. Getting character information (character classes)
4.5. Basic string conversion
4.6. Advanced conversion
4.7. Character set conversion
4.8. Unicode handling
4.9. Base64 encoding
4.10. Checksums
4.11. URI functions
4.12. Shell related functions
4.13. Glob-style pattern matching
4.14. Simple XML parser
5. Automatic string completion
6. Command line parsing
7. Date and time
8. Timers
9. Random numbers
10. Internationalization
11. Filesystem related stuff
12. Key-value file parsing
13. Bookmark-file parsing
14. File (and others) handling
15. Process handling
16. Hook functions
17. Error handling
18. Debugging and message output
19. Message logging
20. Threads
21. Lexical scanner
22. UNIX emulation on Windows
23. Miscelannous functions
A. Some GLib types
A.1. gboolean
A.2. gpointer and gconstpointer
A.3. gchar, guchar, gint, guint, gshort, gushort, glong and gulong
A.4. gint8, guint8, gint16, guint16, gint32, guint32, gint64 and guint64
A.5. gfloat and gdouble
A.6. gsize and gssize
A.7. goffset
B. Useful macros
B.1. G_OS_WIN32, G_OS_BEOS and G_OS_UNIX
B.2. G_DIR_SEPARATOR and G_DIR_SEPARATOR_S
B.3. G_SEARCHPATH_SEPARATOR and G_SEARCHPATH_SEPARATOR_S
B.4. TRUE, FALSE and NULL
B.5. MIN(a, b) and MAX(a, b)
B.6. ABS(x)
B.7. CLAMP(x, low, high)
C. Compiling GLib programs

Preface

GLib is a powerful tool in the good hands. It gives you many possibilities: it works so well in file and network handling and thread execution, and so on. But trust me. You haven't heard everything yet.

In this part, I will show you some examples on using GLib's features. There will be some snippets which could be done much faster with standard C stuff, but most of those times you sacrifice portability on the altar of fastness. So always think before you decide: GLib is highly portable as it is available on many systems nowadays.

This tutorial is mostly based on the original GLib API documentation. As of the time of writing, it can be accessed at http://library.gnome.org/devel/glib/stable/.

The examples were written and tested with GLib version 2.6.15 (the most recent stable version as of writing), under a Gentoo Linux. I'm writing them in ViM, so although I will double check, it is possible that I will leave some ViM-specific stuff in the examples, like ViM modelines.


Chapter 1. The main event loop

Usually, this is the heart of a GLib program. An event loop manages all the given sources of events. These events can come for various sources like file descriptors or sockets, or even timeouts. Or, if you want to, you can create your own event source, even with a new hardware driver, or whatever you want.

If you are creating a threaded application, you will likely have many independent sources in many threads. In this case, you will have to use contexts, called GMainContexts. A GMainContext can run only in one thread, but sources can belong to more than one contexts (thus threads).

Event sources also have a priority. By default, it's G_PRIORITY_DEFAULT, which is defined as 0. All priorities below 0 are higher. If more events occur in a given time, the one with higher priority will be processed first. There are also special events, which can be called idle events. Their priority number is a positive value, thus they are processed when there are no events exist with default (or higher) priority.

The event loops are represented by the GMainLoop data type. It can be created with a call to g_main_loop_new(). After it is created, it can be started with a call to g_main_loop_run(). This will continuously check for new events from each assigned sources and dispatch them, or, if no events are available, run the idle functions. If an event handler decides, it can call g_main_loop_quit(), which will send a special event to the main loop. This event will be processed immediately, and will make the loop to end, and g_main_loop_run() to return.

There are times, when multiple event loops need to be exist. A typical case is when a GTK window opens a dialog box, which is set to modal. In this case, the main window's loop will wait for the dialog's main event loop to end. GTK, however, contains wrapper functions for this purpose, like gtk_main() and gtk_main_quit().


1.1. Idle functions

Besides events, you can also register idle functions. If your application has to do some work what has very low priority (e.g refining graphics or save some backups or user information), it can be done with an idle function. These functions run only when no events exist in the event queue.


1.2. Timeout functions

Timeout functions work in a bit different way. After the given time, a new event gets into the event loop, with a default priority. This event will generate a call to the specified function. After the function call, the timer restarts, but only if the timeout function returned TRUE. Otherwise, this timer gets destroyed, and will generate events no more.

Example 1-1. Using main loops and timeout functions (01_main_loop_timeout.c)

#include <glib.h>

#define COUNT_UNTIL 2
#define TIMEOUT 2

static int counter;

GMainLoop *main_loop;

gboolean
timeout_func(gpointer data)
{
    counter++;

    g_printf("Running for the %d. time.\n", counter);

    if (counter == COUNT_UNTIL)
    {
        g_main_loop_quit(main_loop);
        return FALSE;
    }

    return TRUE;
}

int
main(int argc, char **argv)
{
    counter = 0;

    main_loop = g_main_loop_new(NULL, FALSE);

    g_timeout_add_seconds(TIMEOUT, timeout_func, NULL);

    g_main_loop_run(main_loop);

    return 0;
}

After compiling and running this example, you will see that (in the ideal case) timeout_func() will be called every two seconds, exactly two times. Let's get through this program, line by line.

In main(), I simply initialize the counter variable to indicate that the current loop number is 0. Then I create the main loop object with g_main_loop_new(). The first parameter is the context, where the event loop will receive its events. As I passed NULL, it will be set to the default context. In a more complicated environment, e.g when using threads, it can be something totally different. The second parameter is FALSE, which indicates that this loop is currently not running. In some special cases a main loop can be initialized with a running state if you set this parameter to TRUE.

When the main loop is created, I add a new timeout function. In every TIMEOUT seconds, the function timeout_func() will be called with the user parameter of NULL.

Now that the main loop is ready, and we set up every event sources, it's time to get this loop running. This is done in the next line, with the call to g_main_loop_run(). It gets only one parameter: the variable which contains the event loop "object".

When our main event loop will end, the program gets to the last line, which will make main() to return 0, indicating that everything went fine. But how will this infinite loop end?

timeout_func() will do this work. First of all, it increases the counter variable, then it prints out the number of the current call, and check the counter variable. If it reached COUNT_UNTIL, it calls g_main_loop_quit(), which will tell the running event loop main_loop to finish running. In this case, the loop will wait for every running event handlers to return, and when they do, the call to g_main_loop_run() in main() will return. Also, to be so called good programmers, we return FALSE to indicate that we don't need this timeout function any more, so it can be removed from the event loop's sources.

Note

This strictness is not really needed in this example. As we already said our event loop to stop running, it will never call timeout_func() again. Anyhow, it's easier to learn this type of programming this early. It can save you hours of debugging later.

At the end of timeout_func() I return TRUE. This will tell the timeout handler that we still need this function to be called after the specified time (defined in seconds with the TIMEOUT macro) elapses.


Chapter 2. Memory handling

GLib implements its own memory handling functions on top of the standard C methods. Although in general they are the same, GLib extends the standard way heavily. For example, when you try to allocate memory, and it fails (e.g not enough memory or such), they terminate the program. This means that you don't have to check if the memory allocation has succeeded. There might be cases though when you want to try allocating new memory, but you don't want your program to be terminated upon failure. Fot this cases, there are the g_try_* functions. And, for the best part: usually when you allocate memory, you will want to zero that space. There are the functions with a zero at the end of their names, which exactly do this (like g_malloc0(), which allocates and initializes some memory with nul-bytes).

Besides these basic functions, with GLib you can work with equal-sized chunks of memory, which is very efficient on memory usage. Also, you can use caches, which can be used to share resources while conserving memory.


2.1. Basic memory handling

GLib has recreated the standard memory allocation procedures. Their functions use the standard ones as a backend, but they are extended heavily. They can terminate your application when they cannot allocate memory, or fill the allocated space with zeros. Also, there are some macros to ease the allocation of memory "arrays", like many int *'s. It's good to see this written, but how does it work?

Example 2-1. Allocating memory, the basic way (02_memory_allocation.c)

#include <glib.h>

int
main(int argc, char **argv)
{
    gchar *string;
    gsize huge_size = 0xffffffff;

    string = g_new(gchar, 10);
    /* string is not initialized, it points to a random area now */
    string[0] = 'H';
    string[1] = '!';
    string[2] = '\0';
    g_printf("%s\n", string);
    g_free(string);

    string = (gchar *)g_malloc0(10);
    /* string is now initilazized with nul-bytes */
    string[0] = 'H';
    string[1] = '!';
    g_printf("%s\n", string);
    g_free(string);

    /* The next allocate should fail */
    string = g_try_malloc(huge_size);
    if (string == NULL)
        g_printf("Failed to allocate %lu bytes of memory.\n", huge_size);
    else
        g_free(string);

    return 0;
}

This little program demostrates the three main aspects of memory allocation. In the first part, I allocate a gchar pointer with 10 bytes of memory. g_new doesn't initialize the allocated memory, so the string I put in it by characters (H and !) has to be suffixed with a nul-byte.


2.2. Memory slices and chunks

Slices are small chunks of memory with equal sizes. Their use over the standard or GLib'ized malloc() family is wise if you need many chunks of memory with a small size, because the allocator is optimized. This way you application will consume much less physical memory. Although memory is not a real problem nowadays, it can also speed up your application. In pre-2.8 GLib they were called memory chunks, which had several disadvantages. So called memory chunks are now deprecated, and slices should be used instead.


2.3. Caches

Caches allow sharing of complex data structures. This can come handy if you need to share bigger resources.


Chapter 3. Lists

Lists are useful tools to store and manage identical data. This data can be simple, like a plain string, or even a very complicated data structure. Storing this kind of data can be done several ways. Which one you should use depends on the goal.


3.1. Singly linked lists

Singly linked lists are the most effective if memory comes into your mind. In this case the internal structure contains only one pointer which points to the next element. This make searching a bit slow, as you need to start from the first element to iterate through the whole list. It uses the slice allocator, thus being very effective on memory usage.


3.2. Doubly linked lists

The doubly linked list is the improved version of the singly linked one. In this case, the structure also contains a pointer to the previous value. This can speed up searching, but because of the two internal pointers it consumes a bit more memory. However, it also uses the slice allocator, so the memory usage of this list can still be small.


3.3. Double-ended queues

Queues are special doubly linked lists. Queues store both the starting and ending element of the list, so it is easy to use them both as a FIFO or LIFO queue. Using this type of list is much slower than the former two, as it always have to update its internal structure, which contains a pointer of the starting and ending element, so as the count of elements.


3.4. Sequences

Sequences are the other type of special lists. The API suggests it's some sort of a list, but internally it is stored as a binary tree. This makes sequences highly sortable.


3.5. Hash tables

Hash tables are what their name suggests: tables. They are actually key-value pairs, making them good for storing data like configuration items and such.


3.6. Arrays

Normal, pointer and byte arrays


3.7. Balanced binary trees

Balanced binary trees can be looked at as they are hash tables optimized for searching and traversing in order.


Chapter 4. String utility functions

This chapter tells you about utility functions to handle strings in many-many ways. You will learn how to allocate, copy, duplicate and format strings, how to check if a character is of a given type, convert between character sets, encode them in Base64 format, generate checksums, and much more.


4.2. Advanced string handling

GString and GStringChunk


4.5. Basic string conversion

upper, lower, reverse and friends


4.6. Advanced conversion

split and company


4.14. Simple XML parser

There is a library called libxml2, which is developed in parallel to GLib. It can be used for XML parsing (either), but there are times when you need to parse a very small XML chunk. For this, GLib gave us this very simple XML parser.


Chapter 5. Automatic string completion


Chapter 6. Command line parsing


Chapter 7. Date and time


Chapter 8. Timers


Chapter 9. Random numbers

GLib is armed with pseudo number generator functions.


Chapter 10. Internationalization

gettext support


Chapter 11. Filesystem related stuff

Although the filesystem can be accessed with GIO (predecessor of Gnome-VFS), sometimes this small subset of GLib functions is more than enough.


Chapter 12. Key-value file parsing

The good old .ini files

The good old .ini files


Chapter 13. Bookmark-file parsing


Chapter 14. File (and others) handling

GIOChannel

GIOChannel

Although most C implementations and I/O handling libraries implement this functionality well, there might be times when you want to write very portable programs which can work on all the platforms (well, at least on platforms where GLib itself works), or when you need some events to be generated from file descriptors.

For these times, GLib created IOChannels. These are some object-like stuff, which can integrate themselves in the event oriented self of GLib fine. That's because IOChannels have a little cousin, called watches. With watches, you can add a new event source to the main event loop's context, which can generate events when the associated file descriptor can be read or written. Also, IOChannels can be read and written with GLib's own functions.


Chapter 15. Process handling


Chapter 16. Hook functions


Chapter 17. Error handling


Chapter 18. Debugging and message output

Debugging, message output and testing


Chapter 19. Message logging


Chapter 20. Threads

One of GLib's strongest points is thread handling. With it, you can easily create single and simple threads for a small work, just like huge thread pools which can interact and can work concurrently. The former is an easy task with the already existing libraries, like Linux' pthread. The latter, however, can be a long, error-prone and complicated task. But with GLib, it's very easy to achieve such setup. Within them, you can set up your own communication mechanism, but GLib implements a well-working solution of asynchronous communication.


Chapter 21. Lexical scanner


Chapter 22. UNIX emulation on Windows

The following chapter is to emulate some old POSIX functions on Windows™. Naturally, they work only on Windows™ systems, so if you are programming only for Unices, you can safely skip this chapter. Also, if you want (almost) full POSIX compatibility on Windows™, you should try the CygWin project instead.


Chapter 23. Miscelannous functions


Appendix A. Some GLib types

While using GLib, you will meet many new types. Some of them will be more than important, others will be forgotten long before you finish reading this document.

GLib defines a bunch of types. Some of them are non standard to C (like gboolean); others are highly portable, and they mean exactly the same on every different OS's and architectures (like int8, which is 8 bits long on every system); many exist in parallel to C to ease coding (like gpointer); and for last but not least there are types that mean exactly the same like in C, and they just unify the GLib type names (like gchar, which is just the same like C's char).


A.1. gboolean

gboolean is a standard boolean type. It can get the value of TRUE or FALSE.

Note

For the restless, gboolean is a simple integer type, where FALSE is 0, and TRUE is simply !FALSE.


A.2. gpointer and gconstpointer

gpointer is a typeless pointer. It's just an alias of void *. Easier than the original, and it looks better. gconstpointer is the constant version of it (const void *).


A.3. gchar, guchar, gint, guint, gshort, gushort, glong and gulong

They are just aliases to C's standard types: char, unsigned char, int, unsigned int, short, unsigned short, long and unsigned long. They only exists to unify the type names. Their ranges can be checked with constants: g* types' range is between G_MINx and G_MAXx while gu* types' range is between 0 and G_MAXUx. (The original GLib documentation doesn't mention these range constants for gchar and guchar however).


A.4. gint8, guint8, gint16, guint16, gint32, guint32, gint64 and guint64

These are the types I call highly-portables. They always have the given length (8, 16, 32 and 64 bits respectively).

Note

In GLib versions before 2.0 64bit support was not necessary. Thus, there was a macro called G_HAVE_GINT64, which was defined only if the actual platform supported 64bit integers. In GLib 2.0 and later, 64bit support is required, so this macro is always defined.


A.5. gfloat and gdouble

Synonyms of C's float and double types. Their range is between -G_MAXx and G_MAXx.


A.6. gsize and gssize

gsize corresponds to the original C99's size_t type. It is an integer value which has enough space to store a pointer. Thus it's a guint32 on 32 bit systems, and a guint64 on 64 bit systems. gssize is the signed version of gsize (a bit confusing naming convention after the variables like guint)


A.7. goffset

A signed integer type to specify file offsets. Derived from C99's off64_t.


Appendix B. Useful macros

GLib has many useful macros which can be used at compile-time decisions and integration. With them, you can easily get the OS the compiler is running on, the memory page size, the directory separator, and so on. Below you can find a list of them.


B.1. G_OS_WIN32, G_OS_BEOS and G_OS_UNIX

These macros are defined only on the specific OS's (Win32, BeOS, UNIX respectively). G_OS_WIN32 and G_OS_UNIX covers more systems, like G_OS_WIN32 is defined on most current Windows™ systems (as I know, from Windows 95™ to Windows Vista™), while G_OS_UNIX is defined on all UNIX and UNIX-like systems, e.g BSD and Linux.


B.2. G_DIR_SEPARATOR and G_DIR_SEPARATOR_S

These contain the directory separator of the running OS. It's / in Unices and \ in Windows™. G_DIR_SEPARATOR holds it in a char ('/') while G_DIR_SEPARATOR_S holds it in a string ("/").


B.3. G_SEARCHPATH_SEPARATOR and G_SEARCHPATH_SEPARATOR_S

These contain the search path separator character of the running OS, just like G_DIR_SEPARATOR for the directories. It's usually a : in Unices and a ; in Windows™.


B.4. TRUE, FALSE and NULL

TRUE and FALSE hold the two values of a gboolean type. NULL is the standard NULL pointer, which is usually: (void *)(0).


B.5. MIN(a, b) and MAX(a, b)

These macros return the smaller or greater value from a and b, respectively.


B.6. ABS(x)

Returns the absolute value of the number x.


B.7. CLAMP(x, low, high)

Ensures that x is between low and high. If x is less than low, returns low; if x is greater than high, returns high; if x is between low and high, returns x. If low is greater than high, the result is undefined (will be low or high, depending on the three values).


Appendix C. Compiling GLib programs

It is wise to install pkg-config with GLib. pkg-config is a very handy tool, which can help you in compiling GLib programs (and many others, like GTK and friends, and even GnuTLS and gSASL have pkg-config data available on most systems).

pkg-config will give you many compiler parameters for you, like the include directories and libraries to be linked with your new executable. E.g on my system, pkg-config --cflags --libs glib-2.0 will return -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -lglib-2.0, which tells my compiler to look for include files in /usr/include/glib-2.0 and /usr/lib/glib-2.0/include and link in the library called glib-2.0.

The examples below use gcc in a bourne shell-like environment. If you are running on another systems, the same can be achieved by running pkg-config with the parameters (the whole command between the backticks), and copying their output to the compiler's parameters.

Example C-1. Compiling single-source GLib programs with pkg-config

gcc `pkg-config --cflags --libs glib-2.0` -o executable source.c

Or if you have multiple source files, you can use the following example. Note that source3.c doesn't need GLib, maybe it doesn't contain GLib-related code ;)

Example C-2. Compiling multi-source GLib programs with pkg-config

gcc `pkg-config --cflags glib-2.0` -o source1.o -c source1.c
gcc `pkg-config --cflags glib-2.0` -o source2.o -c source2.c
gcc -o source3.o -c source3.c
gcc `pkg-config --libs glib-2.0` -o executable source1.o source2.o source3.o

II. GIO

Table of Contents
Preface

Preface

III. GObject

Table of Contents
Preface

Preface

Although it's originally the part of GLib, it got a whole part of this book. GObject is a robust system, which can be used to extend GLib's, GTK's and such libraries' functionality, or to define a whole new class (GType to be correct).

IV. Pango

Table of Contents
Preface

Preface

V. GDK

Table of Contents
Preface

Preface

Plus GdkPixbuf

VI. GTK+

Table of Contents
Preface

Preface

VII. LibXML2 and LibXSLT

Table of Contents
Preface

Preface

VIII. LibGlade

Table of Contents
Preface

Preface

IX. GConf

Table of Contents
Preface

Preface

X. LibVTE

Table of Contents
Preface

Preface

XI. DBus-GLib

Table of Contents
Preface

Preface

XII. GMime

Table of Contents
Preface

Preface

XIII. GNet

Table of Contents
Preface

Preface

XIV. Gnome Data Access

Table of Contents
Preface

Preface

XV. GNOME Structured File Library

Table of Contents
Preface

Preface