True OOP using plain C
Michiel Steltman
mist at wmt.UUCP
Mon Feb 5 21:40:55 AEST 1990
A friend of mine manages the R&D department of a company that
develops software for editorial systems. The have a lot of expertise in
distributed text databases, graphical issues such as layouts, page makeup,
fonts, and low level stuff such as communications, bitblitters,
graphics hardware, embedded software, etc.
They developed a package called CO2, with which they developed their
entire (very sophisticated) newspaper page layout system.
To my opinion this CO2 is a very interesting and refreshing approach: by
adding a sophisticated run-time binding facility to plain C, everybody can
use true OO techniques today: data hiding, polymorphism, inheritance!
Because I am a fanatic reader of the groups languages.C++ and comp.object, I
asked him to compile an article about this CO2.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
OOPS! by Peter Kriens
C++ is taking off like a rocket. Many users are now starting
to understand the advantages of object oriented programming.
Within this world, C++ is surely the mainstream.
Unfortunately, the greatest benefits of C++ are in the
smaller examples. Some basic limitations of the language
make it unsuitable for larger projects. This was confirmed
by the makers of Pagemaker (Aldus) and members of a large
programming project of AT&T on the last OOPSLA in New
Orleans. We realize that this is not a very popular view
right now.
C++ has some other, minor drawbacks such as: too easy to use normal C,
no real polymorphism, mostly preprocessor based and hard to
use inheritance. But the basic limitation of the language is
the fact that it violates the first law of OOPS, information
hiding. Though it is true that information is hidden for
the programmer, it certaily isn't from the compiler.
in C++ A class definition is usually in an include file. This file
contains the internals of that class and the compiler needs
ALL this information. The result of this is that if an implementation
of that class changes all dependents MUST be recompiled. In
large projects this results in massive amounts of include
files and the project size increases compile time of
separate modules. The larger the project, the more include
files, the longer it takes to compile 1 module. Together
with the fact that source dependence is extremely big means
that we have come back to generation cycles of hours or even
days. Be sure to recognize that this time is exponentially
dependent on project size:
Tgeneration = c * Tcompile * Dependence * Modules
Tgeneration = time to generate a system
Tcompile = compile time of one module, ++ in C++
Dependence = value of 0..1 propability that a module must be
recompiled when something is changed ++++ in C++
Modules = # of modules in a project
Objective C solves some of the problems but has the drawback
that it is not available on all computers and needs special
pre-processing. When we realized these problems 3 years ago
we at Media systemen looked for another solution.
We found a way we call CO2, C object oriented.
Co2 does not need a preprocessor and it fully
supports polymorphism, complete information hiding,
inheritance and it is fast. It actually supports the full
set of Smalltalk classes, except garbage collection. One of the main
advantages is the fact that it works with ANY C compiler.
How is this CO2 used? It is very easy to use Co2, it is just
like normal C and it consists of standard libraries with
methods and a program called SYNTOR. A message send is a
procedure call:
#include "co2.h"
extern object new(object);
extern void atPut(object, object, object );
main()
{
object aDictionary;
aDictionary = new(Dictionary);
atSPut( aDictionary, "Peter", as(String,"31-23-251492"));
printf( dataOf( atS( aDictionary, "Peter" ) ) );
}
This results in the fact that all messages 'new' end up in the
same procedure. Syntor generates a C file that contains all
these procedures and dispatches the message to the correct
method. A method is very easy to write, the only speciality
is that an instance method should start with om_ and a
factory method with of_, followed by the class and message
name. The dictionary implementation of atPut is thus:
om_Dictionary_atPut.
In CO2 an object is an unsigned. This means that all access
is done through an object table. The advantage of this is
that debugging becomes an extremely easy to do task. Illegal
objects are flagged as soon as they are used. This in
contrast to pointers, as in C++, where the illegal use is not noticed
until the abused memory is used again. The use of pointers
dramatically increases the change of erroneous use.
The best part of Co2 is the fact the it makes the dream of
Cox come true. It is possible to deliver software IC's
without too much overhead. A supplier has only to provide a
library, a header file containing the messages and a syntor
definition file. Even if the implementation of a class
changes, the header file will remain the same because it does
not contain any information about how the class works, it just
specifies the interface. This minimizes the size.
The Class definition file contains information about instance
variables, methods, which messages are referred by the method
and startup information. All definition files of all classes
are read by Syntor. Syntor can then design the minimum
configuration and generates a number of C files that should
be linked, NOT included. It also can optionally supply an
class include file for the class modules itself.
Dynamic binding sometimes is considered as a big problem because
it is a balance between speed and memory usage. In CO2 we have found a very
nice optimimum by using packed tries. Finding the right
method is two indexes in an array and a comparison while the
trie size remains small ( less then 6000 entries of 4 bytes
for 130 classes, with 2000 methods and 1300 messages ).
In this way the runtime binding mechanism is exetremely fast.
To port CO2 to another environment it is necessary to
recompile syntor and the base classes, and to write an assembly
routine for the dispatching. In Turbo C this is 5 lines of assembly.
As a real life example I will include a listing of a program
that lists login periods of users from the "wtmp" file on a
Sun. It is a basic problem that is decsribed in Jacksons
book: "Principles of Program design". It shows the
advantages of using dictionaries, sets and special classes.
It consists of three parts:
acc.c contains sources of main program
date.c sources of date class
account.s syntor definition of this program
To run this program you need to have the syntor program and
the libraries. At the end of this article there is information
regarding the obtainability of this. It is not is shar, since it is
just an example.
======================== file: ACC.C
/*
MEDIASYSTEMEN (c) 1990
This is file is copyrighted. It is supposed to be
used to get information about the CO2 OOPS environment
please refer to:
Mediasystemen
Postbox 4932
2003 EX Haarlem
tel 023-319075
tel intl: (+31) 23 319075
Any questions concerning CO2 should be directed to Peter
Kriens, reachable on pkriens at media01.uucp.
-CO2-
This is an example CO2 demonstration file. It shows how
CO2 is used to implement a not completely trivial problem
please refer also to the source of the time class which
implements a time function.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "utmp.h"
#include "lastlog.h"
#include "compiler.h"
#include "co2.h"
#include "kernel.h"
#include "collect.h"
#include "date.h"
/**/
/*
-ERRORHANDLER-
This procedure is called when CO2 detects an error. Error
numbers are defined in error.h
The name of this procedure is installable via a command in
syntor.
This procedure is allowed to return but should return a
oj_NULL object.
*/
object errorHandler(obj, num)
/* ---------------------------- */
/* Object the generated the err */ object obj;
/* Error number (error.h) */ int num;
/* ---------------------------- */{
/* Just print it and abort */ printf("CO2 error object #%d error #%d\n",
/* */ obj, num);
/* Do not handle errors */ exit(1);
/* */ return oj_NULL;
/* ---------------------------- */}
/**/
/*
-MAIN-
Read the wtmp file and display for each user the login time
and time he/she/it was online.
*/
void main(int argc, char *argv[], char * envp[])
/* ---------------------------- */{
/* User r (utmp.h) */ struct utmp r;
/* wtmp file */ FILE *f;
/* Line (cq tty0) information */ object line, lines;
/* login/logout time */ object inlogTime, exitTime;
/* user information (cq pkriens)*/ object user, users, sortedUsers;
/* login memory */ object logins;
/* elapsed time */ unsigned elapse;
/* ---------------------------- */
/* Initialize CO2 */ oj_init(argc, argv, envp);
/* (change filename for unix) */ f = fopen( "wtmp", "rb");
/* File found? */ if (f == NULL)
/* Show error */ {perror("wtmp open error: "); exit(1);}
/* */
/* links user name to line name */ users = new(Dictio);
/* links line to inlog time */ lines = new(Dictio);
/* contains all users that login*/ logins= new(Bag);
/* is used for statistics */
/* Repeat for each input r */ while ( fread(&r,sizeof(r),1,f) == 1)
/* */ {
/* Check for valid record */ if (r.ut_name[0] == '\0')
/* */ continue;
/* */
/* unique creates symbols */ line = unique( String, r.ut_line );
/* a symbol is always one object*/
/* even if it is created twice */
/* This line already logged in? */ inlogTime = at(lines,line);
/* */ if (inlogTime == oj_NULL)
/* user already logged in? */ {
/* NO, create new time object */ inlogTime = asObject(Date,(void*)&r.ut_time);
/* Link the line to the inlog t */ atPut(lines, line, inlogTime );
/* */
/* Create symbol for user */ user = unique(String, r.ut_name );
/* Connect line to user name */ atPut( users, line, user );
/* Remember # of logins */ add(logins, user);
/* */ }
/* User already logged in? */ else
/* */ {
/* which user was on this line? */ user = at(users, line);
/* */ exitTime= asObject(Date, (void*)&r.ut_time);
/* How much time elapsed */ elapse = (unsigned) elapsed(exitTime, inlogTime) / 60;
/* */
/* Show information */ printf(" %-8s %2d %2d:%-2d %s",
/* Return strng pointer */ dataOf(user),
/* days */ elapse / (24*60),
/* hours */ (elapse / 60) % 24,
/* minutes */ elapse % 60,
/* login time as string */ asCString(inlogTime));
/* */
/* this line is logged out */ removeKey( lines, line);
/* Remove unused objects */ dispose(inlogTime);
/* */ dispose(exitTime);
/* done for logged in user */ }
/* done for a record */ }
/* reuse user dictionary */ dispose(users);
/* create a new Set */ users = new(Set);
/* from BAG -> SET remove */ addAll(users, logins);
/* duplicates! */
/* Sort the list */ sortedUsers = new(Sorted);
/* */ addAll(sortedUsers, users );
/* */
/* For each user we walk */ FORALL( sortedUsers, user)
/* print user information */ printf("%-8s %5d\n",
/* string pointer */ dataOf(user),
/* # of times logged in */ occurencesOf(logins, user));
/* end all users */ ENDALL
/* */
/* close input file */ fclose(f);
/* normal return */ exit(0);
/* ---------------------------- */}
============================== file: DATE.C
/**/
/*
-DATE-
This is an implementation of a generic Date class. This
class maintains the date through the number of seconds
from a starting date.
This file demonstrates a simple class and how it
is constructed.
*/
#include <stdlib.h>
#include <time.h>
#include "co2.h"
#include "kernel.h"
#include "date.h"
/**/
/*
-NOW-
Set Date to current Date.
*/
void om_Date_now( self )
/* ---------------------------- */
/* */ object self;
/* ---------------------------- */{
/* C Date */ time_t t;
/* ---------------------------- */
/* Get current Date */ time(&t);
/* place in object */ setData(self,(void*)t);
/* ---------------------------- */}
/**/
/*
-GET/SET-
Set the Date from seconds (Date_t type)
*/
void om_Date_set(self, t)
/* ---------------------------- */
/* */ time_t t;
/* */ object self;
/* ---------------------------- */{
/* */ setData( self, (void*)t );
/* ---------------------------- */}
time_t om_Date_get(self)
/* ---------------------------- */
/* */ object self;
/* ---------------------------- */{
/* */ return (time_t) getData( self);
/* ---------------------------- */}
/**/
/*
-ELAPSED-
Return the number of seconds elapsed between
two date objects.
*/
time_t om_Date_elapsed( self, other )
/* ---------------------------- */
/* */ object self, other;
/* ---------------------------- */{
/* Return difference */ return get(self) - get(other);
/* ---------------------------- */}
/**/
/*
-ASCSTRING-
Return the date as a string
*/
char * om_Date_asCString( self )
/* ---------------------------- */
/* */ object self;
/* ---------------------------- */{
/* */ time_t t;
/* ---------------------------- */
/* Get time */ t = get( self );
/* and return ascii string */ return ctime(&t);
/* ---------------------------- */}
/**/
/*
-ASOBJECT-
Return a new object instantiated from a time
This is a factory method.
*/
object of_Date_asObject(self, t)
/* ---------------------------- */
/* */ object self;
/* */ time_t *t;
/* ---------------------------- */{
/* */ object temp;
/* ---------------------------- */
/* Create new object */ temp = new(self);
/* set this time */ set(temp, *t );
/* and return the new date obj. */ return temp;
/* ---------------------------- */}
============ file: ACCOUNT.S, syntor definition file
#include "kernel.s"
#include "collect.s"
Object :: Date
{
data{};
message set; message get;
message asCString; factory message asObject(set);
message now(get); message elapsed(get);
};
class String; class Bag; class Set; class Dictio; class Date;
message add; message addAll; message asObject;
message at; message atPut; message atS;
message dataOf; message dispose; message includes;
message includesKey; message occurencesOf; message removeKey;
message new; message newSize; message next;
message removeIt; message unique; message set;
message get; message asCString; message now;
message elapsed;
exceptions = "errorHandler";
======================================== end of example.
We are not in the business of selling software to software
people, we are a manufacturer of editorial systems, our expertise
is in the area of computer publishing and graphics arts.
We however regard this
development as very interesting for many people using OOPS
and especially those who have met the limitations of C++. We
therefore are willing to supply interested parties an example
of this environment for the PC. It will not have any
documentation but the sources are very well documented. Send
$50 for the copying cost to:
Mediasystemen
Postbox 4932
2003 EX Haarlem, the Netherlands
tel 023-319075
tel intl: (+31) 23 319075
Any questions concerning CO2 should be directed to Peter
Kriens, reachable on pkriens at media01.uucp., or uunet!hp4nl!media01!pkriens
===================================
--
Michiel Steltman hp4nl!wmt!mist +-----------------------------+
Westmount Technology B.V. | Phone: +31 15 610815 |
Poortweg 8, 2612 PA Delft | Fax: +31 15 565701 |
The Netherlands +-----------------------------+
More information about the Comp.lang.c
mailing list