CLASS OBJECTS IN SOM by Nurcan Coskun and Roger Sessions ----------------------------------------------------- Note: 1. Words inside angle brackets should be computer font. 2. Bold lines are indicated by "<--" in right margin. ----------------------------------------------------- THIS ARTICLE WAS ORIGINALLY PUBLISHED IN THE PERSONAL SYSTEMS DEVELOPER SUMMER 1992 AND IS COPYRIGHT BY IBM. THIS ARTICLE MAY NOT BE REPRINTED IN WHOLE OR IN PART WITHOUT PERMISSION. ABSTRACT This article continues the discussion of SOM begun in the winter issue of the Personal Systems Developer. In this article we look at one of the advanced programming features of SOM: the class object. The class object can be used to retrieve at run time a great deal of useful information about classes, including the name of the class and the methods supported. The class object also knows how to instantiate objects of a given class. We give an overview of the class object, and discuss the concept of metaclass. A metaclass of a given class is the class of that class's class object. We show the two important techniques for changing a class's metaclass. INTRODUCTION In the winter issue of the Personal Systems Developer USessions and Coskun, 92e, we gave an introduction to the System Object Model (SOM). SOM is a new methodology for creating object-oriented class libraries introduced by IBM in OS/2 2.0. In our last article we discussed the basic concepts of object-oriented programming: developing classes and instantiating objects; deriving new classes through inheritance; and resolving methods with polymorphism. We also discussed the primary goals of SOM: multi-language support (including both procedural and object-oriented languages); upwardly compatible binaries; and object-oriented extensions to nonobject- oriented languages. In this article we are going to explore an advanced feature of SOM: the class object. We assume that you are familiar with the introductory concepts of our last article. If you find yourself getting lost here, you should go back and revisit our winter article. GOALS OF SOM SOM is a language neutral mechanism for developing object-oriented class libraries. SOM consists of a run-time repository for class information and a series of language bindings. Pictorially we can describe SOM as shown in Figure 1. ................................................................ Figure 1. SOM Run-Time Class Repository and Language Bindings +-+ +-----+ +-------+ +---+ +---------+ ]C] ]Cobol] ]Fortran] ]C++] ]Smalltalk] +-+ +-----+ +-------+ +---+ +---------+ ] ] ] ] ] V V V V V ... etc +------------------------------------------+ ] SOM Run-Time Class Repository ] +------------------------------------------+ ] ] ] ] ] ... etc V V V V V +-+ +-----+ +-------+ +---+ +---------+ ]C] ]Cobol] ]Fortran] ]C++] ]Smalltalk] +-+ +-----+ +-------+ +---+ +---------+ ................................................................ The box in Figure 1 with C pointing to the Run-Time Repository represents a binding which allows classes to be defined in C and added to the SOM run-time repository. The box from the run-time repository to C is a binding which allows C to use classes from the repository regardless of the language in which the classes were originally written. When we say "use a class", we mean either instantiating and invoking methods on objects of a class, or deriving new classes using that class as a base class. In the current version of OS/2 2.0, we support only the C language bindings. The other languages shown in figure 1 are for illustration only. This is neither a complete list of languages under consideration nor a committed list. We are working with a wide variety of language suppliers, both internally and externally, to make this list as complete as possible. As we discussed in the Winter issue, the C bindings allow object-oriented programs to be developed much like they are developed in C++. However in contrast to C++, SOM allows classes to be used by a wide variety of languages as new language bindings become available. SOM also solves other weaknesses in C++ when it comes to developing class libraries. For a good introduction to C++ and these library issues, see Class Construction in C and C++ USessions, 92e. CLASSES IN SOM Lets reconsider a set of classes we first looked at in the winter issue. We will start with the class. An object of type knows how to set up its id and name. It knows how to return its ID. Finally, it knows how to print itself. The class definition file for is shown in Figure 2. .................................................... Figure 2. class definition include class: Student; parent: SOMObject; data: char idU16e; /* student id */ char nameU32e; /* student name */ methods: override somInit; void setUpStudent(char *id, char *name); void printStudentInfo(); char *getStudentType(); char *getStudentId(); .................................................... The class implementation file for is shown in Figure 3. Recall from our last article that most of the class implementation file is produced automatically by the SOM compiler. Those lines shown in bold face are the only lines the class implementor actually needs to write. ................................................................ Figure 3. class implementation (Programmer added lines shown in bold type) #define Student_Class_Source #include "student.ih" SOM_Scope void SOMLINK somInit(Student *somSelf) { StudentData *somThis = StudentGetData(somSelf); parent_somInit(somSelf); _idU0e = _nameU0e = '\0'; <-- } SOM_Scope void SOMLINK setUpStudent(Student *somSelf, char *id, char *name) { StudentData *somThis = StudentGetData(somSelf); strcpy(_id, id); <-- strcpy(_name, name); <-- } SOM_Scope void SOMLINK printStudentInfo(Student *somSelf) { StudentData *somThis = StudentGetData(somSelf); printf("\n Id : %s \n", _id); <-- printf(" Name : %s \n", _name); <-- printf(" Type : %s \n", _getStudentType(somSelf)); } SOM_Scope char * SOMLINK getStudentType(Student *somSelf) { StudentData *somThis = StudentGetData(somSelf); static char *type = "student"; <-- return (type); <-- } SOM_Scope char * SOMLINK getStudentId(Student *somSelf) { StudentData *somThis = StudentGetData(somSelf); return (_id); <-- } ................................................................ Given this definition of , we can derive a new class, say , which is just like a student, but has information about a thesis and a degree. The class definition file for is shown in Figure 4, and the class implementation file is shown in Figure 5. ................................................................ Figure 4. class definition file. include "student.sc" class: GraduateStudent; parent: Student, local; data: char thesisU128e; /* thesis title */ char degreeU16e; /* graduate degree type */ methods: override somInit; override printStudentInfo; override getStudentType; void setUpGraduateStudent(char *id, char *name, char *thesis, char *degree); ................................................................ ................................................................ Figure 5. class implementation file. (Programmer added lines shown in bold type.) #define GraduateStudent_Class_Source #include "graduate.ih" SOM_Scope void SOMLINK somInit(GraduateStudent *somSelf) { GraduateStudentData *somThis = GraduateStudentGetData(somSelf); parent_somInit(somSelf); _thesisU0e = _degreeU0e = '\0'; <-- } SOM_Scope void SOMLINK printStudentInfo( GraduateStudent *somSelf) { GraduateStudentData *somThis = GraduateStudentGetData(somSelf); parent_printStudentInfo(somSelf); printf(" Thesis : %s \n", _thesis); <-- printf(" Degree : %s \n", _degree); <-- } SOM_Scope char * SOMLINK getStudentType( GraduateStudent *somSelf) { GraduateStudentData *somThis = GraduateStudentGetData(somSelf); static char *type = "Graduate"; <-- return (type); <-- } SOM_Scope void SOMLINK setUpGraduateStudent( GraduateStudent *somSelf, char *id, char *name, char *thesis, char *degree) { GraduateStudentData *somThis = GraduateStudentGetData(somSelf); _setUpStudent(somSelf,id,name); <-- strcpy(_thesis, thesis); <-- strcpy(_degree, degree); <-- } ................................................................ With these two classes defined and implemented, we can write client code to instantiate and manipulate s and s. The program shown in Figure 6 instantiates two s and two s, initializes the objects with data, and asks the objects to print themselves. ................................................................. Figure 6. Client Program For and Program: #include "graduate.h" main() { Student *student1 = StudentNew(); Student *student2 = StudentNew(); GraduateStudent *grad1 = GraduateStudentNew(); GraduateStudent *grad2 = GraduateStudentNew(); _setUpStudent(student1,"120455","Joe Doe"); _setUpStudent(student2,"103606","Mary Smith"); _setUpGraduateStudent(grad1,"203230","Janet Brown", "Parser Generators", "M.S."); _setUpGraduateStudent(grad2,"240900","Mark Black", "Code Optimization", "Ph.D."); _printStudentInfo(student1); _printStudentInfo(student2); _printStudentInfo(grad1); _printStudentInfo(grad2); } Output: Id : 120455 Name : Joe Doe Type : student Id : 103606 Name : Mary Smith Type : student Id : 203230 Name : Janet Brown Type : Graduate Thesis : Parser Generators Degree : M.S. Id : 240900 Name : Mark Black Type : Graduate Thesis : Code Optimization Degree : Ph.D. ................................................................. CLASS OBJECTS Every class with at least one instantiated object has exactly one associated class object. Every instantiated object maintains a pointer to its class object which it can return through the method . This method is inherited from , a class from which every class is derived, either directly or indirectly. In the program shown in Figure 6, and are both objects of class , and therefore both share the same class object. Similarly and are both objects of class , and therefore both share another class object, different than the class object shared by and . This is shown pictorially in Figure 7. .............................................................. Figure 7. Relationship between objects and class objects +--------+ +-----+ ]student1]---> +-------+ +---------------+ <---]grad1] +--------+ ]Student] ]GraduateStudent] +-----+ ]class ] ]class ] +--------+ ]object ] ]object ] +-----+ ]student2]---> +-------+ +---------------+ <---]grad2] +--------+ +-----+ .............................................................. Another way to access a class object is through the SOM defined macro <_classname>. For , this macro is <_Student>. This macro returns a pointer to the appropriate class object, if it exists, or zero, if it does not exist. SOM instantiates the class object the first time an object of its controlled class is instantiated. The <_Student> will, therefore, return 0 if and only if no objects have yet been instantiated. The class object is quite a useful object. The class of a class object is either or, as we will see later, some class derived from . There are a variety of methods defined for , and these are all documented in the class definition file for , and described in the SOM documentation USOMe. There are methods which can tell you the size the class, the parent of the class, whether the class is derived from some other class, and whether the class supports a specific method, to name a few. One of the methods is , which returns a pointer to a character string containing the name of the class for which this is the class object. For the class object, this string would be "Student". Another interesting method is , which returns a newly instantiated object of the appropriate class. For the class object, the newly instantiated object would be of the class. The method is always invoked to instantiate new objects. Many programmers are not aware of this because they use a macro wrapper of the form , for example, . This wrapper first makes sure the appropriate class object exists, and then passes through the instantiation request via . In most cases, we would instantiate objects using the class instantiation macro, say , but there might be cases when we can access the class object but not the class instantiation macro. One such example might be a function which accepts a pointer to any object, and returns another newly instantiated object of the same class. Figure 8 modifies the client program shown in Figure 6 to make use of the and class objects and their and methods. ......................................................... Figure 8. Client Program Using Class Object #include "graduate.h" SOMAny *createObject(SOMAny *object) { return(_somNew(_somGetClass(object))); } main() { Student *student1 = StudentNew(); GraduateStudent *grad1 = GraduateStudentNew(); Student *student2; GraduateStudent *grad2; student2 = createObject(student1); grad2 = createObject(grad1); _setUpStudent(student1,"120455","Joe Doe"); _setUpStudent(student2,"103606","Mary Smith"); _setUpGraduateStudent(grad1,"203230","Janet Brown", "Parser Generators", "M.S."); _setUpGraduateStudent(grad2,"240900","Mark Black", "Code Optimization", "Ph.D."); _printStudentInfo(student1); _printStudentInfo(student2); _printStudentInfo(grad1); _printStudentInfo(grad2); } ......................................................... Several of the methods have passthrough methods defined in . In those cases, the actual code for the passthrough methods just gets the object's class object, and passes through the call using the appropriate method. For example, the method passes through to the method . In this article we will use either the methods or the passthrough, depending on the point we are making. THE CLASS OF THE CLASS OBJECT Every object in SOM is associated with a class. The class of is , which we can see from this program: #include "student.h" main() { Student *student = StudentNew(); printf("student object is of <%s> class\n", _somGetClassName(student)); } which gives this output: student object is of class The class objects are also associated with a class. The class of any class object is by default . The class is, like all classes, derived from , and therefore supports all of the methods. We can, for example, verify the class of the class object as in the following program #include "student.h" main() { Student *student = StudentNew(); SOMClass *studentClassObject; studentClassObject = _somGetClass(student); printf("student class object is of <%s> class\n", _somGetClassName(studentClassObject)); } giving this output: student class object is of class We use the word metaclass to describe the class of an objects' class object. Since the class of the student's class object is , we say the metaclass of is . Don't confuse class with metaclass. The class of is . The metaclass of is . We can change the metaclass of a given class by a two stage process: 1. Derive a new class from SOMClass, either directly or indirectly. 2. Tell the given class that its class object will be of the new type through the metaclass directive. There are at least three reasons one might want to change the class of a class object. The first is to add new methods for initializing new objects. Such methods are referred to as constructor methods. The second is to modify how memory is allocated for objects. The third is to keep track of global information about the class, such as how many objects have been instantiated. Lets go through the steps necessary to change the class of the class object from to , or, in other words, changing the metaclass of from to . First, we define the to be derived from . This class will add one new method to those found in . The new method will be . We will also override the client invoked methods , which instantiates a new object. We also need to override the method invoked on a newly instantiated class object, to initialize the count to zero. The class definition and implementation files for are shown in Figure 9. ............................................................ Figure 9: Class Definition and Implementation Files for Class Definition File: include class: StudentFactory; parent: SOMClass; data: int count; methods: override somInit; override somNew; int countObjects(); Class Implementation File: #define StudentFactory_Class_Source #include "countmet.ih" SOM_Scope void SOMLINK somInit(StudentFactory *somSelf) { StudentFactoryData *somThis = StudentFactoryGetData(somSelf); parent_somInit(somSelf); _count = 0; } SOM_Scope SOMAny * SOMLINK somNew(StudentFactory *somSelf) { StudentFactoryData *somThis = StudentFactoryGetData(somSelf); _count ++; return (parent_somNew(somSelf)); } SOM_Scope int SOMLINK countObjects(StudentFactory *somSelf) { StudentFactoryData *somThis = StudentFactoryGetData(somSelf); return (int) _count; } ............................................................ We now modify the student class definition files as shown in Figure 10. ........................................................... Figure 10: Newly Modified student class definition files (Modified lines shown in bold face.) include include "countmet.sc" class: Student; metaclass: <-- StudentFactory, local; <-- parent: SOMObject; data: char idU16e; /* student id */ char nameU32e; /* student name */ methods: override somInit; void setUpStudent(char *id, char *name); void printStudentInfo(); char *getStudentType(); char *getStudentId(); .......................................................... No changes are needed to the class implementation file for . We can now use our new object to find out how many student objects have been instantiated, as shown in this program: #include "student.h" main() { Student *student1 = StudentNew(); Student *student2 = StudentNew(); SOMClass *studentClassObject; _setUpStudent(student1,"120455","Joe Doe"); _setUpStudent(student2,"103606","Mary Smith"); studentClassObject = _somGetClass(student1); printf("Student count: %d \n", _countObjects(studentClassObject)); printf("student1 class object is of <%s> class\n", _somGetClassName(studentClassObject)); } giving this output: Student count: 2 student1 class object is of class IMPLIED METACLASS Given a new class for class objects, we can have any number of classes associated with the new class object. For example, instead of we might have used the metaclass name to indicate greater generality, and then used this metaclass whenever we want to keep track of total instantiated objects. Often we do not need such generality and we only intend for a given class object to be associated with one class. For example, the only change to the metaclass might be the addition of new constructor methods which are only applicable to a specific class. In such cases we can use a slight short cut to redefine the metaclass. This short cut is called implied metaclass. The main advantage of implied metaclasses is that the class definition file for the metaclass can be folded into the class definition file for the class it controls, and similarly for the class implementation file. The student implementation file rewritten to use the implied metaclass is shown in Figure 11. The student implementation file rewritten to use the implied metaclass is shown in Figure 12. ............................................................. Figure 11. Student Class Definition using implied Metaclass (Changes from original shown in bold face.) include class: Student, classprefix = StudentClass_; <-- parent: SOMObject; data: char idU16e; /* student id */ char nameU32e; /* student name */ int count, class; <-- methods: override somInit; void setUpStudent(char *id, char *name); void printStudentInfo(); char *getStudentType(); char *getStudentId(); override somInit, class; <-- override somNew, class; <-- int countObjects(), class; <-- ............................................................ Notice there are two new keywords used to define implied metaclasses. The keyword "classprefix" indicates a prefix used to identify methods associated with the class object. The keyword "class" is used to identify data members or methods associated with the class object. ............................................................. Figure 12. Student Class Implementation using implied Metaclass (Programmer added lines shown in bold face.) #define Student_Class_Source #include "student.ih" SOM_Scope void SOMLINK somInit(Student *somSelf) { StudentData *somThis = StudentGetData(somSelf); parent_somInit(somSelf); _idU0e = _nameU0e = '\0'; <-- } /* Other Student Methods, as before, followed by... */ #undef SOM_CurrentClass #define SOM_CurrentClass SOMMeta SOM_Scope void SOMLINK StudentClass_somInit( M_Student *somSelf) { M_StudentData *somThis = M_StudentGetData(somSelf); parent_somInit(somSelf); _count = 0; <-- } SOM_Scope SOMAny *SOMLINK StudentClass_somNew( M_Student *somSelf) { M_StudentData *somThis = M_StudentGetData(somSelf); _count ++; <-- return (parent_somNew(somSelf)); } SOM_Scope int SOMLINK StudentClass_countObjects( M_Student *somSelf) { M_StudentData *somThis = M_StudentGetData(somSelf); return (int) _count; <-- } ............................................................ Our client program, shown again here, needs no changes: #include "student.h" main() { Student *student1 = StudentNew(); Student *student2 = StudentNew(); SOMClass *studentClassObject; _setUpStudent(student1,"120455","Joe Doe"); _setUpStudent(student2,"103606","Mary Smith"); studentClassObject = _somGetClass(student1); printf("Student count: %d \n", _countObjects(studentClassObject)); printf("student1 class object is of <%s> class\n", _somGetClassName(studentClassObject)); } The output, which had looked like: Student count: 2 student1 class object is of class now looks like: Student count: 2 student1 class object is of class The difference in output reflects the system assigned name of the metaclass. SUMMARY The class object is an important source of runtime information in SOM. Virtually anything that one might want to know about a class can be found out through one of the class object's methods. The class object is itself a fully functional object, like any other object in SOM. It has its own class, by default which can be modified using any of the normal class modification techniques including inheritance and polymorphism. The class object is an important programmer resource. It is the source of runtime information about classes and gives the ability to make runtime decisions about how objects will be manipulated. Because the class object is a standard SOM object, instantiated from a standard SOM Class, you have considerable flexibility to redefine its characteristics as needed. REFERENCES USessionse Class Construction in C and C++: Object-Oriented Programming Fundamentals, by Roger Sessions. Prentice Hall, Englewood Cliffs, New Jersey, 1992. USessions and Coskune Object-Oriented Programming in OS/2 2.0, by Roger Sessions and Nurcan Coskun. IBM Personal Systems Developer, Winter, 1992, 107-120. USOMe OS/2 2.0 Technical Library System Object Model Guide and Reference, IBM Doc 10G6309 ACKNOWLEDGEMENTS SOM is the work of many people. Mike Conner developed the initial idea and implementation, and continues to lead the overall design of SOM. Andy Martin designed the SOM Class Interface Language, and designed and implemented the class Interface compiler. Larry Raper implemented many features of the run time library and porteed SOM to OS/2. Larry Loucks provided close technical tracking and was instrumental in focusing the effort. Other SOM developers include Nurcan Coskun, Scott Danforth, Hari Madduri, Roger Sessions, Simon Nash, and John Wang. The project is managed by Gerry Ronga. BIOGRAPHIES Roger Sessions, IBM Corporation, 11400 Burnett Road, Austin, TX 78758. He is the author of two books, Reusable Data Structures for C, and Class Construction in C and C++ - Object-Oriented Programming Fundamentals, as well as several articles. He is working on object-oriented programming environments and previously worked with high performance relational databases and object- oriented storage systems. Mr. Sessions can be contacted at roger@vnet.ibm.com. Mr. Sessions has a B.A. from Bard College and an M.E.S. in Database Systems from the University of Pennsylvania. Nurcan Coskun, IBM Corporation, 11400 Burnet Road, Austin, TX 78758. Her expertise is in integrated programming environments, code generators, incremental compilers, interpreters, language based editors, symbolic debuggers, application frameworks, and language design. She is now working on object-oriented programming environments and previously worked on the OS/2 Database Manager. Dr. Coskun can be contacted at nurcan@ausvm1.vnet.ibm.com. Dr. Coskun has a B.S. in Industrial Engineering from Middle East Technical University, an M.S. and a Ph.D. in Computer Science from the University of Missouri, Rolla.