Translating Object Representations#
Whenever a native C object is returned from a function or
a Dylan object is passed into a C function, it is necessary to
translate between the object representations used by the two
languages. From MelangeUs standpoint, native C objects consist
of an arbitrary bit pattern which can be translated to or from a
small number of “low level” Dylan types – namely
<integer>
, <float>
, or any subclass of
<statically-typed-pointer>
. This translation is handled
automatically, although the user may explicitly specify which of
the possible Dylan types should be chosen for any given C object
type. In some cases, a further translation may take place,
converting the “low level” Dylan value to or from some arbitrary
“high level” Dylan type. (For example, an <integer>
might
be translated into a <boolean>
or a <character>
, and
a <c-string>
might be translated into a
<byte-string>
.) These “high level” translations are
automatically invoked at the appropriate times, but both the
“target” types and the methods for performing the translation
must be specified by the user.
Specifying low level transformations#
The target Dylan type for “low level” translations is
typically chosen automatically by Melange. Integer and
enumeration types are translated into <integer>
;
floating point types are translated to <float>
; and all
other types are translated into newly created subclasses of
<statically-typed-pointer>
. However, you may explicitly
declare the target Dylan type for any C type by means of an
equate:
option:
define interface
#include "gc.h",
equate: {"char *" => <c-string>};
end interface;
This declaration makes the very strong statement that
any values declared in C as char *
are identical in form to
the predefined type <c-string>
(which is described in
Appendix I). The system will therefore not define a distinct
type for char *
and will ignore any structural information
provided in the header file. You might also use an equate:
option to equate a type mentioned in one interface definition
with an identically named type which was defined in an earlier
interface definition.
You should use caution when equating two types. Since Melange has no way of knowing when two types are equivalent, it must trust your declarations. No type checking can or will be done, so if you incorrectly equate two types, the results will be unpredictable. In some cases, you may wish to go with the less efficient but slightly safer technique of letting Melange create a new type and then “mapping” that new type into the desired type. (This is described in detail below.)
Note also that two types with identical purposes will
not necessarily have identical representations. For example,
C’s boolean types are simple integers and are not equivalent
to Dylan’s <boolean>
. Again, explicit “mapping” may be
used to transform between these two representations.
In the current implementation, an equate:
option only
applies within a single interface definition. Other interface
definitions will not automatically inherit the effects of the
declaration. In future versions, we may add the ability to
“use” other interface definitions (just as you would “use”
another module within a module definition) and thus pick up
the effects of the equate:
(and map:
) options within those
interfaces.
Specifying high level transformations#
Sometimes you may wish to use instances of some C type
as if they were instances of some existing Dylan class, even
though they have different representations. In this case, you
can specify a secondary translation phase which
semi-automatically translates between a “low level” and a
“high level” Dylan representation. In order to do this, you
must provide a map:
option:
define interface
#include "gc.h",
equate: {"char *" => <c-string>},
map: {"bool" => <boolean>};
end interface;
This clause will cause any functions defined within the
interface to call transformation functions wherever the
original C functions accept or return values of type
bool
. Two different functions may be called:
import-value (high-level-class :: <class>, low-level-value :: <object>)
This function is called to transform result values returned by C functions into a “high level” Dylan class. It should always return an instance of “high-level-class”.
export-value (lowlevel-class :: <class>, high-level-value :: <object>)
This function is called to transform “high level” argument values passed to C functions into the “low level” representations which will be meaningful to native C code. It should always return an instance of “low-level-class”.
Default methods, which simply call as
, are provided
for each of these functions. This will be sufficient to
transform C’s integral char
into <character>
,
<c-string>
into other <string>
, or one “pointer”
type into another. There is also a predefined method which
will transform <integer>
into
<boolean>
. However, if you wish to perform arbitrary
transformations upon the values, you may need to define
additional methods for either or both of these functions. For
example, the default methods for transforming to and from
<boolean>
are:
define method export-value (cls == <integer>, value :: <boolean>)
=> (result :: <integer>);
if (value) 1 else 0 end if;
end method export-value;
define method import-value (cls == <boolean>, value :: <integer>)
=> (result :: <boolean>);
value ~= 0;
end method import-value;
It is important to note that, unlike equate:
options,
map:
options don’t prevent Melange from creating new
types. You may, in fact, both equate and map the same
type. This will cause low level values to be created as
instances of the “equated” type and then transformed into
instances of the “target” type of the mapping. For example,
you might take advantage of the defined transformations
between string types by declaring:
define interface
#include "/usr/include/sys/dirent.h",
equate: {"char *" => <c-string>},
map: {"char *" => <byte-string>};
end interface;
This causes the system to automatically translate char *
pointers into <c-string>
(i.e. a particular variety
of statically typed pointer) and then to call import-value
to translate the <c-string>
into a
<byte-string>
. If we did not provide the equate:
option, then we would have to explicitly provide a function to
transform “pointers to characters” into
<byte-string>
. The equate:
option lets us take
advantage of all of the predefined functions for
<string>
, which includes transformation into other
string types.