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])); } } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~