[comp.lang.eiffel] Use of the INDIRECT class

rick@tetrauk.UUCP (Rick Jones) (11/30/90)

Here is another suggestion/question.

The project I am working on involves interfacing to databases and other
external subsystems using C libraries.  Calling C functions from Eiffel gives
no problems, but what is sometimes required is to attach to an Eiffel object
an instance of some C structure for use by the C routines.  I am doing this in
several places, an example being a message buffer managed by an Eiffel class
where each object is an instance of a message.

The first approach was to use standard malloc() to allocate space, and keep
track of the address in Eiffel as an integer attribute.  The problem comes with
deallocation and garbage collection.  It can of course be done with a dispose
routine which calls free() to get rid of the space, but given the curent state
of dispose (suspect), this is not very reliable.  It also means that
duplication of the object when required gets complex.

After digging around in the STRING and ARRAY classes, I finally worked out (I
think!) how the INDIRECT class is used.  This enables a "special object" to be
allocated, which is managed by the Eiffel object heap manager and thus subject
to normal garbage collection, but whose contents are otherwise arbitrary.  With
this method, the special object can be used to hold any C structure, and its
address can be passed to C functions when required.

The following is some example code of how to do this, which may be useful to
anyone else.  I would also like some comment from ISE on this.  First, have I
got the mechanism right (it seems to work), or are there any pitfalls,
especially relating to GC, that I've overlooked?  Second, how permanent and
supported a fixture is the INDIRECT class?  Can this technique be considered
portable and upward compatible?


Use of INDIRECT

1.	A class which needs to hold a special object must inherit from
	INDIRECT.  This provides an attribute "area" which is the reference to
	the special object, and a routine "allocate" which creates a special
	object of a specified size and sets "area" to refer to it.  If you
	don't want to call the special object "area" you can of course rename
	it.

2.	The INDIRECT class is generic.  The actual generic parameter given on
	inheritance should be any basic type, e.g. INTEGER.  It doesn't make
	any difference to the operation what basic type is used (except
	probably DOUBLE).  You must NOT use a class type, though, otherwise the
	garbage collector will think that the special object contains a list of
	object references and will inevitably crash the program.

3.	The "allocate" routine takes as its argument the required size of the
	special object in Eiffel WORDS (not bytes).  I find it best to use a C
	function to calculate this size and return it as an integer (example
	below).

4.	When "area" is passed as a parameter to a C function, the value is the
	address of the object header, not the usable allocated space.  The C
	macro "Access", defined in _eiffel.h, will give the address of the
	actual usable space.  It is important that the C code does not corrupt
	the object header, nor of course write beyond the end of the allocated
	area.

5.	The deep_clone feature can be used on an object which contains a
	reference to a special object, and will result in the special object
	being duplicated as part of the object hierarchy.


Example:

class SP_CLASS		-- a class demonstrating special objects

export

inherit
	INDIRECT [INTEGER]

feature
	space: INTEGER ;	-- amount of space allocated
				-- (this may not need to be held explicitly)
	Create (param: INTEGER) is
	external
		get_space (param: INTEGER): INTEGER language "C" ;
		fill_space (area: like area, space: INTEGER): INTEGER
			language "C" ;
	do
		space := get_space (param) ;
		allocate (space) ;
		if fill_space (area, space) < 0 then
			-- fill_space failed
		end ;
	end ;

	do_something is
		-- routine supported by C function using the special object
	external
		do_space (area: like area, space: INTEGER): INTEGER
			language "C" ;
	do
		if do_space (area, space) < 0 then
			-- function failed
		end ;
	end ;

	...
end

This example represents a scheme where the allocated space is variable,
depending on a parameter passed to Create.  If the space is constant (e.g. to
hold a single C structure), then the space attribute is not needed, nor are the
uses of "param" in the example.


Supporting C code: ( brain.engage (alternate_syntax) :-)

#include <_eiffel.h>

/*	this macro converts a size in bytes (resulting from sizeof, etc)
	to the equivalent space in Eiffel words, rounding up
*/
#define Esize(bytes)	((bytes + sizeof(DATUM) - 1) / sizeof(DATUM))

int
get_space (param)
int param ;
{
	return Esize (function_which_returns_bytes (param)) ;
	/*
		for structures, use:
		return Esize (sizeof (struct (struct_tag))) ;
	*/
}

int
fill_space (area, space)
OBJPTR area ;
int space ;
{
	/* get the address of the data space in area,
	   casting to appropriate type, where MY_TYPE is a typedef
	*/
	MY_TYPE *my_area = (MY_TYPE *)(Access(area)) ;
	/* size in bytes, converted from space in DATUMs */
	int size = space * sizeof(DATUM) ;

	if (fill_function (my_area, size) <succeeds> )
		return 0 ;
	else
		return -1 ;
}


int
do_space (area, space)
OBJPTR area ;
int space ;
{
	/* get the address of the data space in area,
	   casting to appropriate type, where MY_TYPE is a typedef
	*/
	MY_TYPE *my_area = (MY_TYPE *)(Access(area)) ;
	/* size in bytes, converted from space in DATUMs */
	int size = space * sizeof(DATUM) ;

	< do whatever with my_area >

	return <success>? 0 : -1 ;
}


Note that all C functions which manipulate the special object will be passed
the address of area, and so will need the same first line as in fill_space()
and do_space() above.  Also if space is variable, the conversion to bytes will
probably be needed since the byte is usually the basic unit of size in C.
After that it's up to you!

This code should be pretty portable, and makes no assumtions about the actual
size of DATUM, although on current implementations it seems to be the same as a
long int.  See the accompanying posting on equivalence between Eiffel and C
datatypes.

-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Was it something important?  Maybe not
Berks, UK			What was it you wanted?  Tell me again I forgot
rick@tetrauk.uucp					-- Bob Dylan