While parsing, the CParser generates the corresponding type information into a hierarchy of CType objects, which can then be used to create and manipulate byte-oriented data blocks.
In contrast to ST/X's inline C-code features, CParser and
CType are completely implemented as Smalltalk code and are therefore
easier to use and less error prone.
However, the performance may be slower than corresponding hardcoded
inline C routines, since a lot of meta information is kept in the
CType hierarchy.
#initialize
method.
Example:
Now, the classVariable
...
classVariableNames:'CTypes'
...
initialize
"parse C-Types from the file cDefs.h,
which contains C-Language types and #defines"
CTypes isNil ifTrue:[
parser := CParser new.
parser parse:('cDefs.h' asFilename readStream).
"/ fetch types ...
CTypes := parser types.
].
...
CTypes
refers to a dictionary
of C-types, where the keys are the type names.
Thus, if the C-header file contained the definition
a corresponding entry will be found in the dictionary under the key
struct myStruct {
int foo;
float bar;
};
myStruct
,
i.e.
Beside reading types, the CParser also keeps track of #define
definitions. Defines can be retrieved from the parser via
the
...
myStructType := CTypes at:'myStruct'.
...
#defines
message.
Notice, that defines are typeless - i.e. the cparser treats and
returns all defines as string-defines.
However, some protocol exists to extract a #defines integer value
(which might be required for bit-constants or array dimensions).
#include and #if directives are not handled by the CParser - if required, a cpp (c-preprocessor) filtered output must be used for CParser to handle the header file.
isCArray
isCCompound
isCEnum
isCNumber
isCStruct
isCUnion
isIndexed
memberNames
sizeof
dimension
elementType
new
new:
dim
malloc
malloc:
dim
gcMalloc
gcMalloc:
dim
onBytes:
bytes
onBytes:
if some data has either been allocated elsewhere
(for example, in a C-primitive function or library routine), or has
been read from a file or communication channel.
For example, when reading data from a DataBase or via a Socket.
Use new
/ new:
for all data which is not given
to C code directly (i.e. for message/data buffers for file storage,
or which are sent to another program via a pipe or socket).
Use gcMalloc
/ gcMalloc:
for data which is passed
to either inline C-code or to a C-library function and it is known that
the C-code does not keep a reference to the datum internally.
This memory will be automatically freed whenever smalltalk has no more
references to it.
Use malloc
/ malloc:
for data which is passed
to either inline C-code or to a C-library function and it is either
unknown if or certain that the C-code keeps references internally.
Be very careful to avoid memory leaks, since the storage must be
freed manually (via the #free
message) by the programmer.
type
at:
index
at:
index put:
val
memberAt:
fieldName
memberAt:
fieldName put:
val
#at:
/ #at:put:
and field members via
#memberAt:
/ #memberAt:put:
messages.
If the elementType (for arrays) or fieldType (for struct/union) is a scalar type (i.e. char, int, float or double), the get methods return smalltalk integers or floats, and the set-methods accept smalltalk numeric objects as value.
For a non-scalar element type, the get-methods return another
CDatum (i.e. a copy) and the set methods expect a cDatum.
For convenience, some smalltalk collections are allowed for setting:
doesNotUnderstand:
method is redefined
to allow for member access in the typical smalltalk fashion (i.e. get/set
protocol).
For example, the above example data structure can be allocated and
manipulated as:
or:
...
myStructType := CTypes at:'myStruct'.
myDatum := myStructType new.
myDatum memberAt:'foo' put:15.
myDatum memberAt:'bar' put:3.14159.
...
...
myDatum foo:15.
myDatum bar:3.14159.
...
Remember, that the #memberAt:
message extracts a field,
which results in an expensive copy, if the field is a structure, union
or array.
You can create a CPointer (which points into another CDatum) with:
refMemberAt:
fieldNameAt any time, a CDatums byteOrder can be changed/queried via the
msb
msb:
aBoolean
cDatum msb:true
All followup accesses will assume bigEndian data.
You should always set the byteOrder when communicating with external processes/machines (i.e. do not depend upon the default, because it is not the same on all ST/X implementations)
receiver:
sender:
|buffer socket datum foo bar|
...
buffer := ByteArray new:1024.
...
socket readWait.
socket nextAvailableInto:buffer.
...
datum := myStructType onBytes:buffer.
datum msb:true.
...
foo := datum foo.
bar := datum bar.
...
The following code fragment uses a helper method to initialize the
fields of a structure and a CPointer is passed to it to fill a
substructure.
|buffer socket datum foo bar|
...
buffer := ByteArray new:1024.
...
datum := myStructType onBytes:buffer.
datum msb:true.
...
datum foo:123.
datum bar:1.2345.
...
socket nextPutBytes:datum sizeof from:datum data.
...
The corresponding C-header definitions are:
the smalltalk code is:
#define NUM_CHARS 10
typedef struct foo {
int foo1;
float foo2;
};
typedef struct bar {
foo innerFoo;
int bar1;
char bar2[NUM_CHARS];
};
Notice, that passing the result of
initFoo:aFoo
aFoo foo1:10.
aFoo foo2:(Float pi).
^ self
...
cTypes := cparser types.
cDefines := cparser defines.
...
fooType := cTypes at:'foo'.
barType := cTypes at:'bar'.
...
aBar := barType new.
...
self initFoo:(aBar refMemberAt:'innerFoo');
...
NUM_CHARS := Integer fromString:(defines at:'NUM_CHARS').
...
#memberAt:
to the
initFoo
method would
not work in the above example, since that would pass a copy of the inner structure and
leave the original (outer) datum unchanged.
Copyright © 1999 eXept Software AG
<info@exept.de>