Metaobject Protocols For Distributed Programming

0 downloads 0 Views 664KB Size Report
II Re ection for a Distributed Programming Model Implementation 51 ..... and even modi ed to extend a basic OO language or to create other kinds of ... 10 ...... For each tagged method call, the precompiler adds some C++ code that calls the .... file metaobject name2 object A object B object C segment name1 metaobject.
Internship Report: Metaobject Protocols For Distributed Programming Renaud Pawlak Laboratoire CNAM-CEDRIC 292, rue St.Martin 75141 Paris Cedex 03 e-mail: [email protected] September 7, 1998

Acknowledgments This reports presents the work done within the CEDRIC laboratory of CNAM, directed by Prof. Gerard Florin, and co-directed by Dr. Laurence Duchien. I would like to sincerely thank all the CEDRIC people I have worked with, and especially Gerard Florin, for welcoming me in his group, Laurence Duchien and Lionel Seinturier, for all their training, supervisory, and enlighten advice, Pascale Champagnoux and Daniel Enselme for our endless discussions about re ection. Special thanks to Eric Gressier for non re ective advice and to Laurent Martelli for his rational and consistent thoughts.

2

Contents I State Of The Art

7

1 Introduction 2 Re ection, Meta-Level Architectures and MOPs 2.1 Introduction . . . . . . . . . . . . . . . . 2.2 Open Implementations Issues . . . . . . 2.2.1 Open Implementation Guidelines 2.2.2 Fields of Application . . . . . . . 2.3 Meta-Level Architectures . . . . . . . . 2.3.1 Meta Level and Base Level . . . 2.3.2 Re ection . . . . . . . . . . . . . 2.3.3 Metacircularity . . . . . . . . . . 2.4 Metaobject Protocols . . . . . . . . . . . 2.4.1 Explicit Metaobject Protocols . . 2.4.2 Implicit Metaobject Protocols . . 2.4.3 Inter-Metaobject Protocols . . . 2.5 Conclusion . . . . . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

3.1 Introduction . . . . . . . . . . . . . . . . . . . 3.2 Criteria to Discuss . . . . . . . . . . . . . . . 3.3 The Type of Re ection . . . . . . . . . . . . . 3.3.1 Behavioral and Structural Re ection . 3.3.2 Examples and Applications . . . . . . 3.4 The Time of Re ection . . . . . . . . . . . . . 3.4.1 Compile Time and Run Time MOPs . 3.4.2 Examples and Applications . . . . . . 3.4.3 Re ection Time vs. Re ection Type . 3.5 The Meta-Interface Type . . . . . . . . . . . 3.5.1 Object-Based vs. Class-Based MOPs . 3.5.2 Re ection and Classes . . . . . . . . . 3.5.3 Re ection and Objects . . . . . . . . . 3.6 Conclusion . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

3 MOPs Classi cation Criteria

4 A MOP Classi cation 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8

Introduction . OpenC++ v1 MetaJava . . Iguana . . . . CodA . . . . Apertos . . . OpenC++ v2 Conclusion .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . . 3

. . . . . . . .

. . . . . . . .

9 11 11 11 11 13 14 14 15 17 17 19 19 20 21

23 23 23 23 23 24 25 25 26 26 27 27 27 28 28

31 31 31 33 33 34 34 36 38

5 Object-Oriented Re ective Languages

5.1 Introduction . . . . . . . . . . . . . . . . . . . . . 5.2 Object-Oriented Re ective Languages . . . . . . 5.2.1 Overview . . . . . . . . . . . . . . . . . . 5.2.2 The 3-KRS Example . . . . . . . . . . . . 5.3 Implementation Issues for E ective MOPs . . . . 5.4 Metacircularity and Class-Based MOPs . . . . . 5.5 The CLOS MOP Example . . . . . . . . . . . . . 5.6 Metacircularity Implementation Issues in CLOS . 5.6.1 Bootstrapping Issues . . . . . . . . . . . . 5.6.2 Metastability Issues . . . . . . . . . . . . 5.7 Conclusion . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

6 Conclusion

41 41 41 41 41 42 44 44 46 46 47 47

49

II Re ection for a Distributed Programming Model Implementation 51 7 Introduction 8 Distributed Programming Model 8.1 8.2 8.3 8.4 8.5

Introduction . . . . . . . . . . . . . . . Group Types . . . . . . . . . . . . . . Group Class and Group Instantiation Group Method Invocation . . . . . . . Distributed Statements . . . . . . . . . 8.5.1 Distributed Condition . . . . . 8.5.2 Distributed Iteration . . . . . . 8.6 Conclusion . . . . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

9.1 Introduction . . . . . . . . . . . . . . . . . . . 9.2 System Overview . . . . . . . . . . . . . . . . 9.3 Structural Re ection . . . . . . . . . . . . . . 9.3.1 Objects Mapping . . . . . . . . . . . . 9.3.2 Language Extensions . . . . . . . . . . 9.4 Behavioral Re ection . . . . . . . . . . . . . . 9.4.1 Classical RT-MOP Approach . . . . . 9.4.2 The Event Based RT-MOP Approach 9.5 Conclusion . . . . . . . . . . . . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . Object and Metaobject Classes . . . . . . . . . . . . . . . Control Functions . . . . . . . . . . . . . . . . . . . . . . Introspection Functions . . . . . . . . . . . . . . . . . . . Inspection Functions . . . . . . . . . . . . . . . . . . . . . Programmer De ned Meta Interfaces . . . . . . . . . . . . 10.6.1 Implicit Meta Interface De nition . . . . . . . . . 10.6.2 Explicit Meta Interface De nition . . . . . . . . . 10.6.3 Inter Meta Interface De nition . . . . . . . . . . . 10.7 The Transaction Example . . . . . . . . . . . . . . . . . . 10.7.1 The Base Code . . . . . . . . . . . . . . . . . . . . 10.7.2 De nition of Base Events . . . . . . . . . . . . . . 10.7.3 Linking Events to the Objects . . . . . . . . . . . 10.7.4 Meta-Level Programming . . . . . . . . . . . . . . 10.7.5 Access Semantics Changes with Minimum Coding

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

9 Architectural Model

10 The Event Based RT-MOP De nition 10.1 10.2 10.3 10.4 10.5 10.6

4

. . . . . . . .

53 55 55 55 56 56 56 57 57 57

59 59 59 60 61 61 61 61 62 62

65 65 65 66 66 67 67 68 68 68 68 68 69 70 70 71

10.8 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 10.9 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

11 Runtime Metaobject Composition

11.1 Introduction . . . . . . . . . . . . . . . 11.2 MOPs and Wrapping . . . . . . . . . . 11.2.1 Classical Approaches . . . . . . 11.2.2 Why Do We Need MOPs? . . . 11.3 Metaobject Composition . . . . . . . . 11.3.1 Independent Composition . . . 11.3.2 Dependant Composition . . . . 11.4 A Metaobject Classi cation . . . . . . 11.4.1 Composition Relevant Criteria 11.4.2 Ordering Relation . . . . . . . 11.5 Practical Examples . . . . . . . . . . . 11.6 Conclusion . . . . . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

75 75 75 75 76 77 77 78 79 79 80 81 83

12 Conclusion

85

III Practical Experiences with Re ection

87

13 Introduction 14 Using OpenC++ v2 for Transparent Mapping on Corba

89 91

14.1 Introduction . . . . . . . . . . . . 14.2 The Mapper Metaobject Coding 14.2.1 CorbaMapper.h . . . . . . 14.2.2 CorbaMapper.mc . . . . . 14.3 Using the Corba Mapper . . . . . 14.4 The OpenC++ v2 Limitations . 14.5 Conclusion . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

15 Developing Our Own MOP: the TOS Experience 15.1 Introduction . . . . . . . . . . . . . . . . . . . . . 15.2 Implementation Choices . . . . . . . . . . . . . . 15.2.1 Strategies . . . . . . . . . . . . . . . . . . 15.2.2 Languages . . . . . . . . . . . . . . . . . . 15.2.3 Why Tcl? . . . . . . . . . . . . . . . . . . 15.3 Tcl Fundamentals . . . . . . . . . . . . . . . . . 15.3.1 Commands . . . . . . . . . . . . . . . . . 15.3.2 Variables . . . . . . . . . . . . . . . . . . 15.3.3 Command Substitution . . . . . . . . . . 15.3.4 Grouping . . . . . . . . . . . . . . . . . . 15.3.5 Procedures . . . . . . . . . . . . . . . . . 15.3.6 Evaluation . . . . . . . . . . . . . . . . . 15.3.7 Introspection Features . . . . . . . . . . . 15.3.8 Namespaces . . . . . . . . . . . . . . . . . 15.4 TOS Default Object Model and Implementation 15.4.1 The Object Model and Syntax . . . . . . 15.4.2 The Default Implementation . . . . . . . 15.5 The TOS MOP . . . . . . . . . . . . . . . . . . . 15.5.1 Meta-Level Objects . . . . . . . . . . . . 15.5.2 And... What About the Class Class? . . . 15.5.3 Metacircularity . . . . . . . . . . . . . . . 15.5.4 Bootstrapping Issues . . . . . . . . . . . . 15.5.5 Runtime Metacircular Issues . . . . . . . 5

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

91 91 92 92 96 97 97

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

. 99 . 99 . 99 . 99 . 100 . 100 . 100 . 101 . 101 . 101 . 102 . 103 . 103 . 103 . 104 . 104 . 105 . 107 . 107 . 109 . 109 . 110 . 111

99

15.6 Using TOS MOP: A Meta-Programming Example 15.6.1 Creating a Metaclass . . . . . . . . . . . . . 15.6.2 A Simple Example . . . . . . . . . . . . . . 15.6.3 Class Metaobject Runtime Change . . . . . 15.7 Conclusion . . . . . . . . . . . . . . . . . . . . . .

16 Conclusion

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. 111 . 111 . 112 . 113 . 114

115

6

Abstract This DEA internship report proposes a study and a classi cation of best known Metaobject Protocols (MOPs). Far from being totally exhaustive, it explains the nowadays motivation and use of MOPs by practical examples in many application areas. These examples naturally lead to distinguish di erent kinds of use of MOPs techniques and also, as one can expect, di erent kinds of implementation. These functional and implementational di erences allow us to classify MOPs in two large families: behavioral and structural oriented MOPs. The former is mainly used to deal with behavioral issues and is mostly used in system approaches. The later is more used in approaches consisting in controlling languages and structural features. This classi cation naturally leads to the idea that, to eciently build e ective object-oriented distributed applications, we should be able to consistently use those two approaches within the same development process, and even within the same system. This is the goal of our architectural framework that proposes a mixed compile-time structural re ection with runtime behavioral re ection. Since our approach provides more exibility, we also deal with more tricky issues like runtime metaobject composition.

2

Introduction

3

This report presents the work done during my Computer Science DEA (a PhD preparation year) internship, at the CEDRIC-CNAM laboratory in Paris, France. I have worked in the Fault Tolerant and Distributed Systems Research Group with Gerard Florin, Laurence Duchien, and Lionel Seinturier. Our research focuses on Metaobject Protocols (MOPs) and Re ective Architectures1 study and use for programming and building distributed applications and middlewares in Object-Oriented environments. In fact, for several years now, and in many areas, Metaobject Protocols and Re ective Architectures appear to impose themselves as a powerful and elegant way to deal with modern systems or applications, that have to face more and more complex, evolving, and uncertain environments. By adding the possibility to see and modify what is not visible in classical approach, and by o ering the selfreasoning feature, re ection have developed means to increase the system abilities to be easily and/or automatically adaptable and con gurable, extensible and highly reusable for various application needs | much more than any other kind of development scheme. However, since Metaobject Protocols is a very recent research eld | the word was rst used in 1991 by G. Kiczales |, they are still many progresses to achieve to make them really ecient and simple to use. For this last reason, we have deeply studied the state of the art and their theorical foundations to be able to anticipate their evolutions. Furthermore, since re ection is now introduced in many various elds, like communications, software engineering, operating systems, and languages, it becomes really dicult to categorize re ection types and know how to use them. Despite these diculties, this work tries to classify the di erent kinds of re ections and the di erent implementations and uses one can encounter when working with MOPs (in part I). We also propose an architectural framework for building distributed applications, by taking the best of the di erent systems we have studied (part II). This architectural framework is composed of a compile-time re ective part, that is a technique that comes from re ective languages, and from a runtime re ective part that has been widely used in re ective operating systems research. In our proposal, the compile-time part controls the runtime part so that one can adapt the exibility/performance trade-o to its personal needs. Moreover, we begin a metaobject automatic runtime composition study, which is a point that, despite being very poorly explored, is, to us, one of the major key for maximum

exibility, reusablity, and separation of concerns in meta-level programming. In the last part, we present two practical experiences with MOPs. The rst one consists in using a recent well-known re ective language to transparently map some language-level objects (C++ objects) to distributed objects of a middleware layer (Corba). This experience validated the ability of MOPs to rapidly build powerful semantics extention to existing languages. However, this experience showed some limitations of the currently available MOPs. Most of the time, they su er from the fact that they are built on the top of complex existing systems | that has not been developed in a re ective purpose. Thus, the meta-interfaces are often very complex and not general enough to totally exploit the re ective features of the system. In our case, some problems arose when developping the extension to Corba. Those problems could be even harder to solve when implementing our architectural framework. This First experience encouraged us to build our own re ective language. Our motivation was to dispose of a general purpose re ective architecture, so that we will not be limited by the meta-interface limitations or inability to ll our needs. For this, we constructed a full runtime MOP that can be used and even modi ed to extend a basic OO language or to create other kinds of MOPs.

1 Re ective Architectures and Metaobject Protocols (MOPs) are often used to design the same kind of systems though MOPs were rst used for re ective languages.

5

6

Part I

State Of The Art

7

Chapter 1

Introduction Since modern systems and applications are becoming more and more complicated and are dealing with more complex, evolving, uncertain environments and large number of di erent kinds of users, the need to introduce clear behavioral controlling features upon software entities becomes more important. In many areas such as operating systems, communications and networks, software engineering, and languages, designers have developed means to increase their system abilities to be easily or automatically adaptable and con gurable, extensible and highly reusable for various application needs. Because human intelligence is hardly able to understand all the over-mentioned issues at the same time, all the proposed solutions to address them are more or less based on separation of concerns. However, a total separation like the one provided by the black box abstraction cannot be suitable for such complex implementation issues. Hence, the appearing of \open" solutions, which are proposing some features to control the internal implementation of the system. Nevertheless, those recent trends are not yet formalized and their developers dispose of very few guidelines and generic solution to implement them. For this reason, a crucial point consists in providing generic implementation frameworks to facilitate the open implementation designer task. Metaobject Protocols (MOPs) are one of these frameworks. Metaobject protocols strength is that they provide a object-oriented based solution that can be easily integrated in classical development processes. They are yet very largely used and studied in operating systems and languages areas where they propose an elegant and uniform way of programming and thinking based on re ective concepts. This part proposes a study and a classi cation of MOPs in re ective architectures and mainly focused on MOPs for re ective operating systems and languages. In chapter 2, we give an overview of the open implementation issues, the meta-level architectures and MOPs. We give as much examples as possible so that the reader can understand the MOPs practical challenge and we also de ne as clearly as possible some basic terms that will be used further. Chapter 3 gives some classi cation criteria for MOPs so that we can restrain the discussion to the most important aspects. For each criteria, we give some example and reasons for which we choose it. In chapter 4, we focus on some representative MOPs that we choose in function of the previously discussed classi cation criteria. For each MOP, we focus on its main particularity. Then we give a short description table of most of the well-known MOPs. Finally, chapter 5 discusses some important implementation issues. This discussion is mainly oriented towards metacircular re ective languages implementation because this area address the re ective implementation in more generic way than the re ective operating systems | despite these are more advanced in behavioral- and adaptative-oriented re ection.

9

10

Chapter 2

Re ection, Meta-Level Architectures and MOPs 2.1 Introduction This part gives a brief overview of meta architectures issues, what kind of problems they are addressing and how they address them in comparison to classical approaches. It also de nes some of the wellknown terms and concepts that are meta-architecture domain speci c and that are most frequently used by the authors. The rst section gives a general overview of the Open Implementation [Imp] proposal | as it is more and more used to face modern architectural issues. Then, section 2.3 introduces the basic concepts one can encounter in meta-level architectures including the base and meta level (section 2.3.1), the rei cation and re ection processes (section 2.3.2) and metacircularity (section 2.3.3) in meta-level architectures. Finally, section 2.4 brie y introduces the metaobject protocol approach by insisting on the interface point of view.

2.2 Open Implementations Issues

2.2.1 Open Implementation Guidelines

A recent trend in complex application support systems such as operating systems, enables applications not only to use services provided by these softwares but also gives them means to control how this is done. This trend seems to go against the traditional principle | informally known as the blackbox abstraction | saying that a module should expose its functionality but hide its implementation ( gure 2.1). Black-box abstraction has many attractive qualities and gives some interesting solutions to portability, reuse, ease of development issues and so on. Exposing only the functionality of a module (or an object) in its interface, however, can sometimes lead to some performance diculties when the module get reused | this problem, raised by choosing a well tted implementation of an interface is often called \mapping dilemma". Moreover, diversi cation of the modern applications, dealing with many kinds of implementation problems at the same time, tends to complexify functional modules and naturally leads to splitting these modules into di erent parts, achieving by this way some separation of concerns ( gure 2.2). In fact, the open implementation principles rather extend the black-box principles rather than question them. Those principles should more be seen as a framework for more extensible designs and using the black-box principles. Consequently, open implementation keeps the ve characteristics of quality interface designs outlined in [Hof88]. That is, the interface is consistent, essential, general, minimal and opaque. In fact, those principles are even more crucial issues in an open implementation design than in a classical one. In [KLL+ 97], open implementation guidelines are set as bellow:

 OI module interfaces should support a clear separation between client code that uses the module functionality (use code) and client code that controls the module implementation strategy (ISC 11

Multi-threaded Application

Just execute

Multi-threaded Application

list of thread IDs

Multi-threaded Application

ID of selected thread

Control of the sceduling algorithm thread queues

Closed OS

Modestly Open OS

Totally Open OS

Figure 2.1: Open Implementation example: the multithreaded application.

replication metaobject

persistance metaobject

atomicity metaobject

consistency metaobject

communication metaobject

Meta level (policies level) binding switch Base level (functional level)

applicative object

notifies when base event (upcalls the meta level) controls the base level

Figure 2.2: Separation of concerns in modern operating systems. 12

code).  OI module interfaces should be designed to make the ISC code optional, make the ISC code easy to disable, and support alternative ISC codes for one piece of use code.  OI module interfaces should be designed to allow the scope of in uence of ISC code to be controlled in a way that is both natural and suciently ne-grained. As one can expect, there are many di erent degrees of Open Implementations. One should go from the black-box implementation to the totally open implementation (see gure 2.1). Practically, the right implementation should be a trade-o , thus loosing as few as possible of the bene ts of the two approaches. Moreover, the means to realize open implementation should di er in function of the concepts that need to be opened. For example, if we want to open an operating system, the ISC code, should be able to manipulate some low level system mechanisms like resources or execution entities. In case we need to control the implementation of a compiler, the ISC code will probably be able to deal with parse trees or syntaxic concepts. Despite those di erences, it could be interesting to keep a certain kind of uniformity between the two programs (ISC and use code). For example, the implementation strategy and the user program could be written in the same language to avoid the programmer to learn more syntax than she/he used to know in the black-box abstraction approach.

2.2.2 Fields of Application

Open Implementation, that is now a very active research eld, has been before informally used in many contexts. We can notice some Open Implementation features used in many research elds such as programming languages and parallelism, operating systems, networks and communication, software engineering.

Programming Languages and Parallelism

As in other areas of computer science, one of the driving forces in programming language design has been towards higher level abstractions | to simplify programmers' work. However, the higher the abstraction level is raised, the more serious the problems with mapping dilemma become. Because of this, there are many languages that more or less t the notion of re ective modules1, especially in elds where implementation issues are crucial for performance. One of the best example is High Performance Fortran that is using meta-language2 annotations that allows parallel machines programmers to solve some complex mapping dilemma, like dispatching array elements to distributed memory. In the following example, the two arrays A and B are distributed on the multiprocessor memories via the !HPF$ annotations. REAL A(1000,1000), B(998,998) !HPF$ ALIGN B(I,J) WITH A(I+1,J+1) !HPF$ DISTRIBUTE A(*,BLOCK)

Many other languages are also using explicit meta interface to control the compiler behavior. The meta keywords are usually said to be pragmas and can be found in C [KR78], C++ [Sou91], Hermes, Modula-3 [CDG+89], Common Lisp [Ste90]. Metaobject Protocols3 are a more recent and powerful means to control programming languages. One of their advantages is to also de ne an implicit metainterface, allowing the programmer to clearly separate the use code from the ISC code.

Operating Systems

Like programming languages, operating systems are among the rst software modules to be extensively reused. OS designers have thus been experimenting with OI-like implementation techniques for several 1 A re ective module is a module that includes a meta-level, i.e. a module that also describes its own implementation. We come back to this notion in 2.3. 2 As one can expect, a meta-language is a language used in the meta level of a re ective module. That is to say, the language that describes the implementation issues of the module (see 2.3 for details). 3 Metaobject Protocols are a kind of object-oriented re ective architecture (see 2.4).

13

years. Meta-interfaces have been developed to make various OS functionalities adaptable to a wider range of program behaviors. Process meta-interfaces are used to control the scheduler (e.g. yield function to give the hand to a new process, hando scheduling function to set the next selected process, nice program in Unix to set the relative importance of processes, ...). Meta-interfaces are also used to provide increasingly ne-grained control over virtual memory (e.g. the Mach External Memory-Management Interface, EMMI) [You89]. Last example is the le system meta-interfaces used to control bu ering strategies, naming and access control of les (e.g. AFS that uses a lightweight le naming interface to implement a new le namespace and le access control list [HKM+ 88]).

Networking and Communication

Since networking and communication systems are generally viewed as low-level systems, they have historically led to carefully hand-crafted code wrapped up in a black-box with a relatively in exible client interface. In this usual approach, programmers have to accept either an integral subsystem with all its built-in mapping decisions (e.g. TCP), or a stripped-down minimalist version on top of which they can implement their own mapping decisions from scratch4 (e.g. UDP). However, since we should consider multimedia systems, there are a lot of mapping decisions that the programmer should be able to make to completely exploit a communication interface:  Format of the transmitted data (\marshaling"),  Synchronous/asynchronous communication (e.g. RPC vs. message passing),  Message bu ering and retry policies,  Synchronization and ordering constraints,  Congestion control,  Priorities or scheduling policies.

Software Engineering

Ever since software systems of any size have been built, software engineers have sought mechanisms and methods to promote the reuse of software components. The Open Implementation and re ective module approaches are intended to provide new opportunities for enabling more reusable components. However, it remains still unknown in general sense how to e ectively apply an Open Implementation approach within a software development. This is an emerging research eld where many topics need to be addressed [Imp]. However, we can mention the growing number of metacases5 [CM] and, for instance, Objecteering [Des96] and Metagen6 that are both opening the user level model to perform some kind of treatments on it | such as model translation and code generation. The meta approach7 furnishes a good framework for OI design because it naturally provides a conceptual distinction between the base and the meta level. In fact, base-level program stands for the user code while the meta-level program plays the ISC code role.

2.3 Meta-Level Architectures

2.3.1 Meta Level and Base Level

Generally speaking, the meta term denotes a higher sphere of control, in fact, some information about the information. However, this de nition is quite informal and, by its genericity, could be applied to many situations. Since our subject here are the metaobject protocols (see the next de nitions), we will now adopt an object oriented point of view. Hence, a program will be represented by a set of object and sometimes, object behavior will be described in some classes objects. 4 This phenomenum, forcing the programmer to entirely rebuilt some well-known policies because the existing ones does not exactly meets his needs is often called the hematoma phenomenum. 5 A metacase is a tool that is using re ective facilities to design cases (CASE = Computer Aided Software Engineering). 6 Metagen is developped by G. Blain of the Laforia Paris VI. 7 Meta approach is an other word to say re ective approach. We detail this approach in the next sections.

14

Because we need to distinguish the control sphere from the ordinary sphere, we can introduce two di erent levels: the base level and the meta level [Mae87a][Smi82]. The base-level objects are called baseobjects (or sometimes objects for short) and the meta-level objects are called metaobjects. However this distinction is still quite general and applies to many cases. Let's take the Smalltalk example. It is a good example because Smalltalk has a uniform way to represent the system concepts. Applying this, each Smalltalk class has itself a metaclass which is playing the same role than the classes for the ordinary objects [GR89]. Since the metaclasses are controlling the behavior of the classes, they can be seen as metaobjects for classes. But if we follow the same principles for the classes and the object, we naturally conclude that classes are metaobjects for objects. Thus, Smalltalk architecture has two meta levels, one of them is used to control the classes. However the kind of object they control is not the only di erence between those two meta levels. In fact, there is a more subtil di erence that one can apprehend by the simple fact that contrary to the class-object relationship where a class can control many objects at the same time, a metaclass can only control one class at the same time. For example, Zimmermann in [Zim96] introduces the Smalltalk meta-level architecture by considering metaclasses as metaobjects and classes and objects as baseobjects. Next sections will explain further the di erent kind of meta levels.

base level: the base level is the applicative level where applicative (functional objects) are evolving. meta level: the meta level is a generic term that designs a level where system/control/implementational

metaobjects are evolving. Depending on the case, metaobjects are said to depict, control, implement or modify the baseobjects. metaobject protocol: a metaobject protocol is the object-oriented interface that allow the base and meta levels to communicate (see section 2.4 for details).

2.3.2 Re ection

Imagine now that we have a metaobject implementing a persistence service. The meta approach says that if a base object is controlled by this metaobject, then the baseobject should automatically become persistent. To achieve this, we need a strong relationship between the two objects: the meta relation. This relation allows the baseobject to ask services to the metaobject and the metaobject to change the object implementation to make it persistent. Generally speaking, the meta-level control over the base level always takes place in two steps [Mae87b][Fer89]:  The baseobject calls the metaobject requesting a change in terms of semantics (e.g. the persistence). This is called the rei cation of one implementation or semantic aspect. Rei cation (from Latin res,rei : thing) means in fact to make concrete an implicit aspect of an object, so that it can be changed by the metaobject.  After this is done, the ow of control returns from the metaobject back to the baseobject. Because the metaobject modi ed a part of the baseobject, its behavior or representation is now changed. This process is also called re ecting the changes back into the baseobject. Notice that the meta relationship is not symmetric. Indeed, to re ect the semantics changes into the baseobject, the metaobject needs to know how the base object works and is implemented. Conversely, the base object does not need to know many things about the metaobject(s). To change its properties, it just needs a well-known entry point to send a rei cation message and base-level informations the meta-level needs to know/change. Figure 2.3 shows three concrete rei cation examples. The rst example shows a metaobject that rei es the synchronization aspects of a baseobject. In this example, it is the metaobject that controls all the synchronization aspects by directly using the send/receive message queues. Thus, it can implement di erent communication policies by modifying certain kind of criteria (such as the queuing/dequeuing or the acknowledgment policies). The second example shows a metaobject that rei es the object state to make it persistent. Notice that the metaobject does not reify the same base-level data than on the previous example (execution/state). This is due to the fact that the synchronization metaobject does not need to know the internal state of the baseobject to synchronize message queues. On the contrary, the persistence metaobject needs to know the internal state of an object in order to save it on a data 15

persistence metaobject

synchronization metaobject send/receive queues

data storage reify (execution)

reify(state)

reflect(execution made synchronized)

reflect(state made persistent)

base object

base object

Persistence aspect reification

Syncronization aspect reification

persistence metaobject

synchronization metaobject send/receive queues

data storage reify (execution)

reflect(execution made synchronized)

reify(state) reflect(state made persistent)

binding switch reify (execution+state)

reflect(execution made synchronized +state made persistent) base object

Syncronization and persistence aspects reification

Figure 2.3: Base and Meta Level Communication.

16

storage. The third example shows a meta-level architecture that rei es both aspects. To do this, it can compose the two metaobjects by adding a binding switch to turn the rei cation information toward the concerned metaobject. Note that the base level rei ed information is now the sum of the previous ones. In general, the rei cation process tries to pass to the meta level only relevant information (for the kind of re ection it processes).

rei cation: The rei cation operation consists in expliciting some base concepts or mechanisms that

are usually transparent for the programmer (e.g. the method look-up process in an object-oriented programming language). Those rei ed concepts or mechanisms are usually implemented in some metaobjects (at the meta level). In a functional point of view, the rei cation process occurs when the base level gives hand to the meta level. re ection: The re ection operation consists in modifying the base level interpretation (by using metalevel information) to change the base-level objects semantics. In a functional point of view, the re ection process occurs when the meta level processes some extra computing and gives back the hand to the base level. An amusing thing to notice is that the words re ection and rei cation are sometimes used indi erently

to indicate that some meta intervention happens. In fact, it depends of the point of view the author wants to highlight, on one hand, it is the meta level that modi es the base level (re ection point of view), on the other hand, it is the base level that is modi ed by the meta level (rei cation point of view). For example, OpenC++ v1 (see section 4.2) syntax uses the keyword \re ect" to link a baseobject to a metaobject and Iguana (section 4.4) syntax uses the \reify" keyword to express the same idea. In this report, we usually use the re ection point of view.

2.3.3 Metacircularity

We introduced in section 2.3.1 Smalltalk metaclasses that can be seen as metaobjects for classes (and classes as metaobjects for objects). Generally speaking, considering the metaobjects as ordinary objects raises the question of knowing who is controlling metaobjects. The logical and consistent answer is to control meta-level objects with some meta-meta-level objects. But since this assertion introduces a in nite loop | a metacircularity | in the object de nition, a meta-level architecture is nally depicted as an in nite tower of meta levels [KdRB91] (see gure 2.4). This metacircular de nition eventually rises a tricky implementation issue, forcing practical metalevel architectures to apply some heuristics. We can denote three di erent approaches to solve this problem. A rst one is to impose a hard limit on the number of available meta levels. For example, the Apertos system restricts the number of meta level to four meta levels [LYI95]. The philosophy of this approach is to tightly x the semantics control into the system specializing each meta level for one well de ned task. A second approach tries to break the rigidity caused by the rst one by constructing another oor of the tower on demand only [WY88]. Since this approach lives with minimum meta levels until it is actually needed, it is more exible and could give better performances. However, it raises the meta stability and composition semantics issues. A third approach which was rst tried in CLOS [KdRB91] is to accept the metacircularity by saying that the meta level is in fact controlled by itself. Those meta architectures, said to be true metacircular [Chi96], present many advantages in matter of ecient implementations but need to introduce mechanisms to control the meta level stability and consistency [CKL96] (see gure 2.5). Metacircular architectures are discussed in chapter 5)

2.4 Metaobject Protocols Since the most interesting thing about a meta-level architecture is the way the base and meta levels communicate and what kind of information they exchange, object-oriented meta-level architectures are often called metaobject protocols (MOPs). This term was rst used by the CLOS [KdRB91] architecture to indicate a meta-level architecture or a re ective language8. Particularly, MOPs mean

8 In this report, we use indi erently the term \meta architecture" or \re ective". However, the term re ective is conceptually stronger than the other one since we prefer to employ the re ective term for language based approaches and meta architecture for system based approaches (even if this generic term can be applied to many situations).

17

synchronization metaobject

Infinite meta level send/receive queues

reify (execution)

reflect(execution made synchronized)

If a meta level n needs to reify synchronization aspects, it needs a n+1 meta level to control itself.

synchronization metaobject send/receive queues

reify (execution)

Meta level 1 and base level for meta level 2

reflect(execution made synchronized)

Base level base object

Figure 2.4: The In nite Tower of Meta Levels (the synchronization example).

18

Breaking the infinite regression.

reify

reflect

Meta level for meta level is the meta level itself.

synchronization

Unique metacircular meta level

metaobject send/receive queues

reify (execution)

reflect(execution made synchronized)

Base level base object

Figure 2.5: True metacircular architectures. programming interfaces for customizing the system or the language. This terminology, using the word protocol instead of interface, comes from Smalltalk. Finally, MOPs are the interfaces used by the base and metaobjects to communicate in a meta-level architecture. Those interfaces fall into three categories [Zim96]: explicit metaobject protocols, implicit metaobject protocols and inter-metaobject protocols (see gure 2.6). They are further discussed in the next sections.

2.4.1 Explicit Metaobject Protocols

This interface is used by base objects to communicate with metaobjects in order to directly control the behavior of the base objects. For example, the base object could ask the metalevel to become persistent. Explicit MOPs are not transparent for the baseobjects but are extremely useful to customize and control the implementation. If we refer to the open implementation guidelines, those metalevels parameterization should be the ISC code and should be optional to the base program and well adapted to the kind of customizations the base level wants to achieve. In gure 2.6, one can see an example of an explicit upcall to the meta level (bold, plain arrow). Typically, the base object can use the explicit MOP to ask the replication metaobject to instantiate a new replica. Since it is an explicit upcall, the base object can pass at runtime the location where it wants to be replicated. Another kind of typical expicit call could consist in asking for a migration towards a given node. However, if the metalevel implements some automatic migration or replication facilities, those upcalls do not have to be explicit and can be implicit.

2.4.2 Implicit Metaobject Protocols

In contrast to explicit metaobject protocols, where an object has to invoke its metaobject directly, an implicit metaobject protocol takes place transparently. In general, this interface is xed (because its upcall is a built-in process) and characterizes the MOP rei cation capabilities. Depending on wether the application is coded in an interpreted object-oriented programming language such as Self [US87] or CLOS or a compiler-based language such as C++ [Sou91], di erent implementation mechanisms are employed to facilitate the transfer of control from the base level to the meta level | this built-in mechanism is also called the metalevel interception (MLI) [Zim96] and is used to mantain the causal connection between base and meta levels. Implicit protocols meet the second requirement of the open 19

inter metaobjects messages (e.g. giving the replica list to the consistency protocol)

consistency metaobject Meta level (policies level)

replication metaobject

explicit upcall (e.g. asking for replication)

implicit upcall (e.g. when message sending/receiving)

base object

base object

Base level (functional level) base object

base level message send explicit metaobject protocol message send inter metaobject protocol message send implicit metaobject protocol message send

Figure 2.6: The Three Categories of MOPs. implementation guidelines because they are completly transparent. This means that a base object semantics can be modify without changing a line of the base code. As we can expect, this protocol has to be very well de ned and generic enough so that a base object can be transparently controlled by several kinds of metaobjects. In gure 2.6, the dashed arrows show typical examples of some implicit upcalls to a consistency metaobject. Indeed, a simple way to implement a consistency protocol in a metaobject is to proccess an implicit upcall when a base-level message sending or receiving happens. Then, the metaobject can, for instance, easily lock the base object and can then invalidate replicas when a message modi es the object state. As we will see later, the implicit MOP is quite dicult to implement. Indeed, if this protocol (the rei cation protocol) is too rich it can lead to ineciency | for example, passing the state of an object in order to add synchronization functionalities (see the example in gure 2.3) |. As a consequence, an implicit MOP should be a trade-o between power and e ectiveness and should also bene t from some tricky implementation techniques (see section 5.3). Note that explicit metaobject protocols do not raise this issue because they can be seen as classical library interfaces.

causal connectivity: The causal connectivity is the fact that, when some meta-level code is exectuted,

it automatically, transparently, and immediately changes some base level behavioral or structural aspects. Causal connection is maintained by the rei cation and re ection processes that rely on the implicit part of the MOP. As one can expect, causal connection is a crucial implementation issue.

2.4.3 Inter-Metaobject Protocols

The third category of metaobject protocols are the ones that are used when metaobjects communicate with each other. This metaobject protocol is not visible from the base level but is quite similar to the explicit one in a sense that they can be implemented by classical invocations. For example, in gure 2.6, the replication and consistency metaobjects have to communicate each other some information. As one can easily imagine, the consistency metaobject has to know the list of the replicas of a baseobject to implement the consistency algorithm. Moreover, for better e ectiveness of the whole system, the replication metaobject could notify the consistency metaobject when a replica is added or deleted. 20

2.5 Conclusion The Open Implementation guidelines give some reference for ecient and clear development in many elds used in practical applications especially in programming languages, parallelism, operating systems, networks, communication and software engineering. Re ective architectures meet these Open Implementation guidelines by providing a clear and elegant way to conceptually and practically separate functional and implementation issues. Thus, we try to de ne as clearly as possible the involved concepts in re ectiveness, like meta levels, re ection, rei cation and so on. Metaobject protocols are brie y introduced and appear to be a good mean to t the open implementation requierements and to implement re ective Object-Oriented architectures. MOPs mainly provide interfaces for meta programming and accessing re ective features. We retain the Zimmermann classi cation of MOPs' interfaces: explicit, implicit and inter MOPs. Next parts will mainly focus on implicit MOPs because it is the most crucial interface to consistently de ne and eciently implement.

21

22

Chapter 3

MOPs Classi cation Criteria 3.1 Introduction The rst chapter presented some general concepts such as rei cation and re ection. This chapter establishes a classi cation between the di erent kinds of MOPs. This is quite a dicult exercise because MOPs di er in many di erent but interlaced ways. For this reason, we evaluate MOPs by focussing on two main points of view: their power of rei cation (openness) and some kind of practical and concrete criteria like performance and implementation issues. However, one can expect these two points of view to be tightly linked.

3.2 Criteria to Discuss Ferber [Fer89] gives a rst overview of the essential issues to consider when implementing a MOP: 1. The number of rei ed concepts | subjected to inspection and modi cation by the meta level | should not be too important. In fact, the more MLIs take place, the more the overhead due to the meta level is important. 2. The implicit and explicit MOPs must be well balanced so that the separation of concerns meets the meta-level architecture needs. 3. For performance reasons, rei cation should happen as seldom as possible. For instance, if it is possible, a rei cation should happen only during the construction of an object instead of during each method call. This problem is often linked to implementations problems. More recent issues [Ibr90] are also very important: 1. How are the base level and meta level synchronized? This issue is not much addressed for the moment but it should become more and more important with the advent of parallel architectures. 2. How is a base-level aspect rei ed and re ected | message passing, shared memory? This implementation issue is crucial for performance and is linked with the rst and third points. 3. How is metacircularity addressed? It is certainly one of the most important point in terms of implementation, performance, or ease of use. We largely discuss this point in chapter 5.

3.3 The Type of Re ection

3.3.1 Behavioral and Structural Re ection

We can distinguish two di erent types of re ection: structural and behavioral (or computational) re ection [Fer89]. Structural re ection rei es structural aspects of the base objects or program, such as inheritance and data types. It means that structural re ection can actually modify the implementation 23

and the deep semantics of a base object (like its interface or the set of classes it is related to). Behavioral re ection is concerned with the rei cation of computation and behavior. This kind of re ection was deeply studied by Maes [Mae87b] and Ferber and can be easily understood, in an object-oriented context, as a kind of framework for separation of concerns in di erent kinds of objects (the objects and the metaobjects). However, those two kinds of re ections are in fact two extreme visions of re ection that are not clearly separated. They can almost be seen as two di erent points of view for the same result since structural metaobjects can sometimes be seen as control metaobjects (if we consider them as a part of the baseobject) and control metaobjects can be considered as structural metaobjects if we decide to integrate them into the structure of the baseobject. Figure 3.1 shows a possible straightforward mapping between two re ective architectures: the left one for behavioral re ection and the second one for structural re ection. As you can see, the main di erence between those two architectures are the metaobjects granularity and representation. Anyway, even if these re ections can have some semantic links, the kind of interfaces and the associated metaprograms and implementation issues are really di erent. In practical, the behavioral re ection is much more suited and straight forward to implement some system mechanisms while structural re ection is widely used for language extensions.

3.3.2 Examples and Applications

Behavioral Re ection

Behavioral re ection is typically used in operating systems and communications. For example, the Apertos [Yok92] re ective operating system implements the system objects management at metalevel, including some control features like:

 Inspecting the internal and the state of an object,  Changing a policy of object management such as object scheduling and memory management,  Delivering a message to a destination object, and so on. One of the rst consequences of those re ective features is that Apertos can easily adapt some of its system policies to provide the best possible service. For instance, since a metaobject is responsible for delivering messages, it can construct the optimal network protocol for delivering a message to its target. Other examples of adaptative policies could be the implementation of a memory management suitable for the object granularity [YMFT91]. Some other examples of MOPs providing a set of control metaobjects at the metalevel are Muse, CodA [McA95], Iguana [GC96], AL-1/D [OIT92], Actalk and so on. Note that some of them also provide structural re ection oriented metaobjects that can be used to process a certain kind of introspection |especially in Iguana. One of the most simple example of behavioral re ective architecture is the OpenC++ v1 MOP [Chi93]. Most of these MOPs are further described in chapter 4.

Structural Re ection

Because the structural re ection implies to describe the base level structures, the overhead due to the structural rei cation process is generally much more important than in the behavioral approach. On gure 3.1, one can see that contrary to the behavioral re ective architecture, the structural one dynamically creates new kinds of object implementation by de ning new baseobject metaclasses (in the behavioral way, the implementation of an object is always the same). This approach gives many advantages. One of the most important is to allow a direct mapping from the system objects to the language objects without any external translators like compilers. On the other hand, one obvious drawback of this approach is that the rei cation concepts are very complex and imply a big overhead. For this reason, this kind of rei cation is moderately used as is in operating systems contexts but are more and more used in the design of extensible languages. Some well-known examples using this kind of re ection are 3-KRS [Mae87b], CLOS [KdRB91], OpenC++ v2 [Chi95]. All those MOPs achieve more or less complete structural re ection. A common technique to achieve structural re ection is to let a metaclass | a metaobject for class | control a number of base classes. For example, Run-Type Type Identi cation (RTTI) of C++ 24

baseobject metaclass

persistence metaclass

comunication metaclass

communication aspects

baseobject class

persistence aspects

baseobject class

baseobject communication metaobject

persistence metaobject

persistence aspects

communication aspects

functional aspects

baseobject

A typical behavioral/computationnal reflective architecture.

A typical structural reflective architecture.

a final object of the system (i.e. a class instance in an object-oriented context). a class of the system (or a metaclass, class for class). separation between base and meta levels. instanciation link (a metalink from an object towards a class). behavioral metalink (this is not a true instanciation link).

Figure 3.1: Behavioral Re ective Architectures vs. Structural Re ective Architectures. [Sou92] and the MIP [BKPS92] are a kind of structural re ection. In general, behavioral re ection is implemented by metaobject(s) for objects.

3.4 The Time of Re ection

3.4.1 Compile Time and Run Time MOPs

MOPs have typically and historically been developed in the domain of the interpreted languages. The simple reason for this is that for a program to be interpreted, an interpreter must construct a lot of information about the program | such as appropriate dispatching functions to use, inheritance hierarchy lookup and so on. In a classical context, this information is not used by the program but is needed by the interpreter to execute the program. At the contrary, in a re ective programming context, the subject of computation can be the actual interpretation of the program1 (it is called metacode or metaprogram). One important point of re ective programming is that it can dynamically adapt (if possible at runtime) the program interpretation | or base-level objects semantics | via modi cation of the meta-level information. Since an interpreter has already constructed a signi cant amount of meta-level information, extending the interpreter to be re ective only involves adding support for exposing the meta-level information to the base-level program, allowing the program to in uence the decision process of the interpreter and a ect its own behavior through modi cation of the meta-level information and mechanisms. In the case of compilers, the meta-level information that is constructed at compile time is seldom kept beyond the compilation process | except sometimes some debugging information or built-in 1 In other words, the re ective computation is computing an aspect of how to interpret the program. Here, we are adopting a sightly more general point of view than an object oriented re ective architecture.

25

structures like virtual tables for classes. However, the meta-level informations are implicit and can not be altered. Thus, adding some re ection to a compiled language entails maintaining the metalevel information beyond the compilation process and also embellishing the generated code with the appropriate links to the meta-level information that controls the behavior. However, keeping all the meta-level information beyond the compiling process would imply as much overhead at run-time as a re ective interpreter, consequently loosing all the performance bene ts of a compiler. That is why some recent trends in MOPs consist in bringing the re ection process as close as possible to the compilation time or even during the compilation time [Chi96].

3.4.2 Examples and Applications To reduce the rei cation overhead, a simple approach in run-time MOPs based on a compiler is to only reify what is really needed by the program. For instance, the Iguana approach (which is a C++ based MOP), allows the user to implement the MOP that best meets her/his needs. Iguana o ers very ne-grained facilities for selecting at compile time, the run-time MOP that should be used by a class or even instances or methods. The problem of these kinds of approaches is that they force the programmer to think about the kind of meta-level information he needs to implement his base program. This kind of approach, generally called ne-grained MOPs | it can also be found in CodA [McA95], Apertos [Yok92], Muse [YTM+ 91], and Actalk [Bri89] |, provides a complex and sometimes dicult to use meta interface (implicit, explicit and inter MOP [Zim96]). Some more transparent techniques has been studied in CLOS or 3-KRS like MOPs. Those \intelligent" techniques, are trying to nd out at run time if it is worth executing the meta program or if the program can use shortcuts to obtain the same results. They are generally based on partial evaluation based algorithms. However, because partial evaluation techniques are very dicult to implement, those techniques are not very used or are explicitly implemented by the meta programmer (see the memoization in section 5.3). Another recent trend is to let the meta-information entirely at compile time and to give facilities to write clear compile-time meta-programs. The advantage of these compile-time MOPs | OpenC++ v2 [Chi95], MPC++ [IHS+ 96] | is that they imply no overhead at run time because they perform all the metaprocessing at compile time. However, since those MOPs cannot dynamically change the program behavior at run time they are mainly used to perform powerful syntax extensions for the compiler and seamless link to runtime libraries.

3.4.3 Re ection Time vs. Re ection Type As one can expect, there is sometimes a link between the time the re ection occurs at (runtime or compile time) and the kind of re ection the MOP is mainly designed for. We try here to clarify this relation. Most of the time, behavioral re ection, that deals with computation issues, is entirely done at runtime and rei es very dynamic computational events such as method invocation, slot access or entity creation. For example, OpenC++ version 1 MOP can be considered as pure behavioral re ection. However in practice, the pure behavioral re ection is mixed with structural runtime re ection or is brought close to compile time for performance matter. For example, MPC++ [IHS+ 96] is a C++ compiler allowing the base program code rei cation (here it is more behavioral than structural re ection). Structural re ection can also be done at runtime (CLOS, Smalltalk) but some examples of compile-time structural re ection can be found in the open compilers MOPs (OpenC++ version 2). The compile-time or run-time criteria seems a quite blur criteria to characterize MOPs. In spite of this, it is still a relevant indicator for certain kind of characteristics like performances and the nature of the meta programming. Indeed, behavioral re ection at compile time will consider very compile-time concepts such as parse trees. Moreover, the indisponibility of typical run-time information like variable values makes the compile-time behavioral re ection less powerful and especially less easy to program than the run-time behavioral re ection. 26

3.5 The Meta-Interface Type

3.5.1 Object-Based vs. Class-Based MOPs

Chiba [Chi96] puts metaobject protocols types of interface into two big families: the object-based MOPs and the class-based MOPs. This classi cation seems relevant because it is easy to distinguish class-based from object-based meta interfaces and also because this implementation choice implies many other properties to the meta-level architecture. In class-based MOPs, metaobjects are designed to be metaobjects for classes (metaclasses) so that it exists a true instantiation link between the metaobjects and the baseobjects. In this context, metaobjects can easily be integrated in a consistent, simple, uniform and unique execution level. This naturally leads to true metacircular architecture like Smalltalk or CLOS (see gures 2.5 and 5.3). On the other hand, in an object-based MOP, metaobjects are metaobjects for objects. It means that there is naturally a distinction between the metaobjects for objects and the classes of the baseobjects. Metaobjects are related to baseobjects through a metalink that is not an instantiation link. This kind of approach eventually leads to having two levels of interpretation. A default interpretor, that interprets the classes level, and a meta-level interpretor |composed of the metaobjects|, that interprets the base level (see chapter 5 for more details).

3.5.2 Re ection and Classes

We would like here to discuss the di erent degrees of metalevel architecture explicitness. In objectoriented languages, classes can be seen as a meta level of the program they implement. But what kind of re ection (structural or behavioral) do they provide? In fact, classes provide both kinds of re ection by determining the structure of an object (attributes and relationships) and its behavior (depicted in its methods). Conceptually, the class acts like a metaobject in a sense that, when a baseobject calls a baseobject method, the object-oriented language transparently upcalls the class to lookup and execute the method. In performance-oriented languages like C++, this mechanism is built-in. Classes do not really exist at execution but the compiler has instantiated for each class the well-known virtual method table used to implement dynamic method calls. Since we cannot access classes at runtime (unless we use RTTI), classes in C++ are compile time metaobjects and if we really want to talk about the C++ class MOP, we would say that it is an entirely implicit one. In Smalltalk, classes are considered like objects and are instances of metaclasses. They can be accessed at run time. For example, we know the attributes name by asking to the metaclass. Furthermore, we can change the structural representation of an object during the computation. Notice that to the classes point of view, Smalltalk metaclasses mainly stand for behavioral re ection metaobjects. Indeed, one can change the behavior of a class by coding the new method of the attribute accessor method. On the contrary, to the objects point of view, metaclasses stands for structural re ection metaobjects, because changing the way to code attributes is a kind of structural re ection. This last assertion illustrates the diculties one can encounter to di erentiate the two kinds of re ections. In fact, a behavioral re ection towards a class level can be a structural re ection toward the object level. However, in Smalltalk, metaclasses are generated in an automatic way and there is one metaclass for each class ( gure 3.2). This comes from the fact that Smalltalk metaclasses are almost implicit entities. They are built-in but they can be represented as objects in the system because Smalltalk uses some tricky metacircular implementation means to do this [FJ89]. The ClassTalk [LC95] project claims that it would be interesting to control the classes by real explicit metaclasses [BC89] ( gure 3.3). As a consequence, ClassTalk metaclasses can be seen as true SmallTalk classes, allowing programmers to classify (with the inheritance mechanism) the behavioral representation of their classes in generic metaclasses. Generally speaking, in a class system | that allows the programmer to structure, reuse and classify |, it is easy to realize structural re ection by expliciting the metaclasses (metaobjects for classes). The degree of explicitness can be a simple behavioral re ection metaobject (Smalltalk metaclasses: gure 3.2.) or another class system (ClassTalk metaclasses: gure 3.3.). Explicit metaclasses systems like ObjVLisp [Coi87] and ClassTalk raise the problem of metaclasses composition [MMC95]. In fact such systems identify and classify into metaclasses some classes prop27

Meta_Car allocMem callMethod

Meta_PLane allocMem callMethod

Car

Class definition level.

Plane

break accelerate

For each new class, a new metaclass (metaobject for class) is automatically and implicitly created. The metaclass inheritance graph follows the class inheritance graph.

takeOff land

speed : int

aCar

Object level.

aPlane

speed : 70 : instanciation link

Figure 3.2: Structural Re ection Implementation with implicit metaclasses. erties like the fact that a class has a unique instance or that it is an abstract class. Since metaclasses can be composed through the inheritance relation, some conceptual or implementing incompatibilities might happen.

3.5.3 Re ection and Objects

A simple way to achieve behavioral re ection is to consider that a metaobject only controls a baseobject through computation-event MLIs (Meta Level Interception) [Zim96]. In this case the metaobject is eventually a runtime metaobject for object. The advantage of these kinds of MOPs is that they are easy to implement. Since metaobject can be seen as regular objects controlling other objects, they can be de ned and programmed with regular classes. In this context, the MOP implementor just needs to make the link between baseobjects and metaobjects by inserting some meta-level upcalls in the base code to achieve MLI | e.g. by parsing the original code with a pre-compiler.

3.6 Conclusion Finally, we can characterize and classify MOPs upon the following criteria:  The type of re ection (behavioral oriented / structural oriented).  The way the MOP addresses the metacircular2 issues (not addressed / tower of meta levels / true circularity).  The time when the re ection occurs (at runtime or at compile time).  The type of interface (class based / object based - implicit / explicit).  The granularity (one metaobject per baseobject or more ( ne grained)). 28

Class

InstanciableClass

AbstractClass

allocMem callMethod

The metaclasses hierarchy is explicit and totally independent from the class hierarchy. It allows the user to classify all the kind of classes with specific criteria (functional or implementationnal)

Vehicle break accelerate Class definition level.

speed : int

Plane Car

aCar

takeOff land

Object level.

aPlane

speed : 70 : intanciation link

: inheritance link

Figure 3.3: Structural Re ection Implementation with explicit metaclasses.

29

However, a relevant functional classi cation seems to put metaobject protocols into two main families: the object-based MOPs and the class-based MOPs [Chi96]. Indeed, recent trends consist in using the object-based MOPs to implement either runtime behavioral re ection for non-metacircular and operating system oriented re ection while the class-based MOPs are mainly used to implement open languages and metacircular systems. The next chapter classi es some existing MOPs with these criteria.

2 We brie y introduced metacircular architectures in 2.3.3. However we did not discuss them in this chapter because we address them in chapter 5.

30

Chapter 4

A MOP Classi cation 4.1 Introduction This chapter describes some well-known and representative MOPs. We try, if possible, to discuss them in regards to the classi cation criteria we described in the previous part, that is to say the type of re ection, the type of interface, the time of re ection, the MOP granularity and the way the MOP addresses the metacircular issues. We especially focuss on the interresting points of those MOPs and their direct applications. Those description do not try to be complete for each MOP but all the description put together are quite representative of MOPs implementations, properties and use in a general approach. In the conclusion (4.8), we summarize those descriptions in a classi cation table describing them through some relevant criteria.

4.2 OpenC++ v1 OpenC++ v1 [Chi93] is a object-based MOP that re ects runtime base object behavior by upcalling the meta level (MLI) when three kinds of run-time base events occur (see gure 4.1):  Method invocation  Object construction  Attributes access (read or write). The OpenC++ v1 compiler parses the initial code where some classes and methods are tagged with a "re ect" keyword. For each tagged method call, the precompiler adds some C++ code that calls the metaobject method to reify the method invocation instead of the initial method (see gure 4.1). This meta-level method can execute some code and can re ect the initial method by calling a special method of the MetaObj class (every metaobject is an instance of this class). Since the "re ect" keyword takes a category for parameter, the metaobject can di erentiate the kind of meta algorithm to process. For example, a base programmer can categorize a method to be a "write" method and another one to be a "read" method. By this way, the metaobject reacts di erently for the two kinds of methods and can implement a consistency protocol. The class X shows an example of base-level programming in OpenC++ v1. The instances of this class are re ected by class M instances. // MOP reflect class X : M; class X public: X(); //MOP reflect: int func(int); //MOP reflect(write):

f

31

gives the hand to the ordinary compiler 2 Metaobject OpenC++ v1 compiler

reifyNew reifyMethodInvocation reifyAttributeAccess

Ordinary C++ compiler 3

1 adds meta-level upcalls

compiles

reflect (execute ordinary action) anObject

new anObject()

anObject

anObject.aMethod()

aMethod anObject.anAttribute()

anAttribute : int

Figure 4.1: OpenC++ v1.

g

int update(int); private: ...

Notice that the update method calls the re ect method with the write category. Thus, the metaobject M can check the category and process the rei cation in consequence.

f

class M : public MetaObj public: void Meta MethodCall(Id method, Id category, ArgPac& args, ArgPac& reply) if(category==write) ... else ...

g

g

f

g

f

g

f

This feature makes the OpenC++ v1 MOP well suited to seamlessly implement some distributed utilities like atomic types or some frameworks for distributed computing. For example, the authors present an additional MOP for distributed computing in [CM93a] called the Object Community Framework. In this case, upcalls to the meta level are added by the compiler at compile time. Note that the new rei cation can be seen by some people as a very generic interface for run-time structural re ection (since it allows the class to construct a di erent object that the de ned one). However, in practical cases, this interface is too simple to realize clear and powerful structural extensions. Another interesting thing to notice in the OpenC++ v1 MOP is the compile-time attachment of metaobjects. This means that the classes that are not tagged with the re ect keyword are not rei ed at compile time. In this case, there is absolutely no rei cation overhead. This feature is available in many run-time MOPs that are implemented with compiled languages (e.g. Iguana). 32

MetaObject eventMethodEnterVoid(Object, EventDescMethodCall) ...

metaprogram calls

attachObject(Object) registerEventMethodCall(Object) continueExecution(Object, EventDescMethodCall) ...

implicit meta calls

implicit base events internal interactions normal services (base interface)

meta interface

Reflective Java Virtual Machine

Figure 4.2: Metaobjects and JVM interactions in MetaJava.

4.3 MetaJava MetaJava (or MetaXa) is a very interesting evolution of OpenC++ v1 principles. Indeed, to implement the re ective architecture for Java, the designers have rewritten their own Re ective Java Virtual Machine (RJVM) by adding to it a meta interface [GK97b]. This meta interface, that provides some primitive access to the RJVM implementation (like functions to attach the objects to the metaobjects, execute the base methods or the re ected methods, ...), is called by the metaobject and in uence the virtual machine behavior [KG97]. Those functions are implicitly called by the standard metaobject class (that furnishes the JVM meta interface as protected methods). Possible MLIs are de ned as events (like enter-method, load-class, create-object ) and are passed to the metaobject during the baselevel computation (to its public interface, see gure 4.2). Another important point of MetaJava is that it does not implement the behavioral metaobjects as objects but as classes. This comes from the fact that, in the JVM, they can access classes at runtime (the JVM is an interpreter and not a compiler). For this reason, the authors decided to implement the behavioral re ection into classes. However, since a base object can be re ected in di erent way, they introduce the notion of shadow classes [GK97b]. Each object of a certain class is associated with a shadow class which is the exact replica of the original class in terms of structure (same attributes and methods), but can di er in a behavioral way, that is to say the kind of event sent to the associated metaobjet. MetaJava has been successfully used to implement some real-time actors in Java [GK97a]. One should also notice that, contrary to OpenC++ v1 and Iguana, the metaobject attachment can be done at run time, thus providing a more exible and well-separated meta-level code.

4.4 Iguana Iguana [GC96] is a run-time object-based MOP for C++. Its particularity is to be very ne-grained. Indeed, a base object can be controlled at run time by up to 23 metaobjects (MClass, MCreation, MDeletion, MInvocation...). The main idea of Iguana is to let the programmer instantiate his own MOP for each baseobject. Thus, Iguana provides some compile-time features to describe the run-time MOP to be instantiated. The very ne-grained MOP allows the programmer to reify exactly what he needs in a special context. This technique of ne-grained selecting can be seen as a very complete extension of the OpenC++ v1 compile-time attachment and categories (see section 4.2). 33

The compile-time features provided by the Iguana pre-compiler allow very complete MOP declaration. A MOP declaration consists in combining the di erent kind of 23 available metaobjects so that the base object using this MOP can bene t from the de ned re ection features. This MOP declaration is composed of declaration sections. The dependent section forces the compiler to also reify the rei ed aspects of all the MOPs the currently de ned MOP depends on. The local section de nes a one-to-one link between objects and metaobjects (contrary to the shared section). The shared section tells that a metaobject is attached to all the objects that use the MOP (n-to-one link). Finally, the global section links a metaobject to all the existing base objects. For example, a MOP declaration could be: protocol MyMOP : SuperMOP

f

g

dependent: FundamentalMOP; local: Object myPrivateMetaObject; reify Class : MClass myPrivateClass; reify Invocation : MContextSwitch; shared: Object objSharedByMetaLevel; global: Object objGloballyShared;

4.5 CodA CodA [McA95] is a run-time object-based MOP implemented on Smalltalk. It is largely oriented to facilitate the distributed objects implementation and run-time behavioral changes (especially in the communication aspect). Since a base object is controlled by a set of seven metaobjects (Send, Accept, Queue, Receive, Protocol, Execution, and State), CodA is relatively ne grained. When no metacomponent is speci ed, a CodA object has exactly the same behavior as a standard Smalltalk object. However, the user can change the behavior of a base object by de ning a new type of metaobject for a base object. For instance, to change the Queue behavior, the programmer can de ne a SandardQueue (that implements a Queue interface) as:

f

StandardQueue : implements Queue nextFor (Baseobject b) return queue.next(); enqueue (Message m, Baseobject for) queue.add(m);

f

g

f

g

g

A typical metalevel con guration for message sending is shown in gure 4.3. In this example, one can see that each one of the seven previously depicted metaobject plays its role except the State metaobject. Indeed, the State metaobject is more structural oriented and is not needed in such a behavioral-oriented example.

4.6 Apertos Apertos [Yok92] is an operating system based on a re ective framework for constructing an objectoriented large scale OS. In the adopted model, a system object is supported by a group of metaobjects that the authors call a metaspace. Apertos deals with metacircularity issues by saying that each metaobject composing a metaspace is an object and by introducting metaspaces for metaobjects. Thus, objects and metaobjects are represented within a meta-hierarchy that can be entirely user de ned. This metahierarchy can be compared to a tower of meta levels but in a more exible sense since metaobjects can be shared between several metaspaces (see gure 4.4). The Apertos MOP can be classi ed within the object-based behavioral-oriented MOPs. However, since it is largely operating system oriented, it has some interesting particularities like providing 34

Queue Protocol

3: queue(M) 2: accept(M)

5: next()

Accept

Send

6: methodFor(M) Execution

Receive

1: send(M)

7: execute(method)

4: receive() receiver baseobject

sender baseobject

Figure 4.3: Sample metalevel con guration with CodA.

object A

object C

object B

segment metaobject

directory metaobject protocol metaobject

name2 metaobject

name1 metaobject

file metaobject

object / metaobject

pager metaobject

metaspace

disk metaobject

Figure 4.4: Sample metaspace con guration in Apertos. 35

support for object migration. In Apertos, migrating an object means changing its metaspace. Because the metaspace represents the system internal representation of the object, migrating an object can be used, for instance to store an object on a secondary storage. The following pseudo-code makes an object explicitly requesting its metaspace to transfer itself to the secondary storage metaspace:

f

object::example method dest = mymeta.find(secondary storage); mymeta.migrate(dest);

g

Another point that is interesting to notice is that Apertos provides some features to check the compatibility between metaspace that allows the object to know what kinds of services will be available on the new metaspace. For example, on a portable computer, the metaspace could be partially compatible with a classical workstation. Apertos can be compared to the Muse re ective operating system. However, Apertos provides a re ective generic framework with non- xed meta interface (user-de nable) while Muse de nes some xed metaspaces that provides a basic extendable object-oriented concurrent computing model. Contrary to more classical object-oriented OS such as Amoeba [TvRvS+ 90], Chorus [RAA+ 90], and Choices [RJC88], they encourage concurrent object-oriented programming thanks to the object / metaobject separation that allows the system programmer to implement concurrency at any granularity level. Moreover, those re ective operating systems are well suited to implement adaptable policies since metaspaces control the behavior of lower metaspace objects.

4.7 OpenC++ v2 OpenC++ v2 is not a real evolution of OpenC++ v1. It is in fact a totally di erent MOP because, contrary to OpenC++ v1, it deals with structural re ection. It is a metacircular class-based MOP that could be consider to be the direct descendant of the CLOS MOP |we do not present the CLOS MOP here since we use it as an example in chapter 5. However, the main di erence is that OpenC++ v2 MOP runs only at compile time. As a consequence, the overhead caused by the rei cation process happens only at compile time and not at runtime. Another direct consequence is that OpenC++ v2 re ection is only done at a language level, making meta-programming quite dicult compared to simple behavioral re ection meta-programming. The OpenC++ v2 meta level is mainly composed of two metaclasses, one for the classes (StandardClass ), and one for the members of those classes (Member ). Those metaclasses allows code translation operations with some help of introspection and self-modi cation functions. Imagine we want to program a RecordedClass that records each new instance in an array (see the \RecordedObject " example in 5.2.2 to see the di erences with a run-time approach). In a compile-time approach, the meta-programmer have to think in term of source-to-source translation. What we have is:

f

class Point public: int x,y;

g

f

void f() ... Point *p = new Point; ...

g

What we want is:

f

class Point public: int x,y;

36

Run-time meta level definitions SandardClass

SynchronizedObject

translateNew translateMethodInvocation translateAttributeAccess translateClass ...

RTMOPClass

reifyNew reifyMethodInvocation reifyAttributeAccess

RTMOPMetaclass

Car break accelerate

PersistenceClass

speed : int

Compile-time meta level (metaobjects for classes)

Base-level classes

In this example, the persistence aspect of "Car" is reified at compile-time and the synchronization aspect is addressed by a run-time metaobject at run time. compile-time metalink (instance-of in OpenC++v2) run-time metalink (implemented by the RTMOPClass and RTMOPMetaclass metaclasses) inheritance link

Figure 4.5: A Mixed Approach the OpenC++ v2 Generated Runtime MOP.

37

g

f

void f() ... Point *p = (history[n++]=''Point'',new Point); ...

g

What we need is a metaclass that can translate the rst piece of code to the second one. In OpenC++ v2, this class looks like this:

f

class RecordedClass : public Class public: Ptree *TranslateNew(Environment *env, Ptree *header, Ptree *new op, Ptree *placement, Ptree *type name, Ptree *arglist) return Ptree::Make1 (``(history[n++]=\''%p\'',%p)'', type name, Class::TranslateNew(env, header, new op, placement,type name, arglist));

f

g

g

This metaclass rede nes the function that translates the new statements of the base program and change this statement so that it gives what we need. The nal code is normal C++ code and is compiled by a standard compiler. The nal program implies absolutly no overhead at runtime (no more than an hand-coded solution). Moreover, if behavioral re ection at run time is really needed, OpenC++ v2 allows the users to program their own runtime MOPs ( gure 4.5).

4.8 Conclusion In this chapter, we give an overview of some existing MOPs, insisting on their particularities and direct applications. Since we cannot do this for all MOPs, we now shortly present a more complete list of MOPs, describing them through some relevant criteria (see the next tables). MOP re ection type interface type re ect. time metacircular impl. OpenC++ v1 behavioral object-based run time none MetaJava behavioral class-based run time none CodA behavioral object-based run time none Iguana behavioral/ structural object-based run time none Actalk behavioral object-based run time none Apertos behavioral object-based run time metalevel tower Muse behavioral object-based run time metalevel tower MPC++ structural/ behavioral language compile time none 3-KRS structural/ behavioral object-based run time none CLOS structural class-based run time metacircular Tiny CLOS structural class-based run-time metacircular EuLisp structural class-based run-time metacircular STk structural class-based run-time metacircular OpenC++ v2 structural class-based compile time metacircular OpenJava structural class-based compile time metacircular ClassTalk structural class-based run time none Smalltalk behavioral class-based (implicit) run time metacircular ABCL/R metaspaces AL-1/D behavioral object-based run-time metaspaces 1

Make creates a new parse tree. See [Chi96], page 40 for more details.

38

MOP OpenC++ v1 MetaJava CodA Iguana Actalk Apertos Muse MPC++ 3-KRS CLOS Tiny CLOS EuLisp STk OpenC++ v2 OpenJava ClassTalk Smalltalk ABCL/R AL-1/D

impl. n.mo attacht C++ 0-1 CT* C* Java 0-1 RT* Smalltalk 7 C++ 0-23 CT P* Smalltalk 9 C 0-N RT 0-N RT C++ ? Lisp 1 Lisp 1 Scheme 1 Lisp 1 ? Scheme 1 ? C++ 1 Java 1 Smalltalk 1 Smalltalk 1 N MM* C++ N MMRF*

typical application distributed/atomic objects implementation distributed/atomic objects implementation distributed computing active objects implementation adaptable policies to provide optimal OS services C++ semantics and syntax extension Lisp semantics extensions (a little syntax) CLOS self-extension, Lisp-based OO languages simple CLOS-like MOP for teaching purpose EuLisp self extension transparent RT libraries links, C++ extensions transparent RT libraries links, Java extensions class properties classi cation and composition experimentation plateform with re ective facilities distributed computing complete controlling features (OS oriented)

*: CT=compile-time metaobject attachment. RT=run-time metaobject attachment. C=categories.

P=protocol de nition. MM=multi-model. MMRF=multi-model re ection framework. n.mo=number of metaobjects. attacht=attachement.

39

40

Chapter 5

Object-Oriented Re ective Languages 5.1 Introduction In this chapter, we focus especially on re ective languages. Those MOPs are more dicult to implement eciently because interpreters or compilers tend to need very ne-grained re ection. Re ective languages address the re ection problem in a more general and conceptual point of view and are, by this way, more deeply re ective than the system oriented MOPs that focus on the rei cation of some particular operating system concepts. Thus, re ective languages provide a very interesting context for re ective architecture implementation issues and the work done in this area can be easily applied to OS-oriented MOPs |despite OS-oriented MOPs are much more advanced for adaptative policies and distributed object implementation. In the next sections, we discuss some design strategies of object-oriented re ective languages by taking the 3-KRS [Mae87b] and CLOS examples. With CLOS, we especially study the metacircularity implementation issues.

5.2 Object-Oriented Re ective Languages 5.2.1 Overview Early representatives of such languages are CLOS [KdRB91][Ste90] and 3-KRS [Mae87b]. As we will see later, their design strategies di er in many points. A great deal of object-oriented re ective languages have explored various applications of the meta architecture. For example, ABCL/R is a parallel language that allows the programmer to customize the default scheduling policy. We can also mention here Ferber's language, RbCl [IMY92], the MIP for C++, and BETA (re ective version).

5.2.2 The 3-KRS Example

Description

3-KRS [Mae87b] is designed mainly for customizing the default behavior of the language on demands, rather than implementing some new abstractions on top of the language. It is an object-based re ective language in the sense that each base-level entity such as objects and messages is associated with a special object called metaobject. This metaobject is the meta representation of the base-level entity. Calling a method of the metaobject allows the programmer to obtain some meta information of the entity | that is to say, method names, attributes types, etc. Like 3-Lisp, 3-KRS also provides some built-in meta functions to re ect the base level. For example, the metaobject can call a base object method by invoking a particular meta function. 41

The \RecordedObject" example

Let's take the concrete case of a programmer who needs to change the new semantics so that it can now record the classes which had actually been instantiated in a variable called 'history'. He needs to annotate his classes with the right purpose metaobject (here RecordedObject ). Note: Despite 3-KRS and many re ective languages are Lisp-based, we will translate all the code of our examples in a C++ based pseudo-code. We believe it makes it clearer for many system programmers who are not familiar with the uniform Lisp syntax. class Point class Rect

f

f

g

x y : meta RecordedObject; top bottom left right : meta RecordedObject;

g

Here, the class RecordedObject is a metaclass for both the classes Point and Rect. It is de ned as follows: class RecordedObject : public MetaObjectForObject

f

g

f

createObject (expression, environment) class name = expression[1]; h = environment.envRef ("history"); environment.envSet ("history", h + class name); MetaObjectForObject::createObject (parse tree, environment);

g

Now we can write: >history.print();

fg

>p = new Point; >r = new Rect; >history.print(); Rect, Point

f

g

This method overrides the default createObject method de ned in MetaObjectForObject. It is called for each new expression (for example 'new Point '). It rst gets the class name of the currently created object (it is the rst item of the expression). Then it gets the value of the variable named 'history' through the built-in 'envRef' function. Then, through another built-in function (envSet ) the metaobject modi es the 'history' value by appending the class name to the old value. Of course, it also has to call the default createObject function that e ectively creates the object.

5.3 Implementation Issues for E ective MOPs Systems like 3-KRS need sophisticated implementation techniques to be really e ective. This property comes from the fact that the natural way to implement a non-metacircular system with two independent execution levels is to use two distinct interpreters. The rst interpreter executes the second interpreter, while the second interpreter executes base-level functions. In this implementation, meta functions are regarded as part of the program of the second interpreter ( gure 5.1). This double interpretation maintains the causal connectivity between the base level and the meta level, but it signi cantly decreases the execution speed of the base level program even though no meta functions are used. As described in the next sections, some complicated techniques of optimization are used to get around of this performance problem: partial evaluation, memoization, and currying. Improving the execution speed in such systems consist in compiling or memoizing as much code as possible | and especially the meta-level code | so that there is no need anymore to jump to the meta-level interpreter (that is very slow). For example, 3-KRS uses a partial evaluation technique consisting in preevaluating the environment and the expression so that no total switching to the default 42

interpreter interpret/compile

meta functions interpret

base-level program

Figure 5.1: The default non-metacircular implementation.

interpreter switch

default interpreter

interpret/compile

meta functions

interpret/compile

interpret

base-level program

Figure 5.2: An improved non-metacircular implementation.

43

interpreter is needed. The compiler will in fact statically specialize the eval functions so that the metacode be more ecient | for instance, in teh previous example, the envSet function should be compiled the rst time it is called to reduce the interpretation overhead. This approach has been studied by a few researchs including [Ruf93][CM93b][MMAY95], but implementing this technique is very dicult in practical languages such as C++ for which no compiler using partial evaluation has been developed yet. A more simple technique is sometimes used by programmers to optimize their code by hand (it is not speci c to metaobject protocols). This technique, called memoization, consists in explicitly compiling the code of the curently evaluated function. However, this technics is bad for source code clarity.

5.4 Metacircularity and Class-Based MOPs The origin of the metacircular architecture can be found in Smalltalk-80. In fact, Foote reported the use of the metacircularity in Smalltalk-80 in [FJ89]. Also, CommonLoops [BKK+ 86] should be noted as an early metacircular language. But CLOS is the rst language to totally deal with metacircularity. Furthermore, the metacircular architecture is found in ObjVlisp [Coi87], Classtalk [BC89], EuLisp [BDKP92], STklos [Gal], and OpenC++ v2 [Chi96]. In classical re ective languages like 3-Lisp [Smi82][Smi84] or 3-KRS [Mae87b], despite the meta and the base level are written in the same language, they are in fact two di erent instances of the same language. It means that at the beginning, the system may seem (arti cially) metacircular because the two languages are strictly identical but as soon as the meta programmer extends the base language, the meta and base languages begin to di er. This phenomena happens because the meta-level program is in fact interpreted by a built-in interpreter that cannot be extended. So, to modify the meta-level programming concepts, you need to add another interpreter that will be able to interpret the metalevel the same way the meta-level interprets the base level. Thus, to bene t from the extension done at a meta-level, you need to add another meta-meta-level and so on. The point is that each added level implies either run-time overhead either diculties for compile-time optimizations. Class-based MOPs can be implemented to be truly metacircular. The classical implementation of metacircularity is obtained by assuming that, in the system, the metaclass object is the metaclass of itself. Thus, when a new base class is created, the system instantiates the standard metaclass that is itself a class and when a new metaclass is instantiated, the system also instantiates the standard metaclass ( gure 5.3). Practically, in a true metacircular system, each customization a ects the base level and the meta level. In other words, the meta-level of the meta-level is the meta-level itself. Beyond solving the in nite tower of meta levels issue, this approach also simpli es the meta programming by reducing the number of meta-accessor to the base level (see the RecordedObject examples in sections 5.2.2 and 5.5 for further explanations). On the other hand, this approach can introduce some in nite regression problems (like instances created before their classes or method non-well-founded recursion path). Section 5.6 discusses those problems by taking as an example the CLOS MOP approach.

5.5 The CLOS MOP Example CLOS was the rst system to use the term Metaobject Protocol (MOP) to indicate a meta architecture or a re ective language as the 3-Lisp's one. Particularly, MOPs mean programming interfaces for customizing the language. The use of the word protocol instead of interface comes from Smalltalk. The particular feature of CLOS is that it is a metacircular system. To keep metacircularity while letting programmers avoid a circular de nition, the CLOS MOP uses a class system for controlling the scope of language customization. This kind of MOP is often called class-based because customizations are controlled by class metaobjects and not metaobjects. In the CLOS MOP, customization is applied only to particular classes and their instances. Thus, if programmers carefully distinguish classes, they can avoid circular de nition. Suppose that a meta-level program alters the behavior of a class Point. Since the CLOS MOP is metacircular, the programmer may use the customized class Point in the meta-level program but can still avoid circularity unless he explicitly programs it. Let's take the same example that we took to illustrate the meta programming in 3-KRS: the recorded instantiation example (see section 5.2.2). The way to customize the language is quite similar 44

Class

Metaclass

Point

meta_Metaclass

meta_Class

meta_Point

Metaclass

Class

Point

p0

p0

CLOS-like metacircular implementation (with explicit metaclasses) class

Smalltalk-like metacircular implementation (with implicit metaclasses)

final instance (object)

instance of

explicit metaclass

implicit metaclass

inherits of

Figure 5.3: Dealing with metacircular implementation issues: explicit and implicit class solutions. as shown below. class RecordedClass : public MetaobjectForClass

f

g

f

createObject (init args) class name = self.name; history = history + class name; MetaobjectForClass::createObject (init args);

g

Since the CLOS MOP does not provide the metaobject for an object, the program de nes a new metaclass. In fact it is simply a new class for class metaobjects (a metaclass is a class). Note that because it is a metaclass, self.name returns the name of the class exactly like metaclasses in Smalltalk. Thus, you do not need the base code expression to know the class name. Because of metacircularity, there is no explicit (syntaxic) distinction between base-level programs and meta-level programs in the CLOS MOP. The two kinds of programs are written in the same language and run under the same runtime environment. They may even coexist in a single function or method. Therefore, metaobjects can use the base-level primitives to access the base-level data as the objects can do. Hence, contrary to the 3-KRS code where the metaobject accesses the history variable through the built-in meta functions envRef and envSet, the metaclass can in CLOS, directly access the history variable like any other base object. Beyond avoiding gratuitous di erences between levels, the metacircular CLOS MOP gives a few other advantages. First, the meta architecture is easy and intuitive to learn. Since programmers can use base-level primitives to access base-level data from the meta level, they do not have to learn many built-in meta functions unless they need to access meta-level data. For example, to access the class name of an object, they can call the self.name accessor. Another advantage of metacircularity is execution performance. Indeed, metacircular systems make it intuitive and straightforward for implementors to develop an ecient interpreter and compiler. 45

interpreter switch

default interpreter

interpret/compile

meta functions

interpret/compile

interpret

base-level program

Figure 5.4: A metacircular implementation. Indeed, if you look at gure 5.4, you can see that a metacircular implementation naturally leads to a switch between the default interpreter and the meta program, thus providing the same kind of implementation than a non-metacircular re ective language using a partial evaluation technique (see section 5.3).

5.6 Metacircularity Implementation Issues in CLOS Metacircular systems like CLOS frequently have to deal with several kind of vicious circularity; e.g. objects that must exist before they can be created or in nite regression. In fact, we can classify the circularity issue into two distinct general problems [KdRB91]:

 Bootstrapping issues that are involved with how to get the system up and running in the rst

place.  Metastability issues that have to do with how the system manages to run, and to stay running even while fundamental aspects of the implementation are being changed.

5.6.1 Bootstrapping Issues

In the CLOS MOP, because it is a metacircular system, the class metaobject StandardClass is a class which is an instance of StandardClass class itself! Thus, the problem comes from the fact that you cannot instantiate a class with a class that is not yet created. So the system has to solve this chickenand-egg problem by temporally breaking the metacircularity and instantiating the SandardClass class with another means that instantiating StandardClass. In Closette [KdRB91], the general initialization tasks are the following: creating the initial class hierarchy and de ning the standard method of those classes. The insight underlying the bootstrapping technique is that there are only a nite number of initial metaobjects, and the values of their slots can be gured out in advance and hand-coded by the MOP implementor. In fact, the real challenge is to nd a way to create those initial objects without having to write too much code that will be used for bootstrapping purpose only. For example, if we create the StandardClass class metaobject by hand, we can use it to instantiate the other bootstrapping objects while minimizing the coding e ort. A more vicious bootstrapping circularity is the one that exists between some classes and some methods. Indeed, method invocation (dynamic binded functions with a lookup mechanism) cannot work until 46

most of the system is initialized. Generally speaking, metacircular systems bootstrapping code carry out all the calls to the methods without using the lookup mechanism until all classes and methods have been created.

5.6.2 Metastability Issues

Contrary to the normal closed implementations, it is a trick to keep on running a metacircular implementation. Because the system is extendable, there is potential for spectacular failure modes if certain situations are not properly anticipated. Let's take the slot accessing example to illustrate this | \slot" means \attribute" in the CLOS terminology. Typically, when a base object (instance of a base class) wants to access a slot value, it calls the slotValue method passing itself and the slot name. You can see below the implementation of the slotValue method and all the methods that it should use. The slotValue method (of an Instance ) delegates the work consisting in nding the slot to his class (MyClass is a built-in accessor called class-of in CLOS).

f

value Instance::slotValue (slot name) return MyClass().slotValueUsingClass (self, slot name);

g

Then, to return the slot value, the class needs to locate the slot. This is performed by another method of the standard class. value StandardClass::slotValueUsingClass (instance, slot name) ... slotLocation (slot name); ...;

f

g

To locate the right slot, slotLocation will eventually use a method returning the e ective slots of the class. Slot StandardClass::slotLocation (slot name) ... classSlots (); ...;

f

g

To do this, classSlots simply asks the value of one of the well known attribute e ective slots of the StandardClass object (which is viewed as an instance of the StandardClass class) |e ective slots is a StandardClass slot that contains the slots description of the instances of StandardClass (classes in general).

f

StandardClass::classSlots () slotValue ("effective slots");

g

But now we are back to slotValue, that will be evaluated in the same way as the original. What we have is a non-well-founded recursion path, that would cause the implementation to loop indefinitely. However, this loop can be broken in slotLocation by special-casing the slot named "e ective slot " of the class metaobject for StandardClass. That ensures that the special call StandardClass::slotLocation("e ective slot") returns the known and xed location of this particular slot of this particular class metaobject without recourse to further slot accesses. The fact that StandardClass sits at the top of all the classes is a crucial property of the MOP and assures that the in nite loop is e ectively broken in any case. In fact, myClass().myClass().[...].myClass() must be the StandardClass class metaobject.

5.7 Conclusion Recent approaches (CLOS, OpenC++ v2) show that true metacircular systems could be easier to implement eciently while providing better extensibility and uniformity in the involved concepts. Indeed, 47

the extension of the meta level is done by itself (instead of another meta level in non-metacircular approaches). Thus, there are no exchange of data between interpreters and no context swapping needed when accessing an extended metaobject: all the metaobjects evolve in the same run-time level. However, this assumption eventually leads to some problems of knowing who should nally perform a given task |in a metacircular system, a metaobject could ask to its metaobject (itself) to perform a task and so on. CLOS gives some kinds of solution to break the in nite regression by assuming that the root metaobject is unique and performs a nal task. However, since this solution implies a unique metaobject for a given object, [CKL96] proposes a solution called the MetaHelix to allow separation of concerns within a single metacircular metalevel.

48

Chapter 6

Conclusion This part presents the Metaobject Protocol approach which is a clear and elegant way to achieve separation of concerns in object-oriented languages and systems. It appears that this implementation technique is mature enough to address real-world problems in an ecient way and in many practical elds. In operating systems and communications, MOPs are more and more used to implement adaptable objects so that they provide more ecient services for the clients at runtime. This feature is allowed by re ecting some system implementational aspects in some metaobjects. In this case, the causal connectivity is mainly implemented by upcalling the meta-level on well-de ned events. However, for performance sake, this type of re ection is voluntary limited to some aspects and most of the research consist in nding the best interface to reduce the rei cation overhead. In re ective languages, the provided MOP re ect the interpreter or compiler, so that we can change its behavior by extending and adding some semantics to the base language. These kinds of environments perform a very complete re ection that needs optimization techniques to work ne. Recent researches in re ective languages pointed out that metacircular systems are more easily extendable and more ecient. However, this technique is not totally mature yet and only begins to explore the multimetalevels metacircular implementation which is crucial for powerful separation of concerns (like in the tower of meta levels approach). In re ective languages, the causal connectivity is maintened by the interpreter (or the compiler) itself by considering metaobjects as part of the interpreter (or compiler) program. Finally, it seems that those two tendencies in re ection by MOPs, that leads to di erent implementation means, are complementary and address di erent issues. To our mind, the rst one is more oriented towards behavioral re ection and the second one towards structural re ection. We think that a combination of those two approaches could bring out the bene t of addressing structural re ection at compile time (at a language level) while addressing behavioral re ection at runtime (at a system level). In fact, metaobject addressing structural re ection at a language level could behave like a \decider" that could decide which aspects of the program should be implemented in a re ective way at the system level, for instance, to implement adaptable services.

49

50

Part II

Re ection for a Distributed Programming Model Implementation

51

Chapter 7

Introduction Programming environments such as CORBA [OMG95] or DCOM [Gri97] associated with an objectoriented language as C++ or Java provide some basic mechanisms to realize remote method invocations on objects and to accept dynamic binding in an open universe. This classical model allows to easily write distributed client/server applications. Our project is to extend this model toward a distributed algorithmic model where the distributed application is a program that is processed by a group of objects. By this way, we de ne a programming model that provides a set of mechanisms tted (distributed type, class, instantiation, invocation, condition and, iteration) to programmation in a distributed environment. In this environment, the previous six concepts are the extension of the usual mechanisms of the programming object model (type, class, instantiation and invocation) and of the algorithmic statements (if condition and while loop). They apply to the group of objects taking part in the realization of a distributed application. In our model, we use the term group of objects to point out the behaviors of the distributed application. The mechanisms of our programming model are implemented in a re ective level with OpenC++ v2 [Chi95] metaclasses. Our goal is that the basic level of the distributed application keeps a near similar syntax to the one of a centralized and sequential program and that meta levels implement aspects related to distribution. In [Led97], re ective techniques and Metaobject Protocols (MOPs) are experimented to deal with the implementation of an ORB such as the proxy presentation. The proposed solution allows to reify an invocation and then to swap between a CORBA remote invocation and local invocation without changing any line of code (only by dynamic metaclass change). In [FNP+ 95] and in [GGM95], they also propose some re ective schemes for fault tolerance, secure communication and group-based distribution. They don't propose a global distributed algorithmic model. However, despite MOPs are more and more widely known as a framework for re ective languages (see the CLOS MOP) and systems, it appears that designing an ecient MOP can be extremely dicult. This diculty is mainly caused by the rei cation process that makes the performance of the program decreases as its clearness increases (separation of concerns). Recent approaches are trying to solve the problem by moving the rei cation process as close as possible to compilation time [Chi96]. The problem of a pure compile-time approach is that the meta level only rei es compile time concepts, loosing the bene t of knowing some runtime information (e.g. an attribute value). As a consequence, compile-time meta programs are very dicult to write and do not re ect the run-time meta algorithms but the way the base program has to be translated to implement these algorithms. In the Run-Time MOP (RT-MOP) approach [Chi93][GK97a][McA95][OIT93][Yok92], for performance reasons, the rst idea of the CLOS MOP [KdRB91] (that is to say, designing a totally re ected and extensible language), is replaced by a more limited approach consisting in upcalling an attached metaobject when some kind of runtime event occurs on a baseobject (e.g. method invocation, attribute accessing in Open C++ v1). Those MOPs generally provide some features to dynamically choose the re ected concepts (see the ne-grained MOPs [McA95][OIT93]), thus limitating the runtime rei cation process overhead. However, the RT-MOP solution has two major drawbacks. First, it allows structural re ection [Fer89] in a limited and a non-metacircular way [Chi96]. Second, the meta program is obscured by the meta-level xed interface |which can be very complicated for ne-grained 53

MOPs. Since our nal goal is to allow ecient and clear distributed programming language-level features, we need to keep the advantages of the two approaches by designing a meta-level architecture combining RT and CT-MOPs. Thus, we allow clear meta-level distributed algorithms at runtime and powerful language-level translation at compile time. Our proposal is to process structural re ection at compile time and behavioral (or control) re ection at runtime. To achieve this, we implement all the distribution structural features in OpenC++ v2 compile-time structural-metaobjects and the distributed algorithms themselves in run-time control-metaobjects. The RT-MOP is itself implemented by some compile-time metaobjects and only rei es behavioral aspects of the program. For this reason, it only rei es basic runtime events like method invocations. Moreover, because we need metacode as pure as possible, we decide to hide the RT-MOP behind some event interfaces that are used to link the base level to a user-de ned meta interface. We also try to deal with run-time automatic metaobject composition. We call this approach an Event-Based RT-MOP (EB-RT-MOP). This part is divided as follows. In the next chapter, we develop our distributed programming model. We describe the group types and group classes and we de ne the di erent semantics of the group instantiation and the group method invocation mechanisms. Then, the distributed statements are presented. Chapter 9 depicts the architectural organization of the system and introduces the events in a RT-MOP approach. Chapter 10 presents our EB-RT-MOP, illustrates its use with the example of a transaction application and gives some details about the implementation of our MOP. Chapter 11 sums up our work on metaobject composition within the EB-RT-MOP. Finally, chapter 12 concludes and provides some trends for future works.

54

Chapter 8

Distributed Programming Model 8.1 Introduction Since our project is to extend the classical distributed programming model, we de ne a programming model that provides a set of distribution-oriented programming mechanisms such as distributed type, class, instantiation, invocation, condition, and iteration. Our distributed programming model can be seen as an additional middleware layer between a distributed environment such as CORBA, and a programming language such as C++. Because we adopt a re ective approach, each layer rei es some mechanisms of the inferior layer and provides some additional features. Basically, we de ne three layers: the CORBA layer, the distributed programming model (DPM) layer and the base layer (see gure 8.1). The rst one provides all the functionalities of a CORBA environment (in fact, this is a glue layer to a CORBA ORB | ORBaccus [Obj98] in our case). The DPM layer provides the features presented in the remainder of the chapter. The base layer gives the program to perform. Finally, some interfaces (i.e. the elements rei ed by the MOP) are de ned between each layer. The MOPs are implemented with an event-based run-time MOP presented in the next chapters and detailed in [PS98b]. CORBA layer MOP interface

DPM layer MOP interface

Base layer

Figure 8.1: Our distributed architecture organization. This chapter is divided as follows. The second section de nes group types. The third section discusses the group class and the group instantiation semantics. The fourth section deals with method invocation on a distributed object. Then, section 5 presents the distributed statements semantics of our DPM.

8.2 Group Types In our model, a distributed application can be compared to a program that is processed on a set of distributed objects. It provides a service that can be expressed by a type that we call a group type. 55

For instance, a distributed diary application provides two primitives of service: one to arrange the date of a meeting and another to check that a time period is free:

f

Interface diary void fixdate(in date free period); boolean isfree(in date begin period, in date end period, out date free period); ;

g

As one can expect, this group type in a distributed universe is not implemented in the same way than a type in a centralized environment. For example, xing a date requires to coordinate the di erent components of the distributed diary.

8.3 Group Class and Group Instantiation The group type is implemented on several sites by objects that are member of one or several classes. We call group class the implementation associated with the group type. A group class is composed of several fragments that are distributed on the di erent sites participating to the distributed application. Each of them is implemented with a base object and a metaobject. Among other things, the metaobject manages the group instantiation mechanism. The base-level object provides the group membership and upcalls the group instantiation mechanism. In the base level we limit the interference in the code to the group composition (the instantiation hosts) and to the group instantiation call. We de ne three steps in the instantiation mechanism. First, all the group object instances are registered in a directory of the distributed objects that we call a group directory. For each group object, we store its logical name, its type, and the binding with the implementation. In a second step, the group object is created. A group object is composed of several fragments. Several schemes of distribution can be implemented. For instance, the data can be replicated, partitioned or distributed in accordance with an halfway scheme. The di erent fragments are installed on each participating site. After this installation phase, a group reference for the group object is created. In the third step, the binding between the object logical name and its group reference is done in the group directory. In the diary example, in the base level, we de ne the group G of participating sites and we instantiate the distributed diary. group G ("host1","host2"); diary &di = new diary(G);

The instantiation model of the distributed diary example can be the following. Each fragment handles the diary of one person. If everybody owns the same rights, then all the fragments have the same behavior and are instances of the same class. On the other hand, if some hierarchical relations exist between people and lead to management rules of the di erent diaries, then the group type will be implemented by several classes, and then di erent fragments.

8.4 Group Method Invocation In this context, the method invocation uses the group reference. In the distributed diary example, we write di. xdate(...). di corresponds to the group object reference instantiated on group G . This invocation is rei ed. The meta level processes it and checks if the reference is a group object reference. If so, di erent solutions (depending on design choices) may be implemented in the metaclasses. For instance, the xdate method needs to write the rendez-vous on all the fragments of the group object. So, the call needs to be broadcasted to all the fragments. In other cases, we may choose to invoke an access point for the group object.

8.5 Distributed Statements Our model contains two statements that can be used by developers to control the behavior of their group objects. These are the distributed condition and the distributed iteration. 56

8.5.1 Distributed Condition

This statement allows to choose some alternative behaviors depending on a condition value. In the most general case, the condition value is distributed between the di erent fragments. We distinguish in this statement two parts. The rst one corresponds to the condition evaluation, the second one is the execution of the alternative behavior (i.e. the action to process). For instance, in the diary example, the condition part allows to verify that a period is free and the alternative statement books the period for a rendez-vous. if (di.isfree(begin period, end period, free period)) di.fixdate(free period); else ... ;

f

g

Several degrees of parallelism can be implemented between the di erent distributed conditions that can be performed on a group object. For instance, we can decide that only one distributed condition is processed at once on the group, or that the di erent distributed conditions are processed in parallel, or that the distributed conditions are processed on a transactional way. In this last case, the condition evaluation corresponds to a read operation and the action execution to a write operation. The meta level implements one of these semantics.

8.5.2 Distributed Iteration

A group behavior or an action may be processed several times. In this case, we use a distributed iteration that is composed of a stop test and an action to iterate. For example, in the distributed diary, we may want to nd a date between several personal diaries. We give a rst period in which the rendez-vous can be taken, and we increase this initial period until we can nd a free period in it. while (! di.isfree(begin period, end period, free period))

f

g;

increase(end period); decrease(begin period);

di.fixdate(free period);

Each fragment processes the behavior while the condition is not enforced. The condition may be local to each fragment or global. The meta level manages this evaluation.

8.6 Conclusion This chapter presents a DPM that implies the rei cation of many programming aspects in some implementation metalevels. There are other instructions that can be rei ed within a group point of view (e.g. a ectation or distributed recursion). However this model is rich enough to experiment a re ective architecture implementation. This implementation is described in the next chapters.

57

58

Chapter 9

Architectural Model 9.1 Introduction In this chapter, we describe the architectural model we use to e ectively implement the previously described Distributed Programming Model. For eciency, most of the structural aspects are rei ed at compile time, within the OpenC++ v2 compiler. Especially, structural-dedicated metalevels are useful to transparently map our nal objects to an existing ORB and to implement the runtime re ective system | by adding some runtime meta information and mechanisms.

9.2 System Overview

To allow structural and behavioral1 re ection without complexifying the meta programs and with no useless overhead, our architectural model rei es structural aspects in compile-time metaobjects and behavioral aspects in run-time metaobjects. For compile-time part, we use the OpenC++ v2 [Chi96] compiler which provide a CLOS-like MOP [KdRB91] but at compile time. This open compiler allows us to map C++ objects to distributed objects, to add some language extensions (allowing distributed algorithmic programming) and to implement the behavioral re ection by linking the runtime metaobjects with base objects.

Structural Metaclasses

Control Metaclasses

Event Interfaces

Base classes

Run-time metalink Compile-time metalink Run-time events sending

Figure 9.1: Architecture overview. 1 For [Fer89] and [Mae87b], behavioral re ection should rather be called computational re ection | in opposition to structural re ection. We prefer the term behavioral or control re ection in order to bring out the idea of policies controlling the behavior of the base objects.

59

Since we already proceed structural re ection at runtime, it is crucial to notice that our run-time MOP does not need a complex interface (protocol) to implement what it is used for, i.e. control features. For this last reason, we do not choose recent approaches in RT-MOPs. Those approaches, sometimes called ne-grained approaches, consist in pre-de ning a set of metaobjects for each object. A metaobject is responsible for a well-de ned task (like sending a message) and is not always attached2 to the baseobject. By this way, the programmer can control some well-known aspects of an object and limit the overhead due to the meta level. The problem of those approaches [McA95][OIT93] is that they complexify the meta-level programming by introducing many sophisticated and generic metalevel concepts as sending queues or lockers that are not always easy to use and to modify in a real implementation problem. Moreover, they are often a means to realize limited structural re ection at runtime | that we already address at compile time. Another possibility for our RT-MOP is to use a very simple one like OpenC++ v1 [Chi93]. The main default of OpenC++ v1 for our approach is that you can attach only one metaobject to one base object. In real-world problems, you should be able to reify several control aspects independently (for example, the lock aspect and the authenti cation aspect must be implemented in di erent metaobjects3 ). In fact, what we want is an OpenC++ v1-like MOP that explicitly expresses the rei cation semantics and dispatches the run-time events to the metaobjects concerned by this semantics. Moreover, the meta interface has to be user-de ned so that the semantics of the metaobject can be exposed by its interface. To meet these needs, we decide to implement an implicit MOP. In fact, the meta programmer de nes the meta interface | that is a control purpose interface | by de ning a set of metaobject interfaces. Those interfaces are linked to the base objects through what we call event interfaces (for more details, see paragraphs 9.4.2, 10.7.2 and 10.7.3). Each event interface expresses the generic semantics of a control aspect and is composed of one or several events. Each base object linked to an event interface sends the events to the meta level. Symmetrically, a metaobject linked to an event interface can transparently receive and reify all the events of the interface. All the data needed for the meta-level controlling process are implicitly given to the metalevel (like the \this" parameter in C++) so that it does not appears in the meta-function prototypes (see for details section 10). This mechanism allows a meta-level separation of control aspects, clear and functional meta algorithms and easy metaobjects composition (see paragraph 10.3 and chapter 11).

9.3 Structural Re ection As we previously said, the structural re ection is assumed by OpenC++ v2 compile time metaobjects. Those metaobjects should allow two kinds of structural translations:

 a mapping from C++ objects to objects of a distributed system like CORBA,  some language extensions (keyword adding and overloading). An interresting point about OpenC++ v2 is that it is a true metacircular re ective language4 [Chi96]. It means that we can extend the language with as many meta levels as we need. In this particular case, the rst meta level should be the mapper metaobject while the second (the lowest) meta level is composed by the language extension metaobjects (see gure page 63). In the next paragraphs we give more details about the translation tasks performed by the two levels. 2 A metaobject is said to be attached to an object when it actually rei es one aspect of this object. This term is used in the MetaJava [GK97a] MOP. In some other MOPs, the authors prefer to say \is implemented by". We also use sometimes \metalink" to design the fact that an object is attached to a metaobject. 3 In OpenC++ v1, you can categorize the method and use the category to switch to di erent control policies but it is not a clear and easy way to program at the meta level since the programmer has to hand-code metaobjects composition and because the MOP interface doesn't point out the control semantics. 4 True metacircular re ective systems have been pointed to in [Chi96]. Some solutions to solve metacircularity issues in a true metacircular context are depicted in [CKL96].

60

9.3.1 Objects Mapping

For sake of simplicity, we decide to map our C++ objects toward CORBA objects. The advantage of using an existing CORBA system is that the mapping is almost entirely completed by the IDL compiler (stub generation, seamless implantation of the method invocation). Thus, the mapper metaobject only has to generate the IDL les while parsing C++ classes. Then, it runs the IDL compiler, links the base classes to the generated CORBA objects and, adds some code for the needed initializations to use CORBA objects (for example the ORB initialization). To map C++ objects to hand-coded distributed objects, the mapper metaobject would have to perform the same operations than the IDL compiler. However, this discussion goes beyond the scope of this report.

9.3.2 Language Extensions

OpenC++ v2 allows meta programmers to extend the C++ syntax by adding some new keywords or by changing the semantics of existing ones [Chi98]. For our distributed programming model, we need to add some group semantics to allow the programmer to instantiate some group object5 , invocate method on a group object, and achieve some distributed statements (e.g. distributed condition and distributed iteration) on a group object simply by using the usual C++ syntax.

9.4 Behavioral Re ection

9.4.1 Classical RT-MOP Approach

In the classical RT-MOP approach, the rei ed events are linked to the root metaobject interface. This interface provides some generic functions like:

 ReifyMethodCall (class name, object, method name, method args)  ReifyMethodInvocation (class name, object, method name, method args)  ReifyNew (class name, new args)

When you want to write a new metaobject, you inherit from this interface and (re)de ne the MOP functions. Some MOPs [GK97a] do not attach the metaobjects to the base objects at compile time but also provide another method to attach/detach them at runtime:

 Attach (class name, object, attach args)  Detach (class name, object, attach args)

The attach method allows the programmer to change the base object semantics at runtime. Moreover, when no rei cation is needed, it can detach (remove) the metaobject in order to minimize the meta architecture overload. Let us take the lock metaobject example. Imagine that you develop a base class BC that provides a method m(). Because concurrent accesses to m could occur on BC instances, you have to lock the object when a method call event happens. The usual way for doing this is to create a Lock metaobject that de nes the ReifyMethodCall method to synchronize concurrent accesses to the base object. For performance sake, the programmer can detach the metaobject. By this way, the programmer inhibits the rei cation process when he knows that no concurrent invocations to m can happen. The main problem with this approach is that the meta program is too generic to be easily understandable. Indeed, the lock metaobject interface is not related to its semantics. For example, we would rather call a method called Lock or Enter instead of ReifyMethodCall so that the lock object semantics appears more clearly for the user. The other problem consists in the fact that the ReifyMethodCall method is upcalled for each rei ed method of the base object. As a consequence, if the metaobject wants to lock the base object only in case of an m method call, it will have to test the method name and make the meta program dependent on the base program, mixing up the locking issue and the base event management issue.

5 A group object corresponds to a distributed application. It is designed by a base-level object and the associated group metaobject implements it with a set of objects called fragments.

61

9.4.2 The Event Based RT-MOP Approach

We need to give the programmer a simple way to link some base events to some meta level functions. Because we do not want upcalls to be explicit, we have to provide a glue layer between base and meta objects. To do this, we use OpenC++ v2 Compile-Time (CT) metaobjects [CKL96]. Those metaobjects can parse some syntaxic sugar like event declaration and linkage. The main language features provided by those CT metaobjects are the following:

 a way to link a base object to an event interface so that it can send the events described in the

interface to the meta level,  a way to link a metaobject to an event interface so that it can receive the events from the base level,  a way to say that a base object method can send one or more events to the meta level when it is called,  a way to say that a metaobject method can receive one or more events from the base level.

The event interfaces are de ned as virtual C++ classes (interfaces in Java). Each virtual method of those interfaces designs an event. An event can take some parameters described in the event prototype. To link an event interface to an object, you just give the interface name to the OpenC++ CT metaobject so that the compiler allows the object to send the events of the interface. Please note that we only say allow because the base object actually tries to send an event in only two cases. First, if you tag a method with the event name |in this case the event is sent at each invocation of the tagged method. Second, if you make an explicit call to the event |in this case, the base level raises an explicit event to the meta level. The same principles are used for the metaobjects (they can be linked to event interfaces and make some methods receive only some kinds of events). What di erentiates objects and metaobjects are their CT metaobjects. For objects it is a CT event sender and for metaobjects it is a CT event receiver. In fact, a pair of object/metaobject cannot send/receive events until they have not been attached together. So we provide a pair of Attach and Detach functions, like in the classical RT-MOP approach. This framework allows the programmer to de ne a minimum e ective RT-MOP dedicated to its application. In fact, we suggest that the programmer should program at the base and at the meta level in case he really wants to make ecient optimizations. It is one of the reasons why we nd so important to make the metaobject functional interfaces clear and user de nable. Finally a typical application could be implemented as follows (notice that there are in fact three compile-time meta levels and one run-time meta level): One can see on the gure above that run-time metaobjects and objects are implemented by compiletime metaobjects (see the next chapter for more details). In the next chapter, we focus on the EB-RTMOP implementation (i.e., the third meta level). Since OpenC++ v2 allows separation of concerns, we can describe each metalevel in a totally independent way6.

9.5 Conclusion This chapter presents a consistent compile-time and runtime re ection combination, the rst one is mainly used for structural changes of the C++ program and maps towards a runtime re ective architecture based on a RT-MOP. This MOP is completely described in the following chapters.

6 In fact, there could be some dependencies between the runtime metaobjects and the mapper metalevel so that we can dynamically parameter some carefully choosen aspects of the distributed objects. Again, this discussion goes beyond the scope of this report.

62

RUN-TIME META LEVEL AND BASE LEVEL CLASSES (Behavioral Reflection)

COMPILE-TIME META LEVELS (Structural Reflection)

Mapper meta level aMapper

Control1

Control2

Langage extension meta level aLanguageExtender EvntIntf1

EvntIntf2

EB RT-MOP implementation meta level RTRoot BaseObject

RTMetaobject

RTBaseobject

Compile-time metalink Compile-time event interface/class link Inheritance link OpenC++ class Event interface

Figure 9.2: An overview of the di erent meta levels (and their classes).

63

64

Chapter 10

The Event Based RT-MOP De nition 10.1 Introduction We now de ne the EB-RT-MOP functionalities by de ning some meta interfaces and their semantics. It is quite usual to distinguish three kinds of meta interfaces [Zim96] (see section 2.6 for details):

 the implicit meta interface (the base level implicit calls to the meta level),  the explicit meta interface (the base level explicit calls to the meta level),  and the inter meta interface (method calls between metaobjects). In the EB-RT-MOP approach, those three interfaces are entirely de ned by the programmer. Thus, we de ne a fourth interface that is named the default meta interface because it de nes the minimum generic functionalities to allow the meta programming. The di erent functions of the default meta interface can be classi ed in 3 types:

 the control functions that can be used to change the state of base and meta levels,  the introspection functions that allow a metaobject to manipulate its base object. It also allows the programmer to manipulate some Run-Time Type Information (similarly to the MIP [BKPS92]),

 the inspection functions that allow a metaobject to know about other objects. We rst present the default meta interface functions of our EB-RT-MOP. Then, with a transaction example, we explain how the programmer can de ne the previous three meta interfaces and thus create her/his own MOP.

10.2 Object and Metaobject Classes Object and Metaobject classes represent the default metaobject protocol. When the programmer wants to know what function she/he may call, she/he may look at the Object class and Metaobject class interfaces. In fact, as we will see later, every classes inherit from Object or Metaobject virtual classes | for the moment Metaobject does not inherit from Object but it could be useful to allow multiple meta level architectures. Those default interfaces are summarized in the following gure and described in the next paragraphs.

65

Metaobject reflect (Args& args, Ret& return_value) : bool base () : Object& is_explicit () : bool method_name () : String& method_ards () : Args& caller() : Object& class_name () : String& interface_names () : ListOf(String&)

Object attach (Metaobject& tobeattached) detach (Metaobject& toberemoved) meta (String& meta_class_name) : Metaobject& on_reflect (Object& caller, String& method_name, Args& args, Ret& ret_val)

Figure 10.1: Classes for RTobjects default interfaces.

10.3 Control Functions void Object::attach(Metaobject & metaobject)

This function is available for any base object. It attaches the metaobject to the current object. Several metaobjects knowing the same event interface can be attached to the same object. Conversely, only one metaobject of a certain class can be attached to the base object |for example, it would be useless to attach two instances of a lock metaobject (and reify twice the base object). When some metaobjects should receive the same event, the rst attached object receives it and the re ect function sends the event to the next metaobject so that all the metaobjects can treat the event sequentially (except in the case a metaobject does not call the re ect function). Notice that, thus, metaobject composition is performed in a generic and automatic way. The meta programmer should be aware of this feature to adapt the metaobject code (metaobject composition is studied in chapter 11). void Object::detach(Metaobject & metaobject)

This function unattaches a previously attached metaobject. void Object::on reflect( Object & caller, String & method name, Args & args, Ret & ret val)

This function is called by the metaobject to execute the base method method name.

10.4 Introspection Functions Object & Metaobject::base()

This hidden parameter references the base object of the current metaobject. Note: hidden parameters can only be accessed into metaobject methods and only on the current metaobject. For example, you cannot write a metaobject.base() because it is implemented like the \this" keyword in C++ (i.e. a hidden local parameter or variable). bool Metaobject::is explicit()

66

This function can be called to know if the current metaobject has been explicitly upcalled. In this case, the re ect method should have no e ect. In fact, this function is using the following method name and method args to know this. String & Metaobject::method name()

This hidden parameter is set to the name of the base method that is currently being rei ed (the one that sent the event). It is nil if the event has been explicitly sent to the meta level. Args & Metaobject::method args()

This hidden parameter is set to the values of the arguments of the base method that is currently being rei ed. bool Metaobject::reflect(Args& method args, Ret& return value)

This method has to be called by the meta level programmer when she/he wants to execute the original code of the rei ed base function. If re ect is called in a meta level method that has been activated by an explicit event, the re ect function has no e ect but returns true anyway. If another metaobject also rei es the current event for the same base object, the re ect function sends the event to it so that it can perform the adequate controls. Since we only achieve control rei cation and provide an automatic way to do it, we think that the EB-RT-MOP facilitate metaobjects composition. However, this issue is discussed in chapter 11. String & Metaobject::class name()

Allow the metaobject to know its class name. This function is used by the attach and detach function. ListOf(String &) Metaobject::interface names()

Allow the metaobject to know the set of event interfaces it supports. This function is used by the attach and detach function.

10.5 Inspection Functions Object & Metaobject::caller()

This hidden parameter references the caller object. Despite it is accessible from base level, it should only be used at meta level to implement some access control metaobjects. Metaobject & Object::meta(String meta class name)

This function returns the metaobject of a base object depending on its class name. If the base object is not attached to a metaobject of the mentioned class, the return value is nil. It should be only used by meta level objects. For example, the lock metaobject can try to reach the coordinator metaobject of the caller to know if its base object will be a transaction victim or not:

f

if (caller.meta(``Coord'')==nil) /* this is not a transaction */ else /* this is a transaction */

g

f

g

10.6 Programmer De ned Meta Interfaces In the EB-RT-MOP approach, the programmer has to de ne:  events the base level should raise,  interfaces of the metaobjects,  links between the base level and the meta level (through event receiver metaobjects). Those development steps implicitly de ne the three kinds of meta interfaces previously described in section 2.6. 67

10.6.1 Implicit Meta Interface De nition

An implicit meta interface function is de ned when the programmer links a object method with a metaobject method through an event. If the event is called xxx(...), the base function must be tagged xxx(...): and the meta function must be tagged on xxx(...):. The meta function then automatically becomes a implicit meta interface function because it is called each time the base function is itself called. Since the meta function is a metaobject public member, it can |like any meta member| access the base method description through the introspection interface.

10.6.2 Explicit Meta Interface De nition

Explicit meta interface functions are de ned in the same way that the implicit ones. Indeed, making an explicit upcall to the meta level consist in consciously raising an authorized event so that it can call the associated meta method(s). Explicitly raising an event xxx(...) from the o base object can be done in the base code like this: o.xxx(...);

Metaobjects can distinguish explicit from implicitevents by calling the introspection function is explicit(). Moreover, if a metaobject calls the re ect function in a explicitly called meta function, it has absolutely no e ect.

10.6.3 Inter Meta Interface De nition

The inter meta interface is simply de ned by metaobject interfaces. Indeed, a metaobject do not have to use the previous de ned interfaces to communicate with another metaobject (because they both belong to the same level). Considering this, a metaobject which has been implicitly upcalled because of a x() method call event can easily converse with the metaobject of the x() caller by writing: caller.meta(``C'').m();

Note that the metaobject has to know (at compile time) the class of the metaobject he needs to talk with. In this case, he calls the method m() of the caller's metaobject belonging to the class C.

10.7 The Transaction Example In this section, we illustrate the use of our EB-RT-MOP with the example of a transaction application. This inter-objects control scheme is one of the most used in a distributed programming environment. Its purpose is to prevent two client objects that concurrently access some server objects to incoherently modify their states. For simplicity sake, the transaction implementationthat we present here introduces a strong synchronization mechanism between the server objects (we use only one kind of lock). A more parallel implementation (with both a read lock and a write lock) would be more ecient but goes beyond the scope of this presentation of our EB-RT-MOP.

10.7.1 The Base Code

Because the kind of meta interface the programmer may want to de ne varies a lot in function of the application he needs to program, we are going to introduce a user de ned meta interface for a classical example: a transaction (between two objects). Suppose you have written the following simple classes:

f

class Account public: Account(float initval) val=initval; ; void add(float toadd) val+=toadd; ; void sub(float tosub) val-=tosub; ; float getVal() return val; ;

f

f f

f

g g

g

68

g

g

private: float val;

f

class Client public: Client(Account *a1, Account *a2) account1=a1; account2=a2;

g

f

transfert21(float val) if(account2.getVal()-val>0) account2.sub(val); account1.add(val);

g

g

f

f

g

private: Account *account1; Account *account2;

These classes can be used and instanciated as is in a centralize and secure environment. Now, suppose that you want to use the same classes in a distributed environment. Some problems of concurrency and fault recovery arise. In fact, you will probably need to execute the transfert21 function like a transaction. Consequently, you will need concurrency control for the sub & add methods. Let's see how to do this with a EB-RT-MOP approach.

10.7.2 De nition of Base Events At a rst step, the programmer has to decide what kind of event the base level should send to the meta level1 . In this case, we need to signal a transaction event for the client and synchronization events for the account. As we said in paragraph 9.4.2, the programmer has to de ne those events in virtual classes. Using classes allows him to put together all the events related to a kind of problem. Let's be simple for the moment:

f

class E Transac public: on transac(Object &, Object &)=0;

g

// transaction event

f

class E Synchro public: on enter()=0; on exit()=0;

g

// when entering a method // when exiting a method

E Transac is the interface of the client requesting the transfer of the Account objects. Each event (i.e., each method of this class) is pre xed by on . Then, the base objects methods that send the events (see paragraph 10.7.3) are tagged with the full event name (i.e., with the on pre x). 1 The event notion is very close to the notion of category, introduced by the OpenC++ v1. However, because we can classify them in event interfaces and add some parameters to the events, it is a more general and exible notion. Futhermore, since the event terminology can easily lead to misundertanding, we still sometimes use the category terminology.

69

10.7.3 Linking Events to the Objects

Now, we must associate the events to the base objects and to some methods2 . Please note the minimum changes needed (metaclass declaration and method tagging with events): metaclass Account RT Object(E Synchro); class Account public: Account(float initval); enter: exit: void add(float toadd); enter: exit: void sub(float tosub); float getVal();

f

g

metaclass Client RT Object(E Transac); class Client public: Client(Account *a1, Account *a2); transac(account1,account2): void transfer21(float val);

f

g

The transfer21 method is tagged with the transac event and the add and sub methods of the account are each tagged with two events (enter and exit ).

10.7.4 Meta-Level Programming

With the EB-RT-MOP, the meta-level programming is extremely simple. Indeed, because we are able to link some of their methods to some base events AND because we know we dispose of the introspection functions, we can freely program the meta interfaces with no care of the base level. First, the Coordinator metaobject. We decide we need a function to send a lock message to the transaction objects (sendLock ) and a function to release all the locked objects (releaseLock ). Finally, the doTransaction method performs the transaction once the objects have been locked. metaclass Coordinator RT Metaobject(E Transac); class Coordinator public: on transaction(o1,o2): void doTransaction(Object & o1, Object & o2); bool sendLock(Object &, Object &); void releaseLock(Object &, Object &);

f

g

Notice that only the doTransaction method receives the transaction event. In fact, the sendLock and releaseLock method are called within doTransaction. The event parameters must be precised in the event declaration and in the linked method. Those parameters must be named here so that the compiler can distinguish them from normal parameters (o1,o2). Moreover, remember that in a metaobject, you can always access base level informations by using the default meta interface introspection functions or hidden parameters (like base, method name...). Thanks to the default meta interface, you do not have to add or consider those kinds of parameters (contrary to the classical RT-MOP approach). For the Lock metaobject, we only have to de ne lock and unlock methods |we assume that lock returns the locker. A very important thing to notice is that within our approach, we do not need to add the caller object parameter to lock and unlock methods because it is automatically added by the compiler3 (as the caller hidden parameter). The entire example code is available in the paper we have submitted to the ECOOP'98 [PS98a]. You can add it anyway. If you name your parameter \caller" the compiler won't add a second one. On the contrary, if you choose another name, there might be redundancy between your parameter and the hidden parameter caller. 2 3

70

RT_Root add_caller (prototype) add_caller (invocation)

RT_Object

RT_Metaobject

add_listof_metaobjects () wrapp_methods () add_implicit_calls () add_explicit_calls () inherits_object () implements_object ()

add_base (prototype) add_base (invocation) add_dispatchers () inherits_event_interfaces () inherits_metabject () implements_metaobject ()

Figure 10.2: OpenC++ Compile-Time Metaobjects for our EB-RT-MOP Implementation. metaclass Lock RT Metaobject(E Synchro); class Lock public: on enter: Object & lock(); on exit: void unlock(); protected: Object *locker;

f

g

To be as clear as possible, we have chosen a very simple transactional policy. Our synchronization metaclass, called Lock, just authorizes the base object access to the locke r object. The full implementation of those two metaobject is available in our [PS98b] research report.

10.7.5 Access Semantics Changes with Minimum Coding

As we have seen it in section 10.7.3, we decided to synchronize the access to the Account object within the two methods add and sub. To achieve this, we made them both send the enter and exit events. However, when we look at the transactional method of the Client (see [PS98b]), we can see that the code: if(account2.getVal()-val>0) account2.sub(val); account1.add(val);

f

g

won't be a ected by a possible concurrent access to the add method on any of the two present objects (it is not the case for sub ). So, because we know the exact semantics of the client object, we can just synchronize the sub method and avoid a locking process in case another client only wants to credit the account. Note that this modi cation does not require any coding.

10.8 Implementation To implement our language level \glue layer", that is to say the parser that automatically implements the link between the RT base level and the RT meta level, we are using OpenC++ v2 [CKL96]. OpenC++ v2 is based on a compile time MOP that allows the programmer to extend the C++ language functionalities by reifying its language concepts at compile time. The meta program (that rei es the base code) is written in compile-time classes that inevitably exist only at compile time. 71

OpenC++ compiler RT_Metaobject

Coord

Metaobject

Lock

E_Transac

Coord

E_Synchro

E_Transac

RT_Object

Client

Lock

E_Synchro

Object

Account

Client

Account

Inheritance link Agregation link Interface (=virtual class)

RT metalink uselink (pass to the metaobject constructor)

Figure 10.3: OpenC++ v2 translation example from base code to C++ implementation (class-diagram translation). Although CT-MOPs are not very well suited to complex meta algorithm implementations, they are very useful and ecient to extend existing languages and implement regular frameworks like the EBRT-MOP. We next describe the needed compile time metaclasses to implement our framework. Since those CT metaclasses implement classes of some run-time object we pre x their names by \RT ". Implementing the EB-RT-MOP glue layer consists in implementing three CT metaclasses (metaobjects for classes). Figure 10.2 describes the basic functionalities of those classes. Their function prototypes will be implemented as private members of the nal CT metaclasses (we do not mention here the public default CT-MOP functions of OpenC++ v2 | translate... [Chi98]). Finally, the OpenC++ compiler uses the CT metaclasses to translate the original code (base + meta) into a set of classes linked each other with inheritance and aggregation links (see the classdiagram translation in gure 10.3). The main role of RT Root is to add to each method prototype a parameter \Object & caller ". This parameter is a hidden parameter because it is added at compile time. To preserve program symmetry, RT Root also changes each method invocation code by adding a this to the method parameters (it is always the rst parameter). The RT Object metaclass parses the base object classes. It inherits from RT Root and takes a list of strings in input. These strings are the event interface names and will allow RT Object to generate explicit functions prototypes and implicit upcalls to the meta level. RT Object also have to add the control methods (de ned in 10.3) to the class. These public methods are attach and detach. After adding the control methods, RT Object has to add to the base object class the inspection methods (de ned in 10.5) and the on re ect function that is called by the metaobject when re ecting the base method (for an implicit event activated method). The important translations performed by the RT Metaobject consist in adding introspection functionalities, i.e. adding 3 hidden parameters and 2 methods (see 10.4), adding a dispatch method for each event4 | those methods dispatch the events only to the tagged methods, adding an inheritance link between the metaobject and all the event interfaces it supports, adding a list of the interface names supported by the metaobject (used to implement the attach/detach functions), and nally, adding the class name method | it is especially needed to implement the meta inspection function. 4

As we will see in the next chapter, those dispatcher metaobjects can be related to wappers.

72

All these addings can easily be performed at compile time by the OpenC++ v2 compiler (see [PS98b] for all the implementation details).

10.9 Conclusion This chapter presented the EB-RT-MOP, that is characterized by its simplicity and exibility. This MOP is deeply user-con gurable through the event interfaces that can be used to categorize the rei ed aspects and explicitly add some user-de ned arguments to the rei cation process, speci c to the kind of problem that is being implemented (see the transaction example in section 10.7). As we will see in the next chapter, one can see our EB-RT-MOP computation model as a exible Wrapping framework. We will also work on metaobject composition issues within this framework.

73

74

Chapter 11

Runtime Metaobject Composition 11.1 Introduction Recent trends in re ective systems achieving run-time re ection show that programmers mainly need to change the system behavior in a limited way. In a completely object-oriented system where distributed objects interoperate by sending messages to each other interfaces, re ecting the way messages are processed or behave seems sucient to change the global semantics of the system. On the contrary, allowing the programmer to dynamically change the internal structure of the objects (like removing attributes or services) can be error-prone and lead to inter-operability failures (especially in wide opened environments like the Internet). Moreover, structural re ection at runtime leads to ineciencies because it forces the object implementation to maintain a run-time representation of its structure | rather that compile it (in some cases we are not able to get the source program). Since we admit we do not need full structural re ection at runtime, (structural re ection should rather be done at compile-time [Chi96] or at the con guration time), programmers should still be able to introspect compiled-objects structure, change their internal state (by (in)directly access some attributes), and add some interobject-communication control code. If some classical means for introspecting the object structure and changing its internal state are already furnished by some languages (java.lang.Class for Java [Sun97], the MIP for C++ [BKPS92]), the interobject communication control coding is entirely left to the programmer expertise. In this chapter, we propose a framework for easy separation of concerns between the functional code and communication controlling code. This framework is based on the EB-RT-MOP (Event-Based Run-Time MOP) we presented in chapter 10 (for more details, see [PS98a] and [PS98b]) and consists in wrapping objects with run-time metaobjects. The originality of our approach comparing to the previous work is to implicitly x some restrictive hypothesis to allow automatic metaobject composition, that is the key of exibility, reusability and true separation of concerns in opened and changing object-oriented distributed environments. This chapter is organized as follows. First, we introduce our model by comparing it with classical MOPs and Wrapping approaches. Then, in section 11.3, we describe the metaobject composition issue by using some general examples. In section 11.4, we propose a metaobject classi cation, in regards to composition relevant criteria. Finally, in section 11.5, we test our composition framework on some concrete examples before concluding in 11.6.

11.2 MOPs and Wrapping 11.2.1 Classical Approaches

In an environment where distributed objects interoperate, wrapping techniques can be used to control the way messages sends or arrivals are treated by the objects. When you wrap an object with another object, the original object is no more visible to the system. On the other hand, the wrapper object exports its interface so that the objects that previously communicated with the original object now communicate with the wrapper object. By this way, the wrapper can implement some control over the arriving messages. For example, a typical example could be to add some authenti cation features in 75

the wrapper so that the original object is called only if the authenti cation process succeeds. The wrapper object code should look like:

f

if (caller.authentificate() == OK) ... call the called method ... else throw(new AuthentificationError());

g

f

g

In the previous approach, the programmer of the wrapper has a lot to do. First s/he has to program a new object for the wrapper. This new object should have the same interface than the original object while di ering a little for adding some information in method parameters like the caller-object identi er (or, more generally, a capacity) for authenti cation purpose... In a hand-coded wrapping solution, the programmer also has to change the code of the caller object so that it calls the wrapper instead of the original object. However, though this solution provides a framework for some kind of separation of concerns, it is not easy to achieve it in a general and extensible way.

11.2.2 Why Do We Need MOPs?

MOPs are able to greatly simplify the wrapping programming by making transparent the wrapper object and thus achieving a better separation of concerns between the original code and the communication control code. Some works related with MOPs try to implement a automatic and extensible solution. In [WS98], they use the Java Re ection API to automatically change the caller code when a wrapper is loaded in a program. We present here a di erent approach based on a runtime MOP, consisting in considering that the wrapper is the metaobject itself instead of a special wrapper object | this special kind of metaobject is called a wrapper metaobject. In this approach, it is the re ective system that is responsible for the dynamic lookup of the wrapping code when a message is sent to an object. The advantage of this approach is exibility. In fact, since the wrapper lookup is processed by the system at runtime, there is a possibility for dynamically change, add or remove a wrapper metaobject. This kind of model is not new and has been experimented in many approaches like OpenC++ v1 [CM93a] (however, in OpenC++, metaobject attachment is done at the compile time) and MetaJava [GK97b]. Transparent wrapping with metaobjects can be done in almost the same way as classical wrapping through the rei cation and re ection mechanism of the re ective system. During the rei cation phase, the metaobject is looked up by the system and the adequate meta-level method is called. In our approach, the code of this method is considered as wrapping code. When the wrapper metaobject has performed the before code, it calls the re ect feature of the MOP to automatically call the initially called base-level method (i.e. the wrapped object code). When the base object has nished, the hand is given back to the meta level that can achieve some after treatment. In conclusion, the metaobject can be seen as a wrapper that surrounds the wrapped base-object by some before and after statement. In the code below, we take again the example of an authenti cation wrapper object. The main di erence between the classical approach presented in section 11.2.1, is that, thanks to the re ect call, the wrapper code does not depend any more of the wrapped object and can thus be automatically applied to several di erent objects. In other words, MOPs allow us to de ne new kind of wrappers, the wrapper metaobjects, that can be seen as non-specialized wrappers1 .

f

Class X : WrapperMetaobject void calledWhenACallOnBaseObject(...) if (caller.authentificate() == OK) // extra before code ... reflect(); // after code ...

f

f

1 One can ask how the parameters for wrapping are added. However, this is a MOPs speci c problem that is discussed in [PS98b].

76

g g

g

g

f

else throw(new AuthentificationError());

11.3 Metaobject Composition 11.3.1 Independent Composition In the EB-RT-MOP we have developed, the rei cation is a transparent but con gurable process in a sense that the programmer can categorize meta and base level methods in such a way that the rei cation will only occurs between methods of the same kind of category (in chapter 10, we describe how those categories can be highly user-de nable with the use of event interfaces ). For example, if an object implements a method m1 that needs to be synchronized, and a method m2 that modi es the object state and all its replica (the coherency protocol is implemented by the wrapper metaobject). Then the methods can be classi ed in di erent categories and be caught by di erent metaobjects.

f

Class O synchronized: void m1 (...) ... coherent: void m2 (...) ...

f

f

g

g

g

f

Class Y : WrapperMetaobject on synchronized: void lockObject (...) ... // do some mutex (wait) locked=true; reflect(); locked=false; ... // end of mutex (notify)

g

f

g

f

Class Z : WrapperMetaobject on coherent: void notifyReplica (...) ... // send the currently reified msg to replica reflect();

g

f

g

The previous example shows three classes. O de nes wrapped objects and Y and Z de ne two kinds of wrapper metaobjects respectively implementing some synchronization and some coherency management. When an O object is attached to an Y metaobject and a Z metaobject, the Y metaobject only rei es the m1 method calls while Z metaobject only rei es the m2 method calls. This mechanism allows easy and straightforward metaobject composition and increases the system

exibility since the two kinds of wrappers can be independently reused on any kind of object that uses the synchronized or coherent category | notice again that metaobject codes do not depend on the object they wrap. However, this example works ne because there is a total independence between the function m1 and m2 (more precisely between categories synchronized and coherent in class X ). We call this case an independent composition. In real-life problems, programmers may want to compose those metaobjects for the same method | it is a dependant composition case | either at compile-time or at runtime by metaobject attachment. For maximum exibility, reusability, and separation of concerns, this composition have to be possible without changing the wrapper metaobjects. 77

11.3.2 Dependant Composition A tricky example

Let us take three metaobjects x, y, and z respectively instances of classes X, Y, and Z, implementing authenti cation, synchronization, and coherency management (see the previous examples). What should happen if those metaobjects are attached to an instance o of the following class?

f

Class O all: void m1 (...)

f ... g g Note: the all category means that a call to m1 is rei ed by all the currently attached metaobjects, independently from their category.

The main issue of this example can be summarize in the question: how does the re ective system compose the x, y and z metaobjects so that the o semantics is changed the way the programmer expects it? We can found two main solutions in literature.

A First Inadequate Solution

The rst solution to composition problem is the tower of meta levels architecture (see the rst part). In this solution, each metaobject is considered as the metaobject of another metaobject so that calling a method on a metaobject is rei ed in the same way that it is done on a base object. Thus, we can make a tower composed of x -y -z -o (x is the y 's metaobject, y is the z 's metaobject z is the o 's metaobject). To us, this solution is not a good solution. First because it does not respect the rei cation semantics in a sense that each metaobject of the tower controls the o behavior instead of controlling its direct base object (for instance, in a normal re ective logic, x should implement authenti cation for y access and not o access). Second because of performance. Indeed, in the tower approach, the rei cation process is very much used, and, as anyone know, an ecient implementation of this process is very complicated to achieve. A consequence of this last drawback is that re ective systems using this approach limit the number of meta-levels and even prede ne the kind of treatments that should be done at a given level (like in the Apertos [Yok92] re ective operating system). Finally, this solution obviously lacks the exibility we need in a complex run-time environment.

A Second Inadequate Solution

The second solution consists in explicitly calling the di erent metaobjects. For example, x, when nishing authenti cation should call the y metaobject which calls the z metaobject. Then, z calls the re ect function. In this solution, the programmer can entirely control the way metaobjects are composed (it is exible). However, it lacks reusability and transparency because the metaobjects code depends on the other metaobjects. For example, if we want to insert a new metaobject between z and o, we have to change the z code so that z calls the new metaobject instead of re ecting to the base object. Moreover, the Z class is not usable anymore for the old con guration (that could exist somewhere else in the system).

Changing the Re ect() Semantics: a Mixed Solution

Since we want to keep the rst solution's transparency and the second solution eciency and exibility, our proposal is to use the re ect() function to transparently call the other metaobjects. In this solution, the re ect semantics is changed in the following way.

f

reflect (...) if (another metaobject in the attached list) call the function that matches the category else reflect base object

g

g

f

f

g

78

With this new semantics, one can see that:

 there is no need changing the metaobject code even if we add or remove a metaobject.  if the metaobject list is ordered as (x, y, z ), and if all the metaobjects are called, the e ect is

the same as a tower solution, and the way it is done by the system is like a manual composition solution.  metaobjects can be used with any base object.

This solution seems attractive because it allows the programmer to dynamically change the object semantics with a maximum exibility, reusability, and separation of concerns (transparency). However, since the programmer can do that, s/he has to face new problems of compatibility and consistency between metaobjects. For instance, if the metaobject list is ordered as (y, x, z ), the resulted semantics will be the same but if the metaobject list is (z, x, y ), all the replica will be updated before the base object is locked, obviously causing consistency problems. As a consequence, we have to add a mechanism to express how the metaobjects should be composed, that is to say, in what order. Another problem that could occur is a non-call of a metaobject that should have been called or a call of a metaobject that should not have been called. For example, if we insert a verbose metaobject in the list, the e ect of this object will not be the same with (v, x, y, z ) and (x, v, y, z ). In the rst case, the verbose metaobject will always be called. And in the second, it will be called only if the base object is e ectively called (because if the authenti cation performed by x fails, v, y, and z are not called and o is not re ected). In other words, in the rst case, v is called each time someone tries to use o, but, in the second case, v is called each time someone e ectively uses o. To solve those problems, we have to classify wrapper metaobjects with respect to the way they have to be composed with other metaobjects. We discuss this issue in the next section.

11.4 A Metaobject Classi cation 11.4.1 Composition Relevant Criteria

Many criteria should in uence the way metaobject should be composed with each other. In a rst overview:

    

does the metaobject always call re ect() or not? does it change the base object state? does it send messages to other objects? does it always have to be called? does it have to be called only if the baseobject is e ectively re ected?

If a metaobject can answer \no" to the four last questions | it means that it does nothing relevant for composition | we call it a re ector. If a re ector always calls re ect(), we call it a (pure) re ector, if not, a conditional re ector. It can exist a third kind of metaobject that would never call the re ect() function, but we suppose we do not want them. If the metaobject changes the base object state, it is no more a re ector but a base-modi cator (can be conditional or not). If it sends message to other objects, it is a system-modi cator | it can also send a message to the base object. If it always has to be called, it is an obligatory metaobject, and if it has to be called only if the baseobject is e ectively re ected, it is an exclusive metaobject. For example, the table below classi es the four metaobjects we have already talked about in this chapter (note that x is not obligatory because its non-call will not a ect future behavior, y is just a pure re ector because it is nor conditional, nor obligatory, and nor exclusive, z consistency protocol should only be called if the base object is e ectively re ected, and, nally, v can also be also be an exclusive re ector but it would change its semantics). 79

metaobject classi cation x (authenti cation) conditional re ector y (synchronization) re ector z (consistency) exclusive system-modi cator v (verbose) obligatory re ector The main di erence, in terms of composition, between re ectors and modi cators (should they be base, system or both) is that modi cators may have to be ordered in function of the modi cation sequence they achieve. For example, if sending to the base object the message sequence [m1, m2 ], does not give the same result than the [m2, m1 ] sequence (m1 and m2 are not commutative messages), then modi cators have to be ordered in the way the programmer wants it to be. In practical, this problem is a dicult issue to deal with because the meta-programmer is not always aware of the kind of changes performed by all the modi cator metaobjects and cannot say if a modi cator have to be executed before or after a given one. This cases could be solved in an automatic way by using state diagrams at a compiler level. However, since few languages provide this kind of features, the best solution is to leave this to the programmer expertise. Thus, we can regroup all the modi cator kinds into a unique family of orderable metaobjects. Orderable metaobjects are assumed to be manually or automatically ordered thanks to a ordering key or algorithm given by the meta-programmer. However, we assume that most of the time, non commutative operations are performed by the same metaobject so that modi cator metaobjects do not need to be ordered. In this case, we do not need to distinguish the modi cators from the re ectors anymore. In the next sections, we consider that all the modi ers are programmed in a commutative way. Thus, we only speak about re ectors metaobjects. Ordered modi cators will be treated in future works.

Note: this last assumption, reduces a lot the composition problem complexity. For instance, in our

approach, we cannot deal with a set of metaobjects that corresponds to di erent layer of a protocol stack (each one adds some information in the message, but this has to be done in an ordered way). This limitation can be seen by some people, used to reasoning with dependant layers, as a too important restriction. However, we think these messages and parameters modi cations by metaobjects have to be replaced by clean and object-oriented meta-level communications and protocols. We know this will not improve the system global performances, but this is the price to pay for exibility and reusability2 .

11.4.2 Ordering Relation

Next step of our classi cation consists in de ning a new ordering relation depending on the way metaobjects can be composed. For this, we de ne four operators:

 a  b means that a and b metaobjects are equivalent, that is to say, their order can be exchanged

in the metaobject list.  a = b means that a and b metaobjects are equal, that is to say, if one is executed, the other one is executed, and conversely.  a > b means that a metaobject must be executed before b (before in the list).  a < b means that a must be executed after b.

Operators  and = are symmetric, re exive and transitive. Operators > and < are transitive. For example, since two pure re ectors r1 and r2 can be exchanged in the list, r1  r2 is a true logical statement. Generally speaking, let us assert that a list of n metaobjects is attached to an object, then, this list can be split into 6 sublists: 2 We shall justify this in future works. Moreover, for performence sake, we should also think to provide some support for inter-dependant metaobjects. The important point, by now, is to provide a clean and trustful architecture for composition.

80

     

a list L(r) of nr re ectors (r1 , r2 , ... rnr ), with 0nr n. a list L(or) of nor obligatory re ectors (or1 , or2, ... ornor ), with 0nor n. a list L(er) of ner exclusive re ectors (er1 , er2 , ... erner ), with 0ner n. a list L(cr) of ncr conditional re ectors (cr1 , cr2 , ... crncr ), with 0ncr n. a list L(ocr) of nocr obligatory conditional re ectors (ocr1 , ocr2 , ... ocrnocr ), with 0nocr n. a list L(ecr) of necr exclusive conditional re ectors (ecr1 , ecr2 , ... ecrnecr ), with 0necr n.

Odering relations between elements of the same types can be written as follows (they are writable if the lists are not empty | nx 6=0).

     

8 (i, j)2[1; nr ]2, ri  rj 8 (i, j)2[1; nor ]2, ori  orj 8 (i, j)2[1; ner ]2 , eri  erj 8 (i, j)2[1; ncr ]2, cri  crj 8 (i, j)2[1; nocr ]2, ocri = ocrj 8 (i, j)2[1; necr ]2, ecri = ecrj

The two last cases are quite special. Because obligatory conditional re ectors (ocrs ) must always be executed (they are obligatory), they are related by the = relation. However, if one of their condition is evaluated to be false, the following ocrs will not be called (because the re ect() function will not be called). Thus, the only possibility to respect the ocrs properties it to x their maximum number of element to one. For the exclusive conditional re ectors, it is quite the same problem. However, in practical, we will see that we can, in some cases, accept more than one ocr and more than one ecr. Thus, since we are not able to x this by some simple rules, we will let this choice to the meta-programmer expertise. What is now interresting for nal classi cation is the ordering relation between any element of the lists (the inter-type relation). Because of transitivity property of the > operator, we can write (again if none of the lists is empty):

Inter-Type Composition Relation : 8 (i, j, k, l)2([1; nor ][1; nr ][1; ncr ][1; ner ]) : ori > rj > ocr1 > crk > ecr1 > erl This ordering relation between any kind of re ector allows us to automatically know how to correctly classify a new metaobject in the list. Thus, in our system, each metaobject adding will check the validity of this relation before inserting a new metaobject into the list. This feature matches our goals of exibility, transparency, reusability and separation of concerns we wanted to enabled.

11.5 Practical Examples Let us apply the previous result to the x, y, z and v metaobjects. In practical, the meta-programmer needs a way to de nes the re ector type. Because we use the OpenC++ v2 compiler [Chi95], we can easily extends the C++ syntax for simple parameterization (like adding a type descriptor). However, for better understanding, we present a multiple inheritance based solution where Obligatory, Exclusive, and Conditional classes can be combined to the WrapperMetaobject class (considered here as a re ector ). In this case, we can modify the X, Y, Z, and V classes declarations as follows (notice that we do not need to change anything else). 81

class class class class

X Y Z V

: : : :

f

WrapperMetaobject, Conditional ... WrapperMetaobject ... WrapperMetaobject, Exclusive ... WrapperMetaobject, Obligatory ...

f

g

f

f

g

g g

Since in our MOP, all classes for base objects implicitly inherit from the Object class, we can, dynamically call the Object::addMeta() 3 method on any object. addMeta exploits the result of the previous section to correctly insert the m metaobject into the lisof metaobjects list. To simplify the code, we assume that the Object class keeps the end and begin index of the ors, rs, ocrs, crs, ecrs, and ers lists in the listof ors, listof rs, listof ocrs, listof crs, listof ecrs, and listof ers structures (when one of the list is empty, the indexes are set to -1). void Object::addMeta (Metaobject m, int mode) if (m.isA(Conditional)) if (m.isA(Obligatory)) if (listof ocrs.begin!=-1&&mode!=force) // number of ocr already = 1! throw (new OCRAlreadyExist()); return; else listof metaobjects.insert(m, max( listof ors.end, listof rs.end)); return;

f

g

g

f

g

f

g

g

f

f

if (m.isA(Exclusive)) if (listof ecrs.begin!=-1&&mode!=force) // number of ecr already = 1! throw (new ECRAlreadyExist()); return; else listof metaobjects.insert(m, max( listof ors.end, listof rs.end, listof ocrs.end, listof crs.end)); return;

g

f

f

f

g

// it is a conditional reflector. listof metaobjects.insert(m, max( listof ors.end, listof rs.end, listof ocrs.end)); return;

f

if (m.isA(Obligatory)) listof metaobjects.insert(m, 0); return;

g

f

if (m.isA(Exclusive)) listof metaobjects.insert(m, max( listof ors.end, listof rs.end, listof ocrs.end, listof crs.end, listof ecrs.end)); return;

g 3

g

In this chapter, the attach function is renamed addMeta (because we sightly modify its semantics).

82

With this addMeta function, the x, y, z, v combination will always order the list as (v, y, x, z ) that is one of the coherent semantics of the combination (the possible other ones would have been (v, x, y, z ) and (y, v, x, z )). Notice that the verbose metaobject is called on each call even if the base object is not called. If the meta-programmer wants to sightly changes its semantics, he can de ne it as a exclusive instead of an obligatory re ector. Thus, the addMeta function will always tends to order the list as (y, x, z, v ) which also gives a good semantics for this kind of use (the other good solutions would have been (y, x, v, z ) and (x, v, y, z )). Imagine that we now want to add a new metaobject t, that introduces the following semantics:

\If the baseobject has been used N times, then, nobody else can use it."

This kind of metaobject is obviously a conditional re ector, and an exclusive one because it will certainly have to increment and test a call counter only when the base method is e ectively executed. In this case, the addMeta will do (v, y, x, t, z ). Had we preferred the semantics (for security sake):

\If the baseobject has been tried to be used N times, then, nobody else can use it."

Then we would have classi ed this metaobject as a obligatory conditional re ector and the metaobject organization would be automatically set to (v, y, t, x, z ) which is a semantics that prevents a client to call the object more than N times event if the authenti cation (performed by x ). On the contrary, a u metaobject implementing the semantics:

\If the current time is greater than a given time then nobody can use the base object"

has neither to be obligatory nor exclusive because the condition result is not in uenced by wether it is always tested or not. Thus, u is just a conditional re ector and the metaobject list is ordered as (v, y, t, u, x, z ), that matches the wanted semantics without breaking the previously set semantics. Imagine now that you need to implement the following semantics:

\If the baseobject has been tried to be used N times, then, nobody else can use it except the administrator user." . This metaobject is an obligatory conditional re ector. If we try to add it

to (v, y, t, u, x, z ) we can see that there is already an ocr (t ) so that the addMeta function will raise an exception and refuse the adding. In this case, the addMeta function is right to refuse this adding. In fact, if it had been accepted, this metaobject would had badly con ict with the t metaobject semantics. For this case, we propose to let the meta-programmer decide if s/he should cancel and keep the old ocr, force the adding (with the mode parameter of addMeta() ) or remove it from the metaobject list and replace it by the new metaobject. In an open environment with several meta-programmers, this feature implies authenti cation means for ocrs or ecrs removing or adding and an o/ecrs compatibility database for automatization and transparency purpose. This will be explored in future works. Finally, notice that one can break the o/ecr property by simply slitting a metaobject into two di erent metaobject. The rst increments a counter and is classi ed as an or. The seconds test this counter and is classi ed as a cr. This property is very interresting for us because it could bring more

exibility to the system. Future works will try to determine if such an operation is possible in most cases, and if we can provide a way to achieve it in an automatic way.

11.6 Conclusion In this chapter, we propose a run-time re ective model based on wrapping. Wrapping is achieved by special metaobjects | called wrapper metaobjects |, by reifying method invocation. However, since this re ective framework should be used in open environments like the Internet, it is crucial to provide maximum exibility, transparency, and reusability of the meta-level code, especially by allowing easy and reliable semantics wrapper metaobjects composition. With some restrictive assumptions, our solution is based on a simple and realistic metaobject classi cation that led us to determine automatic and semantics-reliable composition rules. Practical examples in section 11.5, showed that our framework is operational on many usable and concrete examples and can bring many advantages to run-time meta-programming. However, lot of work has yet to be done. Since we assumed that all the modi cators modi cations are commutative (see section 11.4), we should provide some support when it is not the case (however, this support should certainly be limited to exception adding if we want to be realistic). We should also nd out our framework limitations and advantage regarding to other compositional approaches like components models [KSC+ 98]. 83

Finally, this framework shows that, in an open environment, some metaobject special administration has to be done. This administration feature should prevent frequent redundancies and semantics con icts and eventually supposes some kind of meta-administrator, responsible of metaobject classi cation and checking. This is a wide issue we will tackle in future works.

84

Chapter 12

Conclusion This part introduces a distributed programming model for object-oriented applications. The main idea of this model is to provide some distributed extensions to the usual mechanisms of OO languages. Hence, we extend the notions of type, class, instantiation, invocation, if condition and, while loops. By this way, one can design and implement a distributed OO applications as a program performed by a group of distributed objects. The bene t of this approach is to seamlessly integrate some distributed aspects in a programming language. The six above mentioned mechanisms are implemented in a middleware layer between a CORBA ORB (OmniBroker) and a programming language (C++). We also present a framework that should allow us to eciently and clearly implement this distributed programming model. This framework allows the programmers to develop distributed applications and systems in a re ective way, achieving a clear separation between structural and behavioral re ection. The former is implemented by some compile-time meta levels written in OpenC++ v2 and the latter by some run-time meta levels written with the RT-MOP that we present in chapter 10 (this RT-MOP is itself written in OpenC++ v2). The compile-time meta levels provide some language extensions and some syntaxic sugar for a distributed environment such as CORBA. The run-time metaobjects implement some control functionalities on the behavior of base-level objects. For instance, we show in chapter 10, how the inter-object synchronization for a transactional operation can be provided by simply attaching control metaobjects to some client and server objects. The main advantage of this approach is that it clearly separates the functional aspects (implemented at the RT base level), the control aspects (implemented at the RT meta level) and the structural aspects (implemented at the CT meta level). The two RT levels are linked with some event interfaces (indeed virtual classes). The methods of the meta level can be either implicitly or explicitly called through this interface. Another important aspect of the EB-RT-MOP is to provide support for automatic metaobject composition. In chapter 11, we give a solution for this complex problem based on a simple metaobject classi cation. Despite metaobject composition is still a very open problem, we believe our framework provides a rst step toward an automatic solution that will ensure more exibility and reusability in re ective operating systems and middlewares. In future work, we will continue to develop the distributed programming model and the composition model and we will use the EB-RT-MOP to de ne some generic metaobjects for group objects and distributed programming. Thus, we will be able to nd out the framework ineciencies and correct them. We will use those metaobjects to de ne some classical applicative examples. The examples we choose are a distributed le system (system oriented) and a distributed scheduler (groupware application).

85

86

Part III

Practical Experiences with Re ection

87

Chapter 13

Introduction The goal of this part is to give an overview of the practical experiences we have done with MOPs and re ection during the DEA internship. Indeed, since we have now widely studied the existing MOPs and how we can use them in practical to build distributed applications, we must now try to implement some of the ideas we have speci ed in the previous part. We do not yet implement the EB-RT-MOP because we still want to think about metaobject composition and general performance problems. Moreover, because we intend to develop the EB-RT-MOP with an existing MOP, we have to test it before using it, to validate the possibility of developping this (quite complicated) framework. Because of the previously mentioned reason, the rst experience consists in developping the mapper metalevel described in chapter 9, that allows a transparent Mapping from C++ to Corba thanks to structural transformations. This experience allowed us to test the OpenC++ MOP and nd out its limitations. These limitations lead us to develop our own CLOS-like MOP in Tcl. In a recent future, this MOP will eventually be substituted to OpenC++ to implement the structural re ection aspects of the Distributed Programming Model discussed in chapter 8.

89

90

Chapter 14

Using OpenC++ v2 for Transparent Mapping on Corba 14.1 Introduction In this chapter, we aim to brie y show how one can use a compile-time MOP like the OpenC++ v2 to assume some automatic mapping from an existing programming language towards an existing middleware or operating system. The example we use here is taken from the re ective architecture we de ned in part II, and will be used to map C++ objects to ORBaccus [Obj98] distributed objects. Notice, that, conversely to other works, our goal is not to use re ective languages to implement an ORB [OMG95], but to seamlessly access this ORB. Thus, we will not deal with stubs or skeletons installation by metaobjects, because this step will still be performed by the IDL-C++ compiler of our target ORB. On the other hand, the metaobjects automatically generates the IDL description for the server objects, change the type of the base program objects, so that we can instantiate and call Corba objects and services, and implement and parameterize the calling and instantiation semantics.

14.2 The Mapper Metaobject Coding For coding the mapper in OpenC++ v2 [Chi98], we only need to change two aspects of the default Class metaobject. Thus, we mainly rede ne two methods: TranslateClass and TranslateNew (Initialize and FinalizeClass are just called at the begin and the end of the compilation to open and close the generated IDL le). In TranslateClass, we parse the class de nition (this parsing is simpli ed by the fact we can use introspection features), and for each class, we generate an IDL de nition that contains the operation prototypes (one operation for one method). The only real diculty is the type translation. The main translation rule is given by the ttt array (for Type Translation Table). But the CorbaMapper metaobject also has to know if the parameter is in, out, or inout. Since the C++ language [Sou91] is ambiguous, we adopt the following rules:  if the parameter type is nor a pointer nor a reference, it is translated as an in parameter.  if the parameter type is a pointer or a reference, it is translated as an inout parameter, except if it is const, then, it is translated as an in parameter. In TranslateNew, we have to change the new semantics, so that it creates or access a remote Corba object. This is done by adding a remote keyword to the OpenC++ compiler (in the Initialize function). The remote keyword takes a parameter that we can read at the compilation time and that identi es the remote object. Thus, the semantics we have chosen is the following:  if the remote object (identi ed by the remote parameter) already exists in the ORB, then we just get its Corba reference.  if the remote object does not exist, a Corba server is locally created and activated. 91

14.2.1 CorbaMapper.h #include "mop.h" #include #define TTT LENGHT 120 class CorbaMapper : public Class

f

public: static bool Initialize(); static Ptree *FinalizeClass(); void TranslateClass(Environment *env); Ptree* TranslateNew(Environment* env, Ptree* header, Ptree* op, Ptree* placement, Ptree* tname, Ptree* arglist ); protected: static ofstream *f idl; static void OpenIDL(); void GenClassIDL(); static void CloseIDL();

g;

static char *ttt[TTT LENGHT]; char *TTCxx2IDL(char *cxx type); char *TTCxx2Cxx(char *cxx type); int GetIDLPassingMode(char *arg); char *GetArgName(char *arg); char *GetArgType(Ptree *arg);

14.2.2 CorbaMapper.mc #include #include #include #include

"CorbaMapper.h"

extern "C" int system(const char *); extern "C" void *malloc(int); ofstream *CorbaMapper::f idl; char *CorbaMapper::ttt[TTT LENGHT]= "int","long","CORBA Long", "unsigned int","unsigned long","CORBA ULong", "short","short","CORBA Short", "long","long","CORBA Long", "unsigned short","unsigned short","CORBA UShort", "unsigned long","unsigned long","CORBA ULong", "float","float","CORBA Float", "double","double","CORBA Double", "boolean","boolean","CORBA Boolean", "char","char","CORBA Char", "octet","octet","CORBA Octet",

f

92

"strg","string","CORBA Char", "","","" ;

g

char modname[4]="DPM"; char idlfile[8]="tmp.idl"; bool CorbaMapper::Initialize()

f

g

RegisterNewModifier("remote"); OpenIDL(); return TRUE;

Ptree *CorbaMapper::FinalizeClass()

f

g

CloseIDL(); // copy the generated idl file only if different from // the old one. ... return nil;

void CorbaMapper::TranslateClass(Environment *env)

f

cout ToString()); cname[strlen(cname)-1]=0; if(Ptree::Match(header,"[remote % ]", &corbaremotecdr)) if(Ptree::Match(header,"[remote ( [%?] )]", &corbaremotename)) return Ptree::Make("(CorbaRTLib::OrbInit(),\n\ CorbaRTLib::BoaInit(),\n\ CorbaRTLib::InitNameService(),\n\

f

93

f

g g g

(CorbaRTLib::Exist(%p)?(cout string [48] length = 4

To prevent blanks, new lines, [], and $ interpretation, a radical way is to use curly braces fg. Thus, the simplest way to write the previous example is:

f

g

set v [48] ; puts stdout ``string $v length = [expr [ string length $v]]'' => string [48] length = 4

Curly braces are very useful when some command takes argument that are in fact blocks of code. For example, the if command takes an expression, a code block, and optional arguments for else or elseif. In spite you can use n or \", the safe and nice way to write an if is: if

g g

f

gf

$a == 0 puts stdout ``I cannot do that!'' else puts stdout ``Result is [expr 1 / $a]''

f

Notice that all the blocks in fg are destinated to be evaluated when the if command is evaluated. In the second and third block, the if command recursively calls the interpreter. In the rst block, if implicitly calls the expr command.

15.3.5 Procedures

One of the most important use of curly braces are when de ning procedures. The proc command takes three arguments, the name of the procedure, the arguments de nition, and the body of the procedure. When called, the proc command register a new command named by the procedure name. Because the body will be evaluated when the new command is called and not before, it is quite safe to use fg to prevent a previous evaluation of the body. For example:

f

proc inv a if $a == 0 puts stdout ``I cannot do that!'' else puts stdout ``Result is [expr 1 / $a]''

f

g

g

gf

f

g

inv 10 => 0.1

Notice that blocks of code can be seen as Tcl lists. In fact, the list creation command is often used to create a new block of code and evaluate it. 102

15.3.6 Evaluation

Like in many interpreted languages, you can explicitly call the Tcl evaluator in you code with the eval command. set cmd ``puts stdout Hello!'' eval $cmd => Hello!

However, Tcl provides other evaluation features. The uplevel command evaluates its arguments but in a di erent context (by default the calling procedure's one). Finally, the subst command evaluates its arguments but does not try to interpret the result as a command. When receiving more than one arguments, the arguments are concatenated and evaluated, this can cause some problems for non experimented Tcl programmers. For example: set string ``Hello Word!'' set cmd ``puts stdout $string'' eval $cmd => error: bad argument in puts 'World!'.

The problem comes from the fact that it is the next command that is evaluated: eval puts stdout Hello World!

In this case, World! is considered by eval as an argument to puts. A safe and nice way to go around this problem is to use the list command each time you need to create a command. Indeed the advantage of the list command is that it respects the block grouping by adding fg around blocks. set string ``Hello Word!'' set cmd [list puts stdout $string] eval $cmd => Hello World!

15.3.7 Introspection Features

The Tcl language provides some built-in introspection features that will be very useful to build our MOP. Most of these are provided by the info command. For example, we can introspect the previously de ned inv procedure: puts stdout [info body inv] => if $a == 0 => puts stdout ``I cannot do that!'' => else => puts stdout ``Result is [expr 1 / $a]'' => puts stdout [info args inv] => a

f

g

gf

f

g

For more details, see the man page of the info command.

15.3.8 Namespaces

Since the 8.0 version, Tcl also implements namespaces. This was one of the reason why we choose Tcl. In fact, namespaces will be very useful when implementing the encapsulation concept proned by the OOP. Creating or evaluating a command or a variable is done by the namespace command. Namespaces can contain variables and procedures. They can import variables or procedures from other namespaces and export their own variables or procedures. The following command creates a namespace called n and containing a variable and a procedure. 103

f

namespace n eval variable v proc p a variable v puts stdout $v ...

f

g

gf

g

Notice that the variable keyword permits variable declaration when used in a namespace but links to a namespace variable within a procedure. To export a command (replace v with * to export all commands): namespace n eval

f

export v

g

Namespaces variables and procedures are accessible by their names preceded from their namespace path. The root namespace is ::. For example to call the p command of the namespace n, just write (if you are in the root namespace, the :: before n is not needed): ::n::p a value

Namespaces provide their own introspection features (they are not incompatible with the info command). For instance, to get all the children namespaces of a given namespace: namespace ::n children

15.4 TOS Default Object Model and Implementation

15.4.1 The Object Model and Syntax

When implementing a MOP (for a re ective language), we need to de ne the default behavior and semantics of the language. We choose a very simple Object Oriented Programming Model, widely coming from Clos and Smalltalk. These are the basic informal rules of TOS:

     

everything is an object. an object is de ned by its functions and its slots. object de nition is done in another object, called a metaobject (or a class). on each object, one can call a function, if part of the object de nition. the object state is de ned by its slots values. objects slots values are only readable or writable from the object functions code.

Since the previous points de nes the objects default semantics, we need to de ne the metaobjects or classes default semantics.

   

a class is an object (from the rst previous point). a class is the metaobject of a set of objects called the instances of the class. a class de nes the functions and the slots of its instances. a class can inherit from a set of classes, thus de ning the sum of all slots and functions de ned by the parent (or super, or base) classes (plus its own de nitions).  function de nitions of base classes can be overridden by the current class de nition. 104

Those rules entirely de ne the basic TOS semantics. It allows us to de ne a basic syntax for TOS programming features. For a function call on an object (note that the only change with Tcl is the object name before the function name). object name function name arg1 arg2 ...

For a class creation, we would like to enable the following syntax: class class name : base classe1 base class2 ... slot stype name default value ... func ret type name arg1 arg2 ... # the body...

f

f

gf

g

g

...

In the method body, we should also be able to easily access a slot value. This is done like in the Tcl syntax, using a $ to retrieve a variable value. Calling a method within a function body can be done in the same way that when we call a method on an object. This implies that we know the instance on which the current method has been called. Thus, the current working instance can be known within a function by getting the value of the hidden variable this. To call a function within a function, just write: $this func name arg1 arg2 ...

But, for easy programming, the following syntaxes are equivalent: this func name arg1 arg2 ... func name arg1 arg2 ...

To call a function of a superclass that has been rede ne by the current class, use the super keyword. super func name arg1 arg2 ...

Excepting those special OO features, function bodies can be entirely written in classical Tcl syntax. Tcl classical extensions (like Tk or dp) can also be used. However, their might be some con its with other less wide-known extensions (especially other extentions for OOP).

15.4.2 The Default Implementation

For implementing the TOS OOP extension, we are using Tcl namespaces. The advantage of using namespaces is that the mapping from the objects to their implementation is quite straightforward. Moreover, using namespaces is much more ecient than a classical implementation, because Tcl will use an internal naming management instead of using a manually built naming management | like we would have to build it if we had used Tcl 7.4 or a namespaceless Lisp interpreter. As one could expect, one object is associated to a namespace. Since we want to stay as simple as possible, the namespace name corresponds to the object name. Similarly, in a given namespace, the variable directly corresponds to an object slot. However, since functions are common to a class (a set of instances of the same class), namespace procedures do not corresponds to the procedures of the namespace object, but of the namespaces implementing the intance objects. Moreover, to be able to access the namespace of an instance, all the procedures have a this parameter added when they are registered in the namespace. The link between the variables of the procedures and the slots of the instance is done at the beginning of the procedure with the upvar Tcl command (that allows a context to access another context data). Because the system needs some built-in data to work, some variables are automatically added to namespaces. We will see later that they correspond to some slots de ned in default metaobjects. Thus, 105

for each object, instanceof and name variables are added. They are used to know the name and the metaobject (the class) of the current object. For each object that is also a class, in addition to the two previous variables, some variables are added to know the slots, functions, and base classes of the current class (respectively called slots, funcs, and baseclasses ). Finally the class de ned bellow:

f

class C2 : C1 slot float number 0 func void add a nummber set number [expr $number + $a number]

f

gf

g

func float getNumber return $number

g

fg f

g

Is actually implemented as follows (notice the default initializations, done just after the namespace creation and the creation of a new Tcl procedure that is used to call functions on the C2 class object):

f

namespace C2 eval export * variable name variable instanceof variable slots variable funcs variable baseclasses proc add this a number upvar #0 ::$ this ::name upvar #0 ::$ this ::meta upvar #0 ::$ this ::number set number [expr $number + $a number]

f

f f f

g

g g g

f

gf

gf

proc getNumber this upvar #0 ::$ this ::name upvar #0 ::$ this ::meta upvar #0 ::$ this ::number return $number

f f f

g

g g g

g

set ::C2::name C2 set ::C2::meta Class set ::C2::slots float number 0 set ::C2::funcs void add a number float getNumber set ::C2::baseclasses C1 proc C2 funcname args # for the moment, only the new and the funcCall # default functions are treated

f ff

f

g

gf

fggg

gf

g

Then, when the C2 class will be instantiated (by calling the new function on C2 ) to an instance called c, this instance will be instantiated in the c namespace. C2 new c

Is actually implemented by: namespace c eval export *

f 106

g

variable name variable instanceof variable number

set ::c::name c set ::c::meta C2 set ::c::number 0 proc c eval [list $ ::c::instanceof

fg f

f

g

g funcCall

c $funcname] $cmd

The previously described basic implemention of the TOS language is stand alone and works ne. It allows class creation, class instantiation, and function call on instances. For instance: c add 12 ; puts stdout [c getNumber] => 12

However, it is a xed Object System that does not permit self modi cations of some implementation aspects of the objects. In spite of this, one can notice that some meta-information has been subsequently added to each class and instance objects (the name, instanceof, slots, funcs, and baseclasses are automatically added variables). Those variables can | and must | be seen as the implementation of the attributes of some implicit meta-level classes that de ne the class and instance objects structure and behavior. Moreover, the C2 new c statement must be seen as the call of the function named new on the class C2. Like any other object, class C2 is an instance of a class object where we must nd the new function de nition (we also must nd the funcCall function used in the c procedure). It is this implicit meta-level class that we need to explicit to make some TOS aspects rei ed. Next section presents the TOS meta-level objects that implement the TOS MOP.

15.5 The TOS MOP

15.5.1 Meta-Level Objects

If we carefully look at the the base-level TOS implementation presented in section 15.4.2, we can easily nd out the meta-level objects TOS has to de ne in order to be consistent with its own de nition. Notice that in re ective system design, the consistency between the meta-level and the base-level appears to be a very important issue. First, for an elegance matter, and second, because if the system is coherent, it will help the programmer to reason about the system and to better understand the causal connectivity between the meta and the base level. This can be summarized in the following well-known assertion: \in computer science, elegance is a matter of life or death". Let us try some self-reasoning about the TOS system. First, instance objects have implicitly added variables called name and instanceof. Because we want to self-describe TOS, those variables can only have been declared and initialized by a class object. This class object is common to any TOS object (we can nd the same variables in the class objects) and is in fact the TOS rst assertion formalization (everything is an object). We will call this class Object, because it describes the implementation of any TOS object. This is its minimal de nition (scalar is a non-typed type in the Tcl sense, that is to say non-formatted string):

f

class Object slot scalar name slot scalar instanceof func scalar name return $name func scalar meta return $instanceof func void setMeta newmeta ... ...

fg f fg f f

g

gf

g

g

107

g

Now, by looking at the classes implementation, we deduce that they must be the instances of a class metaobject that at least de nes the slots, funcs, and baseclasses slots, the new function (that is used to create the c instance of C2 in the previous example), and the funcCall function. The following TOS code shows the very simpli ed declaration of the class metaobject Class (we also add some useful functions).

f

class Class : Object slot scalar baseclasses slot scalar slots slot scalar funcs func scalar new instancename args newSlots $instancename registerCommand $instancename parseDecl $instancename $args registerInstance $instancename return $instancename

f

g

gf

f

gf

g gf gf gf g

func void newSlots instancename ... func void registerCommand instancename ... func void parseDecl instancename args ... func void registerInstance instancename ... func void initSlots set slots [list] func void registerSlot slot decl if [lsearch -glob $slots "* [lindex $slot decl 1] *"] == -1 lappend slots $slot decl else puts "Slot '[lindex $slot decl 1]' already exists."

f

fg f f

f

g

g

f

f

gf

g

g

g

gf

f

g

fg f fg f

g

func list slots return $slots func list allSlots set ret $slots foreach curclass $baseclasses set ret [concat $ret [$curclass allSlots]]

f

g

g

g

return $ret

func func func func func func func func func ...

fg f f f

g

void initFuncs ... ... void registerFunc func decl scalar funcBody func name ... list funcs ... list allFuncs ... void registerBaseClass base name ... bool isA base name ... scalar lookupFunc func ... scalar funcCall instance func args ...

gf gf

fg f g fg f g f f gf g f gf f

g

g

gf

g

gf

g

g

Functions like slots or funcs are called introspection functions because they allow to know about any class characteristics at any time. For example, C2 funcs => void add a number C2 isA C1 => true

ff

g ffloat

getNumber

108

fggg

Some functions can be extremely useful for the system behavior itself. For example, the lookupFunc function is used when calling a function on an object to know where the function is e ectively implemented in the base classes hierarchy. This function is also responsible for solving name con icts when multiple inheritance. In short, the Class metaobject is responsible of the TOS class objects semantics and implementation.

15.5.2 And... What About the Class Class?

We have just de ned a new class called Class that de nes all the classes of the system. The important question is: is the class Class also an instance of Class ? In other words, can we say that Class is self-described. In fact, since the class Class is de ned as a normal TOS class, it is almost the case and it is quite consistent to say that. For example, if we say that Class 's class is Class, then we automatically bene ts from the introspection features o ered by Class. Class slots => scalar slots Class isA Object => true

ff

fgg fscalar

funcs

fgg fscalar

baseclasses

fggg

However, their is one aspect of the system that has not been yet explicited as a function or a class. It is the class keyword that we used to create new classes (including the class Class ). If we follow TOS rules, the class keyword consist in calling the new function in the metaobject of class Class | the new we nd in Class is used for creating instances of normal classes like C2 and is much more simple than the new used for creating a class (that has to parse its declaration and register slots and functions). Thus, we should be able to write: Class new C1 : C2

f

g

...

instead of: class C1 : C2

f...g

As a consequence, we need to add a new meta-level object, that is the class for Class. This class is called Metaclass. In this new class, we can nd the de nition of the new function for the classes. However, since we do not want to loose the introspection features o ered by Class on Class (as explained just before), this class Metaclass must inherit from Class. Thus, Metaclass inherits from Class and override some speci c functions like the new function.

15.5.3 Metacircularity

Since we nally added a new kind of metaobject called Metaclass, to control some speci c behavior of the class Class, we should now ask ourselves if we should also add a new metaobject for controlling the Metaclass behavior. This metaobject could be useful to create new metaclasses (by Metaclass new ). However, this metaobject would also di er from Class and Metaclass by the new function. This raises the question: is it worth continuing adding metaobjects (it is the in nite tower phenomenum), especially for one function overriding? In our quest, consisting in nding the best tradeo between elegance and performance, we decide to stop the in nite tower here by adding a metacircular link at the metaclass level (Metaclass will nally be an instance of itself). Thus, there is a problem when creating new metaclasses, because the wrong new function will be called. We choose to de ne a special function called metanew for creating metaclasses (instances of Metaclass ). This function uses the Metaclass::new function to create a new class and then changes the instanceof link so that it becomes Metaclass (initially Class ). Notice that it is easily feasible because Metaclass inherits from Class and does not add any attributes. This is the Metaclass de nition:

f

class Metaclass : Class func scalar new instancename args ... func scalar metanew instancename args

f

f

gf

109

gf

g

set retval [eval "$this new [concat $instancename $args]"] if "$retval" == "nil" return nil

f

gf

g

Metaclass registerInstance $instancename Class rmvInstance $instancename set ::$ instancename ::instanceof Metaclass if ![$retval isA Object] $instancename registerBaseClass Object

f

f

g

gf

g

g

g

return $retval

The following gure sums up the inheritance and instantiation links between the three metalevel classes (see, it is not that complicated, is it?). Those three classes are totally stand-alone and compose the TOS MOP. It is the minimal base system, the TOS nucleus, on which one can easily build other kinds of metaclasses, classes, and objects. Object

Class

Metaclass

inherits from instance of

15.5.4 Bootstrapping Issues

When looking at the previous gure, one can easily see that it is full of chicken-and-egg problems. For example, we cannot create Object before Class (because Object is an instance of Class ). And we cannot create Class before Object because Class inherits from Object. Same problems occur between Metaclass and Class but the most impressive is the Metaclass, that seems to come from nowhere (because it has been created by itself!). Well, we are not here to solve theological problems but to bootstrap this system. And there is an very easy way to do this. In fact, the system bootstrap is entirely done by an implicit metaclass that will be destroyed at 110

the end of the bootstrap process. This metaclass is very special because it does not use Class or Metaclass to create the Object, Class, or Metaclass objects, thus avoiding metacircular or chicken-andegg bootstrapping issues.

15.5.5 Runtime Metacircular Issues

At the base level that we brie y described and illustrated in section 15.4.1, function calls are done by calling funcCall on the metaobject (the class of the currently called object). For example, the following call: c add 10

leads to the following evaluation (because c is an instance of C2 ): C2 funcCall c add 10

This recursively leads to evaluate: Class funcCall C2 funcCall c add 10

However, if the system continues the recursion here, it inevitably leads to an in nite regression (since the metaobject of Class is Metaclass, that inherits the funcCall function directly from Class itself). Thus, when a call on class is done, the system breaks the recursion by evaluating:

f

gf

if [info command ::Metaclass::$funcname] != "" eval ::Metaclass::$funcname Class $args elseif [info command ::Class::$funcname] != "" eval ::Class::$funcname Class $args elseif [info command ::Object::$funcname] != "" eval ::Object::$funcname Class $args else puts "Error"

g

f

g

f

g

f

gf gf

g

In this case, funcname and args variables are respectively set to the \funcCall" and \C2 funcCall c add 10" values. This can be interpreted like a internally built-in lookup mechanism when reaching the metalevel objects (most of the time, the Class metaobject). This feature brings more than just breaking runtime metacircularity, it also improves the default implementation of TOS by directly accessing to the implementation level (i.e. the Tcl namespaces and introspection features (see the namespace and info commands in section 15.3)). In this particular case, the code that will be evaluated is (note that Class corresponds to the this parameter): ::Class::funcCall Class C2 funcCall c add 10

This code performs good because Class::funcCall looks up the funcCall function in Class, nds it in Class and recursively evaluates: ::Class::funcCall C2 c add 10

Again, funcCall looks up the add method in C2 and nds it (of course it could have been located in a super class of C2 ) and recursively evaluates exactly what we were waiting for: ::C2::add c 10

15.6 Using TOS MOP: A Meta-Programming Example 15.6.1 Creating a Metaclass

Creating a new metaclass is done by the call of the metanew function on Class | creating a new class (like C2 ) is done by calling the new function on Class. When creating a new metaclass, and unless you need to create a totally new kind of object, it is natural to inherit from Class. Thus, you can bene t 111

from the default behavior of a class instead of re-code it from scratch. Function overloading mechanism allows the metaprogrammer to change a default aspect of the default metaclass. For example, if you want the slots to be implemented as an array instead of a list (this can be useful when a class contains a great number of slots | see [KAR+ 91]), then you must change all the functions concerning slots management.

f

Class metanew ArraySlotClass : Class func void newSlots instancename ... func void initSlots set slots(0) func void registerSlot slot decl ... set slots([lindex $slot decl 1]) $slot decl ...

f

g

gf

fg f f

g fgg

gf

fg f

func list slots set ret [list] foreach index [array names slots] lappend ret $slots($index)

f

g

g

return ret

fg f

func list allSlots set ret [this slots] foreach curclass $baseclasses set ret [concat $ret [$curclass allSlots]]

f

g

g

g

return $ret

15.6.2 A Simple Example

Let us take a more simple case: the famous VerboseClass example (it is the \Hello World" example for re ective languages). Creating a VerboseClass that prints a message each time an instance is created or each time a function is called on an instance is really easy. We just need to override the new and the funcCall function of Class (we give the result of the commands with \=>").

f

Class metanew VerboseClass : Class slot scalar nbcalls 0 func scalar new instancename args set retval [super new $instancename $args] if "$retval" == "nil" puts "Class '$this' was not instanciated." else puts "Class '$this' has been instanciated."

f

gf

f

g

gf

f

g

g

return $retval

f

gf

func scalar funcCall instance func args incr nbcalls 1 puts "Function '$func' is called on '$instance'." puts "Calls counter: $nbcalls." super funcCall $instance $func $args

g

g

=> VerboseClass

112

Let us now create a VerboseClass : VerboseClass new Counter slot int counter 0 func void add toadd incr counter $toadd

g

f

gf

f

gf

func void sub toadd incr counter $toadd

g

f

g

=> Counter

Notice that the new command that we have just executed is not the one de ned in VerboseClass, but the one de ned in Metaclass (the VerboseClass 's metaobject). As a consequence, it is not surprising that no message is printed here. On the contrary: Counter new c => Class 'Counter' has been instantiated. => c

and: c add 12 => Function 'add' => Calls counter: => 12 c sub 24 => Function 'sub' => Calls counter: => -12

is called on 'c'. 1.

is called on 'c'. 2.

As you can see, a verbose semantics of the class has been easily implemented | and is available for any instance of the Counter class. In a classical approach, many changes on Counter class would have been necessary (one for each function). Here, semantics changes are (almost) transparent since we just need to change the class metaobject of Counter.

15.6.3 Class Metaobject Runtime Change

What happens if we now change the class metaobject of Counter (from VerboseClass to Class ) by using the Object::setMeta function. Well, it works! Counter setMeta Class c add 12 => 12 c sub 24 => -12

The verbose semantics has disappeared because the function call mechanism is a runtime mechanism that uses the instanceof slot of the objects. It is thus logical that the funcCall de ned by Class is now called. However, it works ne because VerboseClass inherits from Class and does not change any slot implementation features. Thus, Class can access the VerboseClass instances's slots just like if it were its own instances. We say that classes Class and VerboseClass are metacompatible from VerboseClass to Class. What happens if we now want to do it in the other way? 113

f

Class new Counter # exactly the same... ...

g

=> Counter Counter new c => c c add 12 => 12

Let us change the metaobject of Counter: Counter setMeta VerboseClass c sub 24 => Error: variable 'nbcalls' does not exist.

This error is easy to understand. Since the Counter class is an instance of the Class class, only the slots de ned in Class has been allocated when creating the Counter object (slots are allocated by the newSlots function that creates the variables in the namespace corresponding to the newly created instance). Thus, when Tcl tries to access the nbcalls slots (in the incr nbcalls 1 statement of the VerboseClass::funcCall function), an internal error happens. We say that Class and VerboseClass are metaincompatible from Class to VerboseClass. However, since we think it could be very useful and interresting to be able to change the class of an object at runtime, future works will try to deal with this issue.

15.7 Conclusion This experience, building a MOP with Tcl, shows that this language is very well suited for MOP building. We are very satis ed of the clarity of the code despite some Tcl structures could seem a bit strange at rst sight. Performances seem okay, but some real tests still have to be done (however, this was not our primary goal). We believe that, in a recent future, TOS could be used for educational purpose (like the Tiny Clos MOP) and as an experimentation platform for very easy meta-programming experimentations (like implementing the EB-RT-MOP of chapter 10).

114

Chapter 16

Conclusion In this part, we report some practical experimentations with MOPs. These experiences are very positive. The rst one, consisting in using a recent existing MOP for C++ to transparently map a C++ program towards a distributed middleware layer (Corba), shows that MOPs can be an ecient technique for very fast implementation of extended languages. As we can see, those extensions can support some distribution features and semantics. Those new semantics can easily be used in concrete developments because the metalevel program can make the link between them and an existing system or language (in our experience, the meta-level program process some source-to-source translation from an extended C++ to IDL and standard C++). However, the OpenC++ experience quickly showed the limitation of the open compiler. Those limitations are mainly caused by the fact that C++ is not a language designed to be simply rei ed (especially because of type management). Some talk with Chiba convinced us that it was not worth working longer with OpenC++. Our second experiment consists in building TOS. TOS is an Object-Oriented Extension for Tcl, but implemented in a re ective way. This implementation is a success and validates the fact that simple interpreted languages, with powerful built-in re ection and evaluation means are ideal for implementing MOPs (more precisely, re ective languages). Disposing of TOS is a great improvement in our research. Its real simplicity (comparing to existing re ective languages like Clos), makes it a good experimentation plateform to implement the EB-RT-MOP, but also many other kinds of extensions, like Aspect-Oriented Programming Languages.

115

116

Conclusion

117

Our study leads to the conclusion that MOPs will be very widely used in future developments and in such various elds as operating systems, communications and networks, software engineering, and languages. It is a powerful, general, and elegant approach that tends to become more and more performant. Except in some restricted and specialized areas, they even can bring more performance, by using compile-time techniques, or run-time optimization techniques. However, because they provide a very abstract framework that can be applied to many situations, MOPs su er from a obvious lack of specialization and de nition when addressing real problems. In languages and operating systems, one can nd the two most advanced specializations of MOPs (best representatives are respectively CLOS and CodA). However, they do not address the same kind of problems and that are quite incompatible visions of the same framework. We rmly believe that the next research challenge is to reconcile those two visions (the rst one especially deals with structural aspects of a program and the other one with computational and behavioral aspects of a program). Many recent works have tried to integrate those two aspects in a common MOP but the result is complicated and leads to overspecialized MOPs (like Iguana that proposes 23 di erent metaobjects). To us, the solution is much closer to a multiple MOPs architectural framework where some userde ned dependences can be added. The framework we propose in part II for implementing the Distributed Programming Model is matching these criteria. However, a lot of work is still to be done. First, with the metaobject runtime composition issue. We think this is the key of adaptability and reusability of runtime meta-programs, especially in a multiple meta-level approach. Second, with the formalization and the simpli cation of the framework. For this evolution of our framework, we are very interrested in the Aspect-Oriented Programming (AOP) framework that seems to be a very good way to independently specialize some chosen aspects of the meta-interfaces. In future works, we intend to use the TOS platform to implement an AspectOriented System by introducing the aspect notion in the TOS MOP. Because we had to understand the previous works done in Metaobject Protocols and test them in practical cases to nd out their limitations and drawbacks, one year is not sucient to get very signi cant and practical results. I hope that this work can be pursued after this internship within a PhD. thesis on MOPs and AOP. Finally, I would like to conclude by yet acknowledging all the CEDRIC people I have worked with, and especially Pr. G. Florin, for welcoming me in his group, L. Duchien and L. Seinturier, for all their training and supervisory, P. Champagnoux and D. Enselme for our endless discussions about re ection. A special thanks E. Gressier for non re ective advice.

119

120

Bibliography [BC89]

J. Briot and P. Cointe. Programming with Explicit Metaclasses in Smalltalk-80. In Proceedings of ACM Conference on Object-Oriented Programming Systems, Languages, and Applications, pages 419{431, 1989.

[BDKP92] H. Bretthauer, H. Davis, J. Kopps, and K. Playford. Balancing the Eulisp Metaobject Protocol. In Proceedings of the Int'l Workshop on Re ection and Meta-Level Architecture, pages 113{118. A. Yonezawa and B. C. Smith eds., 1992. [BKK+ 86] D.G. Bobrow, K. Kahn, G. Kiczales, L. Masinter, M. Ste k, and F. Zdybel. CommonLoops, Merging Lisp and Object-Oriented Programming. In Proceedings of ACM Conference on Object-Oriented Programming Systems, Languages, and Appications, pages 17{29, September 1986. [BKPS92]

Buschman, Kiefer, Paulisch, and Stal. The Meta Information-Protocol: Run-Time Type Information for C++. A. Yonezanwa and B. C. Smith, eds., 1992.

[Bri89]

J-P. Briot. Actalk: a testbed for classifying and designing actor languages in the Smalltalk-80 environment. In Proceedings of European Conference on Object-Oriented Programming, pages 109{129. Cambridge University Press, 1989.

[CDG+ 89] L. Cardelli, J. Donahue, L. Glassman, M. Jordan, B. Kalsow, and G. Nelson. Modula-3 report (revised). Technical Report 52, DEC Systems Research Center, November 1989. [Chi93]

S. Chiba. Open C++ release 1.2 programmer's guide. Technical Report 9303, Department of Information Science, University of Tokyo, 1993. ftp://ftp.is.s.u-tokyo.ac.jp/pub/techreports/TR93-03-letter.ps.Z.

[Chi95]

S. Chiba. A metaobject protocol for C++. In Proceedings of the 10th Annual Conference on Object-Oriented Programming: Systems, Languages and Applications (OOPSLA'95), number 10 in SIGPLAN Notices, pages 285{299. ACM Press, October 1995.

[Chi96]

S. Chiba. A study of Compile-Time Metaobject Protocol. PhD thesis, University Of Tokyo, November 1996.

[Chi98]

S. Chiba. OpenC++ 2.5 Reference Manual. Institute of Information Science and Electronics, 1998. http://www.softlab.is.tsukuba.ac.jp/ chiba/openc++.html.

[CKL96]

S. Chiba, G. Kiczales, and J. Lamping. Avoiding confusion in metacircutariy: The metahelix. In Proceedings of ISOTAS'96, volume 1049 of Lecture Notes in Computer Science, pages 157{174, March 1996. http://www.softlab.is.tsukuba.ac.jp/ chiba/openc++.html.

[CM]

Cases and MetaCases. Cases and metacases home pages.

http://osiris.sunderland.ac.uk/sst/casehome.html http://osiris.sunderland.ac.uk/rif/metacase/metacase.home.html

.

121

[CM93a]

[CM93b] [Coi87] [Des96] [DM88] [Fer89]

S. Chiba and T. Matsuda. Designing an extensible distributed language with metalevel architecture. In Proceedings of the 7th European Conference on Object-Oriented Programming (ECOOP'93), volume 707 of Lecture Notes in Computer Science, pages 482{501. Springer-Verlag, October 1993. S. Chiba and T. Matsuda. Open C++ and its optimization (extended abstract). In Proceedings of OOPSLA'93 Workshop on Re ection and Metalevel Architectures, October 1993. P. Cointe. Metaclasses are rst class: The ObjectVLisp model. In Proceedings of

ACM Conference on Object-Oriented Programming Systems, Languages, and Applications, pages 156{167, 1987.

P. Desfray. Modlisation par Objects, la Fin de la Programmation. Masson, January 1996. O. Danvy and K. Malmkjaer. Intensions and extensions in a re ective tower. In Proceedings of the ACM Conference on Lisp and Functional Programing, pages 327{341, 1988. J. Ferber. Computational re ection in class based object oriented languages. In Proceedings of the 4th Conference on Object-Oriented Programming: Systems, Languages and Applications (OOPSLA'89), volume 24 of SIGPLAN Notices, pages 317{326. ACM

Press, October 1989. [FJ89] B. Foot and R. E. Johnson. Re ective facilities in Smalltalk-80. In Proceedings of ACM Conference on Object-Oriented Programming System, Languages and Applications, pages 327{335, 1989. [FNP+ 95] J.C. Fabre, V. Nicomette, T. Perennou, R.J. Stroud, and Z. Wu. Implementing faulttolerant applications using re ective object-oriented programming. In Proceedings of the 25th IEEE International Symposium on Fault-tolerant Computing (FTCS-25), pages 489{498. IEEE Computer Society Press, 1995. [Gal] E. Gallesio. Stk main page. http://kaolin.unice.fr/STk. [GC96] B. Gowing and V. Cahill. Meta-Object Protocols for C++: The Iguana Approach. In Proceedings of Re ection'96, 1996. [GGM95] B. Garbatino, R. Guerraoui, and K. Mazouni. Implementing of the GARF replicated objects platform. Distributed System Engineering, 2:14{27, February 1995. [GK97a] M. Golm and J. Kleinoder. Implementing real-time actors with MetaJava. Technical Report TR-I4-97-9, Computer Science Department, Friedrich Alexander University, Erlangen-Nurnberg, Germany, April 1997. [GK97b] M. Golm and J. Kleinoder. MetaJava: A platform for adaptable operating-system mechanisms. In Workshop on Object-Orientation and Operating Systems at the 11th European Conference on Object-Oriented Programming (ECOOP'97), June 1997. [GM95] J. Gosling and H. McGilton. The Java Language Environment: A White Paper. Sun Microsystems Computer Company, May 1995. [GR89] A. Goldberg and D. Robson. Smalltalk 80: The Language. Addison-Wesley, 1989. [Gri97] R. Grimes. DCOM Programming. Wrox Press, 1997. [HKM+ 88] J. H. Howard, M. L. Kazar, S. G. Menees, D. A. Nichols, M. Satyanarayanan, R. N. Sidebotham, , and M. J. West. Scale and performance in a distributed le system. ACM Transactions on Computer Systems, 6(1), pages 51{81, February 1988. 122

[Hof88]

D. Ho man. On Criteria for Module Interfaces. IEEE Transaction on Software Engineering and Methodology, 16(5):537{542, February 1988. [Ibr90] M. H. Ibrahim. Re ection and Metalevel Architectures in Object-Oriented Programming. In Addendum to the Proceedings of the 5th Conference on Object-Oriented Programming Systems, Languages and Applications, pages 73{80, 1990. [IHS+ 96] Y. Ishikawa, A. Hori, M. Sato, M. Matsuda, J. Nolte, H. Tezuka, H. Konaka, M. Maeda, and K. Kubota. Design and Implementation of Metalevel Architecture in C++: MPC++ Approach. In Proceedings of Re ection'96, 1996. [Imp] Open Implementation. The open implementation home page. http://www.parc.xerox.com/spl/projects/oi/. [IMY92] Y. Ishisugi, S. Matsuoka, and A. Yonezawa. RbCl: A Re ective Object-Oriented Concurrent Language without a Run-Time Kernel. In Proceedings of the Int'l Workshop on Re ection and Meta-Level Architecture, pages 24{35, 1992. [KAR+ 91] G. Kiczales, J.M. Ashley, L. Rodriguez, A. Vahdata, and D.G. Bobrow. Metaobject protocols: Why we want them and what else they can do. 1991. [KdRB91] G. Kiczales, J. des Rivieres, and D.G. Bobrow. The Art of the Metaobject Protocol. MIT Press, 1991. [KG97] J. Kleinoder and M. Golm. MetaJava: An ecient run-time meta architecture for Java. Technical Report TR-I4-96-03, Computer Science Department, Friedrich Alexander University, Erlangen-Nurnberg, Germany, June 1997. [KLL+ 97] G. Kiczales, J. Lamping, C. V. Lopes, C. Maeda, A. Mendhekar, and G. Murphy. Open implementation design guidelines. ACM, 1997. [KR78] Brian W. Kernighan and Dennis M. Ritchie. The C Programming Language. PrenticeHall, 1978. [KSC+ 98] F. Kon, A. Singhai, R.H. Campbell, D. Carvalho, and R. Moore. 2k: A re ective, component-based operating system for rapidly changing environments. In Proceedings of the Re ection Workshop of the 12th European Conference on Object-Oriented Programming (ECOOP'98), 1998.

[LC95] [Led97]

T. Ledoux and P. Cointe. Explicit metaclasses as a tool for improving the design of class libraries. Technical Report 95-4-INFO, Ecole des Mines de Nantes, 1995. T. Ledoux. Implementing proxy objects in a re ective ORB. In Workshop CORBA

Implementation, Use and Evaluation at the 11th European Conference on Object-Oriented Programming (ECOOP'97), 1997.

. R. Lea, Y. Yokote, and J.-I. Itoh. Adaptative operating system design using re ection. In Proceedings of the 5th Workshop on Hot Topics on Operating Systems, pages 95{100, 1995. P. Maes. Computational re ection. Technical report, Arti cial Inteligence Laboratory, Vrije Universiteit Brussel, 1987. P. Maes. Concepts and experiments in computational re ection. In Proceedings of the http://ballesta.inrialpes.fr/~bellissa/wecoop97/

[LYI95] [Mae87a] [Mae87b]

[McA95]

2nd Conference on Object-Oriented Programming: Systems, Languages and Applications (OOPSLA'87), volume 22 of SIGPLAN Notices, pages 147{155. ACM Press, December

1987. J. McA er. Meta-level programming with CodA. In Proceedings of the 9th European Conference on Object-Oriented Programming (ECOOP'95), volume 952 of Lecture Notes in Computer Science, pages 190{214. Springer-Verlag, August 1995. 123

[MMAY95] H. Masuhara, S. Matsuoka, K. Asai, and A. Yonezawa. Compiling away the meta-level in object-oriented concurrent re ective languages using partial evaluation. In Proceedings of the ACM Conf. on OOPSLA, pages 300{315, 1995. [MMC95] P. Mulet, J. Malenfant, and P. Cointe. Towards a metodologie for explicit composition of metaobjects. Technical Report 95-6, Ecole des Mines de Nantes, 1995. [Obj98] Object-Oriented Concepts, Inc. The OmniBroker Home Page, 1998. http://www.ooc.com. [OIT92] H. Okamura, Y. Ishikawa, and M. Tokoro. Al-1/d: A distributed programming system with multi-model re ection framework. In Proceedings of the International Workshop on New Model of Software Architecture'92 Re ection and Meta-level Architecture, November 1992. [OIT93] H. Okamura, Y. Ishikawa, and M. Tokoro. Metalevel decomposition in al-1/d. volume 742 of Lecture Notes in Computer Science, pages 110{127. Springer-Verlag, November 1993. [OMG95] OMG. Common Object Request Broker Architecture 2.0, July 1995. http://www.omg.org. [Ous] J. Ousterhout. Tcl and the Tk Toolkit. [PS98a] R. Pawlak and L. Seinturier. A framework for an event based run-time metaobject protocol. Submitted to the Re ective Object-Oriented Programming and Systems Workshop at ECOOP'98, July 1998. http://cedric.cnam.fr/personne/pawlak/cnam.html. [PS98b] R. Pawlak and L. Seinturier. Implementation of an event based run-time metaobject protocol. Technical Report 9804, Laboratoire CEDRIC-CNAM, July 1998. http://cedric.cnam.fr/personne/pawlak/cnam.html. [RAA+ 90] M. Rozier, V. Abrossimov, F. Armand, I. Boule, M. Gien, M. Guillemont, F. Herrmann, C. Kaiser, S. Langlois, P. Lonard, and W. Neuhauser. Overview of the CHORUS distributed operating systems. Technical Report CS/TR-90-25, Chorus Systmes, April 1990. [RJC88] V. Russo, G. Johnston, and R. Campbell. Process management and exception handling in multiprocessor operating systems using object-oriented techniques. In Proceedings of Object-Oriented Programming Systems, Languages and Applications. ACM Press, September 1988. [Ruf93] E. Ruf. Partial evaluation in re ective system implementation. In Proceedings of ObjectOriented Programming Systems, Languages and Applications, 1993. [Smi82] B. Smith. Re ection and semantics in a procedural language. Technical Report 272, Laboratory for Computer Science, Massachusetts Institute of Technology, 1982. [Smi84] B. Smith. Re ection and semantics in Lisp. In Proceedings of ACM Symp. on Principles of Programming Languages, pages 23{35, 1984. [Sou91] B. Soustrup. The C++ Programming Language, Second Edition. Addison-Wesley, 1991. [Sou92] B. Soustrup. Run Time Type Identi cation for C++. In Proceedings of the USENIX C++ Conference, August 1992. [Ste90] G. L. Steele. Common Lisp: The Language. Digital Press, 1990. [Sun97] Sun Microsystems. Java Core Re ection, API and Speci cation, February 1997. http://www.sunsoft.com. 124

[TC98]

M. Tatsubori and S. Chiba. OpenJava 1.0 API and Speci cation. Programming Language Lab., University of Tsukuba, 1998. http://www.softlab.is.tsukuba.ac.jp/~mich/openjava. [TvRvS+ 90] A.S. Tanenbaum, R. van Renesse, H. van Staveren, G. Sharp, S. Mullender, J. Jansen, and G. van Rossum. Experiences with the Amoeba distributed operating system. Communications of the ACM, 33(12):46{63, 1990. [US87] D. Ungar and R. B. Smith. Self: The power of simplicity. In Proceeding of the 2nd Comference on Object-Oriented Programming Systems, Languages and Applications, pages 227{241, 1987. [Wel98] B.B. Welch. Practical Programming in Tcl and Tk, 2nd ed. Prentice Hall, 1998. [WS98] I. Welch and R. Stroud. Using metaobject protocols to adapt third-party components. In Proceedings of the Re ection Workshop of the 12th European Conference on ObjectOriented Programming (ECOOP'98), May 1998. [WY88] T. Watanabe and A. Yonezawa. Re ection in an object-oriented concurrent language. In Proceedings of the 3rd Conference on Object-Oriented Programming: Systems, Languages and Applications (OOPSLA'88), volume 23 of SIGPLAN Notices. ACM Press, September

1988. [YMFT91] Y. Yokote, A. Mitsuzawa, N. Fujinami, and M. Tokoro. Re ective object management in the Muse operating system. In Proceedings of the 1991 International Workshop on Object Orientation in Operating Systems, pages 16{23. IEEE Computer Society Press, October 1991. [Yok92] Y. Yokote. The Apertos Re ective Operating System: The Concept and its Implementation. In Proceedings of the 7th Conference on Object-Oriented Programming: Systems, Languages and Applications (OOPSLA'92), volume 27 of SIGPLAN Notices, pages 414{ 434. ACM Press, October 1992. [You89] M. W. Young. Exporting a user interface to memory management from a communicationoriented operating system. Technical Report 89202, Carnegie Mellon University, November 1989. [YTM+ 91] Y. Yokote, F. Teraoka, A. Mitsuzawa, N. Fujinami, and M. Tokoro. The Muse object architecture: A new operating system structuring concept. ACM Operating System Review, 25(2):22{46, April 1991. [Zim96] C. Zimmermann. Advances in Object-Oriented Metalevel Architectures and Re ection. CRC Press, 1996.

125