Next: Using Smalltalk, Previous: Other C functions, Up: C and Smalltalk
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:
struct
that maps the representation of the class
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.
Next: Using Smalltalk, Previous: Other C functions, Up: C and Smalltalk