4.7 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:
-
Defining the Smalltalk class
-
Defining a C
struct that maps the representation of the class
-
Actually using the C struct
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
instanceVariableNames: 'database request'
classVariableNames: ''
poolDictionaries: ''
category: 'SQL-C interface'
SQLAction subclass: #SQLRequest
instanceVariableNames: 'returnedRows'
classVariableNames: ''
poolDictionaries: ''
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
4.4 Manipulating Smalltalk data from C, point to the actual object only indirectly) and
get a pointer to the actual data. You do that with the oopToObj
macro (note the type casting):
| SQLAction action = (SQLAction) oopToObj(oop);
|
Now you can use the fields in the object like in this pseudo-code:
| invoke_sql_query(
vmProxy->oopToCObject(action->database),
vmProxy->oopToString(action->request);
query_completed_callback, /* Callback function */
oop); /* Passed to the callback */
...
/* Imagine that invokeSQLQuery 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 (oopClass(oop) == sqlActionClass)
return;
if (oopClass (oop) == sqlObjectCreationClass)
{
SQLObjectCreation oc;
oc = (SQLObjectCreation) oopToObj (clientData);
cObject = vmProxy->cObjectToOOP(result->dbObject)
oc->newDBObject = cObject;
}
else
{
/* SQLRequest or SQLUpdateQuery */
cObject = vmProxy->cObjectToOOP(result->rows);
query = (SQLUpdateQuery) oopToObj(clientData);
query->returnedRows = cObject;
if (oopClass(oop) == sqlUpdateQueryClass)
query->numReturnedRows = vmProxy->intToOOP(result->count);
}
}
|
Note that the result of oopToObj 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 section 4.4 Manipulating Smalltalk data from C). 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
indexedWord , indexedOOP and indexedByte macros
declared in gstpub.h , which return an lvalue for the given indexed
instance variable--for more information, see section 4.6 Other functions available to modules.
|