Next: , Previous: Smalltalk callbacks, Up: C and Smalltalk


5.8 Manipulating instances of your own Smalltalk classes from C

Although gnu Smalltalk's library exposes functions to deal with instances of the most common base class, it's likely that, sooner or later, you'll want your C code to directly deal with instances of classes defined by your program. There are three steps in doing so:

In this chapter you will be taken through these steps considering the hypotetical task of defining a Smalltalk interface to an SQL server.

The first part is also the simplest, since defining the Smalltalk class can be done in a single way which is also easy and very practical; just evaluate the standard Smalltalk code that does that:

         Object subclass: SQLAction [
              | database request |
              <category: 'SQL-C interface'>
         ]
     
         SQLAction subclass: SQLRequest [
              | returnedRows |
              <category: 'SQL-C interface'>
         ]

To define the C struct for a class derived from Object, gnu Smalltalk's gstpub.h include file defines an OBJ_HEADER macro which defines the fields that constitute the header of every object. Defining a struct for SQLAction results then in the following code:

         struct st_SQLAction {
             OBJ_HEADER;
             OOP database;
             OOP request;
         }

The representation of SQLRequest in memory is this:

         .------------------------------.
         |     common object header     |    2 longs
         |------------------------------|
         | SQLAction instance variables |
         |           database           |    2 longs
         |           request            |
         |------------------------------|
         | SQLRequest instance variable |
         |         returnedRows         |    1 long
         '------------------------------'

A first way to define the struct would then be:

         typedef struct st_SQLAction {
             OBJ_HEADER;
             OOP database;
             OOP request;
             OOP returnedRows;
         } *SQLAction;

but this results in a lot of duplicated code. Think of what would happen if you had other subclasses of SQLAction such as SQLObjectCreation, SQLUpdateQuery, and so on! The solution, which is also the one used in gnu Smalltalk's source code is to define a macro for each superclass, in this way:

         /* SQLAction
             |-- SQLRequest
             |     `-- SQLUpdateQuery
             `-- SQLObjectCreation       */
     
         #define ST_SQLACTION_HEADER         \
             OBJ_HEADER;                     \
             OOP database;                   \
             OOP request                     /* no semicolon */
     
         #define ST_SQLREQUEST_HEADER        \
             ST_SQLACTION_HEADER;            \
             OOP returnedRows                /* no semicolon */
     
         typedef struct st_SQLAction {
             ST_SQLACTION_HEADER;
         } *SQLAction;
     
         typedef struct st_SQLRequest {
             ST_SQLREQUEST_HEADER;
         } *SQLRequest;
     
         typedef struct st_SQLObjectCreation {
             ST_SQLACTION_HEADER;
             OOP newDBObject;
         } *SQLObjectCreation;
     
         typedef struct st_SQLUpdateQuery {
             ST_SQLREQUEST_HEADER;
             OOP numUpdatedRows;
         } *SQLUpdateQuery;

Note that the macro you declare is used instead of OBJ_HEADER in the declaration of both the superclass and the subclasses.

Although this example does not show that, please note that you should not declare anything if the class has indexed instance variables.

The first step in actually using your structs is obtaining a pointer to an OOP which is an instance of your class. Ways to do so include doing a call-in, receiving the object from a call-out (using #smalltalk, #self or #selfSmalltalk as the type specifier).

Let's assume that the oop variable contains such an object. Then, you have to dereference the OOP (which, as you might recall from Smalltalk types, point to the actual object only indirectly) and get a pointer to the actual data. You do that with the OOP_TO_OBJ macro (note the type casting):

         SQLAction action = (SQLAction) OOP_TO_OBJ(oop);

Now you can use the fields in the object like in this pseudo-code:

         /* These are retrieved via classNameToOOP and then cached in global
            variables */
         OOP sqlUpdateQueryClass, sqlActionClass, sqlObjectCreationClass;
         ...
         invoke_sql_query(
             vmProxy->oopToCObject(action->database),
             vmProxy->oopToString(action->request);
             query_completed_callback,       /* Callback function */
             oop);                           /* Passed to the callback */
     
         ...
     
         /* Imagine that invoke_sql_query runs asynchronously and calls this
            when the job is done. */
         void
         query_completed_callback(result, database, request, clientData)
              struct query_result *result;
              struct db *database;
              char *request;
              OOP clientData;
         {
           SQLUpdateQuery query;
           OOP rows;
           OOP cObject;
     
           /* Free the memory allocated by oopToString */
           free(request);
     
           if (OOP_CLASS (oop) == sqlActionClass)
             return;
     
           if (OOP_CLASS (oop) == sqlObjectCreationClass)
             {
               SQLObjectCreation oc;
               oc = (SQLObjectCreation) OOP_TO_OBJ (clientData);
               cObject = vmProxy->cObjectToOOP (result->dbObject)
               oc->newDBObject = cObject;
             }
           else
             {
               /* SQLRequest or SQLUpdateQuery */
               cObject = vmProxy->cObjectToOOP (result->rows);
               query = (SQLUpdateQuery) OOP_TO_OBJ (clientData);
               query->returnedRows = cObject;
               if (OOP_CLASS (oop) == sqlUpdateQueryClass)
                 query->numReturnedRows = vmProxy->intToOOP (result->count);
             }
     
         }
     

Note that the result of OOP_TO_OBJ is not valid anymore if a garbage-collection happens; for this reason, you should assume that a pointer to object data is not valid after doing a call-in, calling objectAlloc, and using any of the “C to Smalltalk” functions except intToOOP (see Smalltalk types). That's why I passed the OOP to the callback, not the object pointer itself.

If your class has indexed instance variables, you can use the INDEXED_WORD, INDEXED_OOP and INDEXED_BYTE macros declared in gstpub.h, which return an lvalue for the given indexed instance variable—for more information, see Other C functions.