G2++ Tutorial - G2++(C++)

C Interface

There are two sets of I/O routines available for reading and writing G2 records from a C program. One is for applications which deal with a specific set of G2 record types and the other is for programs that work with all G2 records, regardless of type. The first set of routines map G2 data records of known type directly to or from C variables. This is called the compiled interface. The second set of I/O routines map G2 data records of arbitrary type to or from a general-purpose data structure which can be subsequently navigated through a set of pointers. This is called the interpreted interface. Of course, both the compiled and interpreted interfaces can be mixed within a single program and at opposite ends of a communications stream.

Compiled Interface

The routines of the compiled interface are constructed from G2 record definitions. The construction process has four steps:

  1. A file of G2 record definitions is prepared and compiled using the record definition compiler, g2comp. Compilation yields a pair of files: the header file contains one or more C typedefs, and the code file contains one initialized C data structure, called a typedef descriptor, for each typedef. A typedef descriptor describes the physical layout of a typedef.

  2. The header file is place in the application program using #include, where the typedef(s) it contains are used to define variables that serve as the source or target of I/O routines.

  3. The functions getrec() and putrec() are called whenever the application needs to perform input or output, respectively. The appropriate parameters are passed

  4. The application files and typedef descriptor file are compiled and linked with the G2 library, producing an executable program.

We will use an example to show the compiled interface in more detail.

Let's assume that we simply want to read and write G2 records with the definition

   log     10
   id      8
   gid     LONG
   name    40

We start by typing the above definition into a file named usr.g (the name of the file is arbitrary, but the .g suffix is mandatory). This file is then compiled with the following UNIX command:

   $ g2comp usr.g

Assuming this step goes well, we now have two new files: usr.h which contains a typedef and usr.c which contains the typedef descriptor. The typedef in usr.h will look like

   typedef struct {
   char    log[10+1];
   char    id[8+1];
   long    gid;
   char    name[40+1];
   } USR;

(the extra character added to each field is for the null byte). Note that the typename (USR) is the uppercase version of the record name (usr).

Also in usr.h is an external declaration for the typedef descriptor in usr.c:

   extern G2DESC   *usr;

Now we can write a simple C program to read and write G2 usr records:

   #include <stdio.h>
   #include "usr.h"
   USR  buf;
   while(getrec(&buf, usr, stdin)){
     putrec(&buf, usr, stdout);

Assuming this program resides in a file named simple.c, we compile and link an executable program with the command:

   $ cc simple.c usr.c -lg2

In a more realistic program, there would be something useful happening between the getrec and putrec function calls. Such code would be in native C and independent of the G2 I/O functions, for example:

   USR  buf;
   while(getrec(&buf, usr, stdin)){
     putrec(&buf, usr, stdout);

Some applications must be prepared to deal with a collection of possible record types in the communications stream. In this case, function getname() can be called to get the name of the next G2 record, and function getbody() can then be used to map the remainder of the G2 record into a C variable of the appropriate type. The following example illustrates this usage.

   #include <stdio.h>
   #include <string.h>
   #include "usr.h"
   #include "sys.h"
   char  name[20];
   USR   usrbuf;
   SYS   sysbuf;
   while(getname(name, stdin)){
   }else if(strcmp(name,"sys")==0){

Here, two G2 record types, usr and sys, can be handled. In general, any number of G2 record types can be dealt with in this way. Note, however, that a priori knowledge of the record types was required. Unknown record types must be dealt with using the interpreted interface.

Interpreted Interface

The compiled interface requires that each G2 record type be known at compile time. For programs which need to handle the conceptually infinite class of G2 records in a general way, an interpretive interface is provided.

The functions getbuf() and putbuf() map G2 records between a stream and an internal data structure which can be ``navigated'' by following pointers. The data structure, of type G2BUF, is defined as follows in g2.h:

   struct G2NODE;
   struct G2BUF{
   char* name;
   char* val;
   G2NODE* child;
   G2NODE* next;
   struct G2NODE{
   G2NODE* root;

The following program uses the interpreted interface to produce a report of the contents of each G2 record in its input stream:

   #include <g2.h>
   G2BUF buf[1];
   walk(np, level)
   G2NODE* np;
   int level;
   G2NODE* cp;
   printf("%d: %s",level,np->name);
   printf("\ t%s\ n",np->val);
   printf("\ n");

Next topic: Guidelines for Using G2
Previous topic: Record Definition

© 2005 The SCO Group, Inc. All rights reserved.
SCO OpenServer Release 6.0.0 -- 02 June 2005