Object-oriented programming in C
braner
braner at batcomputer.TN.CORNELL.EDU
Thu Oct 16 13:11:23 AEST 1986
[]
Here is my method for object-oriented programming in C.
Please direct comments to <braner at 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]));
}
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
More information about the Comp.lang.c
mailing list