mist@wmt.UUCP (Michiel Steltman) (02/05/90)
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@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@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 +-----------------------------+
js7a+@andrew.cmu.edu (James Price Salsman) (02/07/90)
That looks pretty hot, but is it "CO2" or "Co2"? :James "Greenhouse" Salsman
jacob@gore.com (Jacob Gore) (02/07/90)
/ comp.object / mist@wmt.UUCP (Michiel Steltman) / Feb 5, 1990 / CO2 looks interesting, but I don't see any major differences between it and Objective C. For example: > Objective C solves some of the problems but has the drawback > that it is not available on all computers and needs special > pre-processing. I suppose "special pre-processing" means compiling those parts of a program that are in the Objective C language but not in the C language into C code. For instance, converting [rectangle moveBy:1.0 :2.0] into something like _msg(rectangle, "moveBy::", 1.0, 2.0) and so on. If that's what was meant, that's certainly true---Objective C is a different language than C, you can't just run an Objective C program through a C compiler. But: > 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. Doesn't Syntor also perform "special pre-processing" on a CO2 program? > 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. If I understand this correctly, both the message calls and method declarations require conversion by Syntor into C. Message calls may look like C function calls, but they have to be replaced with a call to something very similar to Objective C's _msg routine. Similarly, Syntor handles method declarations differently than a C compiler handles function declarations (otherwise I don't understand why you need the naming convention). Is this not "special preprocessing"? So, what exactly are the fundamental differences between CO2 and Objective C? Jacob -- Jacob Gore Jacob@Gore.Com boulder!gore!jacob