[net.micro.atari16] Object-oriented programming in C

braner@batcomputer.TN.CORNELL.EDU (braner) (10/16/86)

[]

Here is my method for object-oriented programming in C.
Please direct comments to <braner@amvax.tn.cornell.edu>
or to the address above.

- Moshe Braner

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	Framework for object-oriented programming in C.
	===============================================

	Copyright (C) 1986 by Moshe Braner.

	Permission is here granted for noncommercial use.  The selling
	of this document or the code included in it, or parts of either,
	in printed or machine-readable form, is expressedly prohibited,
	unless given written permission from the author.


	Objects are allocated memory at run time. The structure of an
	object (an instance of a class):

		A pointer to the object's class.

		In case the object inherits any properties from a
		superclass, a pointer to the object's "superinstance"
		(just another object, an instance of the superclass).

		In addition, the object may hold instance variables.

	Class definitions are set up at compile time.  The structure
	of a class definition includes:

		A "typedef struct" describing the objects in the
		class (always as above).

		A #define giving a symbolic name to each method-
		token.  This may be put in the central header file of
		the application, for easy cross-reference, or may be
		left next to the class definition.  The token
		values (positive integers) need not be unique across
		branches of the tree of classes, but they have to be
		increasing along the inheritance pathways.

		The method functions, which manipulate the objects of
		this class in the noninherited ways.  The names of
		these functions should not be repeated in the same
		source file.  A method function is always of type
		"int", and usually returns error codes, but may
		return other information.  It always receives a
		pointer to the object being manipulated ("receiving
		the message"), as the first argument, possibly
		followed by others.  Initialization and clean-up
		methods, if defined, must return ERROR (-1) if errors
		were encountered, OK (0) otherwise.  To reduce the
		verbosity of the method definitions, three macros are
		defined in the file objects.h:  DefMethod, ArgMethod,
		and ENDOK.

		An array of METHODs (initialized at compile time),
		each of which contains a token (actually an integer)
		and a pointer to the actual function.  (A typedef
		struct of the METHOD element is also in the file
		objects.h.)  The last entry in this table has to be
		{NULL, NULL}.  (To reduce the verbosity of this
		declaration, the macros MTable and ENDMT are in
		objects.h.)  At run time this array is searched
		sequentially, so the methods to be called more often
		should be placed at the beginning of the array.
		Methods may override superclass methods,
		intentionally or not, simply by taking on the same
		tokens, or the same token values.  (Initialization
		and clean-up methods, if defined, must be included in
		this table, are always given the same tokens as any
		similar functions belonging to any other class:
		OBINIT and OBCLEAN (#defined in objects.h).  This
		does NOT mask the superclass' counterpart because
		Create() and Destroy() also call the superclass
		methods directly.)

		An actual "CLASS" structure holding a pointer to the
		methods table, a pointer to the superclass, the size
		of an object of this class, and flag bits indicating
		whether the objects in this class need to be
		initialized when created (CFINIT), and whether they
		have a clean-up method to be called before destroying
		them (CFCLEAN).  (A typedef struct of this construct
		is in the header file objects.h.)

	There are three generic object-manipulating functions, defined
	in the file objects.c:

		Create(class-name) (#defined into cCreate(class-
		address) creates a new instance of the class, and
		returns a pointer to the new object (or NULL if
		allocation failed).  Recursively creates the
		super-instances, automatically.

		Destroy(object-pointer) frees up the memory in an
		object.  Recursively destroys all the super-
		instances, automatically.  Returns OK or ERROR.

		The call to a method ("sending a message"):
		    Do(method-token, object-pointer));
		or
		    Do(method-token, object-pointer),arguments);
		(note the unusual syntax with unbalanced parens!).
		This is actually #defined into the form:
		    (*FindMethod(token, object))(object,arguments);
		The generic function FindMethod() searches the method
		list, pointed to by the object, for the method token.
		If not found, the superinstances are searched in
		reverse order of inheritance.  FindMethod returns a
		pointer to the method, which is promptly used to call
		the method itself.  Since FindMethod() is called very
		often, it might be optimized in assembler language,
		although my tests (MC68000) show little improvement.

	More complicated data structures (such as arrays or lists of
	objects) are not part of this basic system but may be easily
	added by defining new classes of objects.  For example, an
	"array of data objects" may be an object holding the
	dimension of the array and an array of pointers, and would
	have an auto-initialization method that creates the "data
	objects", and an auto-clean-up method that destroys them.  A
	"linked list of objects" may be constructed using "ListNode"
	objects (just pointers to an object and the next node), and
	"List" objects that only hold a pointer to the first node,
	but have access to methods for adding nodes to the list,
	removing them, etc.



	Here is the actual C code:
	--------------------------

	/* File: Object.h   - by Moshe Braner, 860813 */
	/* --------------   ------------------------- */

	/*
	 * The basic syntax aid to calling a method:
	 */
	extern int (*FindMethod())();
	#define Do(m,o) (*FindMethod(m,o))(o


	/*
	 * Kludge to access the elements of as-yet
	 *   undefined structures:
	 */
	#define	Obclass(obj)	obj[0]
	#define	Superob(obj)	obj[1]


	/* tokens */

	#define	OK		0
	#define ERROR		(-1)
	#define	OBINIT		0x7FFE
	#define	OBCLEAN		0x7FFF

	#define	CFINIT		0x01
	#define	CFCLEAN		0x02


	/* macros */

	#define DefMethod(name,ptr,type)   int name(ptr) type *ptr; {
	#define ArgMethod(nm,p,t,a)        int nm(p,a) t *p; int a; {
	#define ENDOK		return(OK); }
	#define MTable(name)	METHOD name[] = {
	#define ENDMT		NULL, NULL };

	#define Create(c)	cCreate(&c)


	/*** Structures ***/

	typedef struct METHOD {
		int	method_token;
		int	(*method_function)();
	} METHOD;

	typedef struct CLASS {
		struct METHOD	*class_methods;
		struct CLASS	*super_class;
		int		obj_size;
		int		class_flags;
	} CLASS;

	#define	Obinit(c)	((c)->class_flags&0x01)
	#define	Obclean(c)	((c)->class_flags&0x02)



	/* File: Object.h   - by Moshe Braner, 860813 */
	/* --------------   ------------------------- */

	/*** Generic functions ***/

	char **				/* alias for object pointer */
	cCreate(clp)
		register CLASS	*clp;
	{
		register char	**objptr;	/* object pointer */
		register char	**supptr;	/* super-inst ptr */

		/*
		 * Allocate memory for this instance:
		 */
		objptr = malloc(clp->obj_size);
		if (objptr == NULL) {
	#ifdef DEBUG
			fprintf(stderr, "No memory for object\n");
			exit(0);
	#else
			return (NULL);
	#endif
		}
		Obclass(objptr) = clp;

		/*
		 * Recursively create super-instances:
		 */
		if (clp->super_class != NULL) {
			supptr = cCreate(clp->super_class);
			if (supptr == NULL) {
	#ifdef DEBUG
				fprintf(stderr, "No memory for object\n");
				exit(0);
	#else
				free (objptr);
				return (NULL);
	#endif
			}
			Superob(objptr) = supptr;
		}

		/*
		 * Initialize the object:
		 */
		if (Obinit(clp))
			if (Do(OBINIT, objptr)) != OK) {
	#ifdef DEBUG
				fputs("Obj init failed\n",stderr);
				exit(0);
	#else
				free(objptr);
				return (NULL);
	#endif
		}

		return (objptr);
	}

	int
	Destroy(obp)
		register char	**obp;		/* object pointer */
	{
		register CLASS	*clp;

		clp =  (CLASS *) Obclass(obp);

		/*
		 * Clean up the object:
		 */
		if (Obclean(clp))
			if (Do(OBCLEAN, obp)) != OK) {
	#ifdef DEBUG
				fputs("Obj cleanup failed\n",stderr);
				exit(0);
	#else
				return (ERROR);
	#endif
			}

		/*
		 * Recursively destroy super-instances:
		 */
		if (clp->super_class != NULL)
			if (Destroy(Superob(obp)) != OK)
				return (ERROR);

		/*
		 * Free up memory:
		 */
		free(obp);

		return (OK);
	}

	int (*FindMethod())(token, obp)
		register int	token;	  /* of method to find */
		register char	**obp;	  /* pointer to object */
	{
		register METHOD	*methods;
		register int	c;
		register CLASS	*clp;

		for (;;) {	/* until recursion broken */

			clp = (CLASS *) Obclass(obp);
			methods = clp->class_methods;
			while ((c=methods->method_token) != 0) {
				if (c >  token)
					break;		/* inherited */
				if (c == token)
					return (methods->method_function);
				methods++;
			}

			if (clp->super_class == NULL) {
	#ifdef DEBUG
				fprintf("Method %d not found\n",token);
				exit(0);
	#else
				return (NULL);
	#endif
			}

		/*
		 * Method not here, look at inherited
		 *   methods recursively:
		 */
			obp = Superob(obp);

		}			/* end of for(;;): recurse */
	}



	Here is an example program using these ideas:
	---------------------------------------------

	/*
		Queue Simulation.
		By Moshe Braner,  860813.

		- a C version of the FORTH program by
		Dick Pountain, BYTE, August 1986.
	*/

	#include <stdio.h>

	/* #define DEBUG */

	#include "objects.h"
	#include "objects.c"

	int
	random(modulus)		/* return a random between 0 & modulus-1 */
	{
		return (((rand()&0x7FFF)>>2) % modulus);
	}

	#define	TRUE	1
	#define FALSE	0

	#define	TDELAY	10
	#define SERVT	5
	#define	CDELAY	2
	#define BSIZE	10


	/* global variables */

	int	clock;
	int	next_cust;
	int	last_cust;


	/* Definition of class Customer: */

	typedef struct	CUSTOMER
	{
		CLASS		*cus_clp;
		int		trans_time;
		int		entry_time;
		int		cus_done;
	} CUSTOMER;

	#define JOIN		1
	#define TRANSACT	2
	#define DONECHK		3

	DefMethod(cust_join, cust, CUSTOMER)
		cust->entry_time = clock;
		cust->trans_time = TDELAY + random(SERVT);
		cust->cus_done   = FALSE;
	ENDOK

	DefMethod(cust_trans, cust, CUSTOMER)
		if (--(cust->trans_time) == 0)
			cust->cus_done = TRUE;
	ENDOK

	DefMethod(cust_done, cust, CUSTOMER)
		return (cust->cus_done);
	}					/* does NOT return OK */

	MTable(cust_methods)
		JOIN,		&cust_join,
		TRANSACT,	&cust_trans,
		DONECHK,	&cust_done,
	ENDMT

	CLASS Customer =
	{
		cust_methods,
		NULL,			/* no superclass	*/
		sizeof(CUSTOMER),
		NULL			/* no init/cleanup	*/
	};


	/* Definition of class Cashq: */

	extern CLASS Customer;

	#define MAXQ	20

	typedef struct CASHQ
	{
		CLASS		*cq_clp;
		int		cq_head;
		int		cq_len;
		int		cq_chg;		/* len has changed   */
		struct CUSTOMER	*qbody[MAXQ];	/* could be char *   */
	} CASHQ;

	#define cq_full(cqp)	(cqp->cq_len == MAXQ)
	#define cq_empty(cqp)	(cqp->cq_len == 0)

	#define DQ	4
	#define NQ	5
	#define SERVE	6
	#define SHOWQ	7

	DefMethod(cq_dq, cqp, CASHQ)
		if (! cq_empty(cqp)) {
			cqp->cq_head = (cqp->cq_head+1) % MAXQ;
			cqp->cq_len--;
			cqp->cq_chg = TRUE;
		}
	ENDOK

	DefMethod(cq_nq, cqp, CASHQ)
		int	tail;
		if (! cq_full(cqp)) {
			tail = (cqp->cq_head + cqp->cq_len) % MAXQ;
			if (Do(JOIN, cqp->qbody[tail])) != OK)
				return (ERROR);
			cqp->cq_len++;
			cqp->cq_chg = TRUE;
		}
	ENDOK

	DefMethod(cq_serve, cqp, CASHQ)
		int		head;
		CUSTOMER	*cust;
		head = cqp->cq_head;
		cust = cqp->qbody[head];
		if (Do(TRANSACT, cust)) != OK)
			return (ERROR);
		if (Do(DONECHK, cust)))
			return (Do(DQ, cqp)));	  /* or cq_dq(cqp) */
	ENDOK

	DefMethod(cq_showq, cqp, CASHQ)
		int	i;
		if (cqp->cq_chg) {
			for (i=0; i<cqp->cq_len; i++)
				putchar('#');
			putchar(0x1B);
			putchar('K');		/* erase to eol */
			cqp->cq_chg = FALSE;
		}
		putchar('\n');
	ENDOK

	DefMethod(cq_init, cqp, CASHQ)
		int	i;
		cqp->cq_head = 0;
		cqp->cq_len  = 0;
		cqp->cq_chg  = TRUE;
		for (i=0; i<MAXQ; i++) {
			if ((cqp->qbody[i]=Create(Customer)) == NULL)
				return (ERROR);
		}
	ENDOK

	MTable(cq_methods)
		DQ,	&cq_dq,
		NQ,	&cq_nq,
		SERVE,	&cq_serve,
		SHOWQ,	&cq_showq,
		OBINIT,	&cq_init,
	ENDMT

	CLASS Cashq = {cq_methods, NULL, sizeof(CASHQ), CFINIT};


	/* main program */

	extern CLASS Cashq;

	main()
	{
		char	*cashier[BSIZE];
		int	i, j;

		for (i=0; i<BSIZE; i++) {
			if ((cashier[i]=Create(Cashq)) == NULL)
				exit(0);
		}
		clock     = 0;
		last_cust = 0;
		srand(513);			/* seed for rand() */
		next_cust = random(CDELAY);
		putchar(0x1B);
		putchar('E');			/* clear, home */
		putchar(0x1B);
		putchar('f');			/* cursor invisible */

		for (;;) {			/* stop on ^C only */
			++clock;
			if (clock - last_cust > next_cust) {
				j = random(BSIZE);
				Do(NQ, cashier[j]));	/* ignore errors */
				last_cust = clock;
				next_cust = random(CDELAY);
			}
			for (i=0; i<30000; i++);	/* short delay */
			putchar(0x1B);
			putchar('H');			/* cursor home */
			for (i=0; i<BSIZE; i++) {
				Do(SERVE, cashier[i]));
				Do(SHOWQ, cashier[i]));
			}
		}
	}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~