|
||||
Eiffel on the Web:
|
This article describes the initial step of porting Eiffel to .NET, resulting in an intermediate version, "Eiffel#", that supported a subset of the Eiffel language. That version is now obsolete since Eiffel for .NET, starting with version 5.1 (November 2001) supports full Eiffel, identical to the language supported on other platforms, with multiple inheritance and all other advanced Eiffel mechanisms such as agents. The present article is retained here for information about the state of the technology at a specific intermediate step. For a current description of Eiffel for .NET see the revised version of this article in HTML format or in PDF format. |
This article also appears on the Microsoft MSDN site at:
http://msdn.microsoft.com/library/techart/pdc_eiffel.htm.
© 2000 Microsoft Corporation. For more information see related links at the end of the article.
Interactive Software Engineering (ISE) has been partnering with Microsoft since the summer of 1999, together with a group from Monash University in Australia, to integrate Eiffel into the Microsoft .Net framework.
Eiffel is a comprehensive software development environment (ISE Eiffel) based on a method that covers the entire software lifecycle - not just implementation, but also analysis, design, and maintenance. The environment is based on the Eiffel language, thoroughly applying the principles of object technology and implementing the concepts of Design by Contract™ to produce highly reliable, extendible, and reusable applications. ISE Eiffel is particularly geared towards large and complex systems and is used by major organizations in the financial, defense, real-time, and other industries for mission-critical developments. Universities worldwide (such as Monash University) also use Eiffel to teach programming and software engineering at all levels.
Microsoft .NET Framework is the next generation Web technology developed at Microsoft that leverages many technical solutions for building Internet applications. The framework includes ASP+ for building Web-based applications in a considerably faster and easier way than more traditional approaches. The core of the technology consists of a runtime that interprets and/or compiles byte code (the virtual machine's internal language, also known as "IL") with metadata. The metadata describes each component part of the system, including the prototype for all its methods, fields or events.
Eiffel on Microsoft .NET Framework provides an ideal combination for companies wishing to take advantage of best-of-breed technologies in operating systems, Internet and Web infrastructure, software development methods, and development environments. In particular, the openness of Eiffel to other languages and environments combined with the .NET emphasis on language neutrality, makes the resulting product an ideal vehicle for building applications containing components in many different languages, with Eiffel serving as the "glue" between them. In this article, we describe this combination and the challenges we faced when integrating ISE Eiffel into .NET.
Targeting .NET for a language compiler really means being able to produce IL and the associated metadata.
Generating IL would be enough, if the aim were just to "compile Eiffel under .NET," but would fall short of our goal of providing a general-purpose framework for multi-language interoperability, since other languages would not be able to reuse Eiffel types without the metadata that describes them. One of the goals set by ISE regarding the integration of Eiffel is the ability to reuse existing types written in any language as well as the generation of types that can be understood by any other .NET development environment. Eiffel is a .NET extender, meaning that you can write Eiffel classes that inherit from classes written in other languages, extend them and then recompile them to IL, giving other environments the possibility of reusing the new type.
This generation of IL and metadata is done through a special switch in the system file (the file that describes the Eiffel system, also called ACE file) so that this new Eiffel compiler is fully integrated into ISE's IDE, EiffelBench. Existing Eiffel programmers will be able to work the exact same way they did before the integration.
Another aspect of the port consists of integrating the compiler into Visual Studio .NET. This integration will help new Eiffel developers who do not want or do not have time for learning a new environment but who already make use of Visual Studio to shorten their learning curve. The integration will support all the specific functionalities of Visual Studio such as syntax highlighting, the debugger, and some wizards.
Another key goal is the ability to write ASP+ pages in Eiffel. ISE aims at a full support (so-called advanced semantics) so that Eiffel developers will be able to embed Eiffel into ASP+ pages using the "@language="Eiffel"" directive. This support also includes the ability to write Web services into Eiffel.
With these goals in mind, ISE organized the integration around two major milestones. The first step of the integration will see the creation of a new language called Eiffel# (pronounced "Eiffel Sharp"). This subset of Eiffel is specifically designed to target .NET and embody the full extent of Design by Contract. It is powerful enough to build real applications while keeping the native object model of .NET. The second step consists of extending the results to encompass the full object model of Eiffel.
Both the integration into Visual Studio and ASP+ started with Eiffel#. The integration into Visual Studio started with a simple wrapping around the command line compiler that does not include all the available functionalities. The support for ASP+ started with the support for Web services.
The full support for all these technologies as well as for the entire Eiffel language will be available with the first nonbeta release of the Microsoft .NET Framework. Eiffel# will evolve to leverage more and more .NET technologies.
This section focuses on defining the first version of Eiffel#. The specification will evolve with the successive betas of .NET. The key requirement for Eiffel# is the exclusive use of the common language runtime without any other Eiffel-specific runtime.
Since the rest of this document defines Eiffel# by describing how it is different from Eiffel, we first need to see the main characteristics of Eiffel. More details may be found in the books Object-Oriented Software Construction, 2nd edition and Eiffel: The Language, as well as on the Eiffel Web site at http://www.eiffel.com/, from which some of this material has been extracted.
As a language Eiffel is a "pure" object-oriented language (arguably the most systematic application of object-oriented principles in existing languages) based on a small number of powerful concepts:
For a flavor of the language syntax and style (meant to be clear and simple), here is the outline of a simple class COUNTER describing a counter:
indexing
decrement is
reset is end -- class COUNTER |
At run time this class will have instances: each instance is an object that represents a separate counter. To create a counter you declare the corresponding entity, say
my_counter: COUNTER |
create the corresponding object
create my_counter |
(where
create is the object creation operation), and you can then apply to it the
operations of the class (its features):
my_counter.increment
|
Such operations will appear in features of other classes, called the clients of class COUNTER. A few more comments about this example: All values are initialized by default, so every counter object will start its life with its value, item, initialized to zero (you don't need to call reset initially). Also, item is an attribute, which is exported in read-only mode: clients can call print (my_counter.item) but not, for example, my_counter.item := 657, which would be a violation of "information hiding". Of course the class author may decide to provide such a capability by adding a feature:
set (some_value: INTEGER) is
|
in which case the clients will simply use my_counter.set (657). But that's the decision of the authors of class COUNTER: how much functionality they provide to their clients. The indexing clause at the beginning of the class does not affect its semantics (the properties of the corresponding run-time objects), but attaches extra documentation to the class.
Alone in design methodologies and languages, Eiffel directly enforces Design by Contract through constructs such as class invariants, preconditions, and postconditions. Assume for example that we want our counters to be always non-negative. The class will now have an invariant:
indexing
... class |
Feature decrement now needs a precondition, to make sure that clients do not attempt illegal operations. The keyword "require" introduces the precondition:
decrement
is |
The keyword "ensure" introduces the postcondition.
The precondition tells the client: "Never even think of calling me unless you are sure the counter is strictly positive." The postcondition says, "If you are good (you observe the precondition) here is what I promise to do for you in return: I will decrease the counter by one."
The invariant adds the promise that "Also, all my operations will maintain the counter positive or zero". Preconditions, postconditions, and invariants are called assertions.
This brief introduction to Eiffel raises a few interesting issues for its integration in the Microsoft .NET Framework. Maybe the most challenging is the support for multiple inheritance, since the common language runtime was designed to support single inheritance only. Because Eiffel# must only use the common language runtime, it has to follow the .NET object model and thus disallows multiple inheritance of effective or partially deferred classes. You may however multiple inherit pure-deferred classes in which case they are generated as interfaces. The partially deferred or effective parent class, if any, is the base type.
Eiffel# does not support the new Eiffel constructs that were added after the publication of the current edition of Eiffel: The Language. These constructs include agents and related classes, generic conformance, and generic argument creation.
Another mismatch between the common language runtime and the Eiffel object model is the lack of support for covariance in the former. For this reason Eiffel# does not support covariance. That is, you cannot redefine the types of the arguments or result of a feature in the descendant of a class.
The last difference between the two languages lies in the semantics of expanded types. Expanded types in the .NET Framework are directly mapped to the so-called value types. Although fundamentally the same, Eiffel expanded types and .NET value types do not behave in the exact same way. In particular, value types are sealed, meaning that one cannot inherit from them. As a result, you cannot inherit from expanded types in Eiffel#.
There are no other differences between Eiffel and Eiffel#; in particular Eiffel# supports contracts, exception handling, and genericity, some of the hallmarks of Eiffel programming.
Eiffel# also has some specific functionality meant to leverage necessary aspects of .NET. The first important difference is application packaging. While in a standard environment, providing the choice between building a DLL or an EXE is enough, the .NET Framework defines such new concepts as assemblies and modules that any compiler targeting the environment should support. An assembly is made of a group of modules and corresponds to an application. A module may be either a DLL or an EXE. For that reason, the ACE file for Eiffel# introduces new options to describe the different modules that will be part of the assembly. The Eiffel# compiler generates one assembly whose name is the name of the system as given in the ACE file. You may specify whether the assembly should be an EXE or a DLL in the il_generation default option as follows:
system
|
In this example, the compiler generates a single file "sample.exe" containing both the assembly and the module. In case you would like to specify different files for multiple modules, you can use the module option for each cluster and override the option for any class in the cluster:
system |
This ACE file defines three modules:
The first module, which includes the assembly manifest, is "sample.exe."
The second module, "my_app.dll," includes all classes in cluster root_cluster except the class ROOT_CLASS.
The last module, "root.dll," includes the class ROOT_CLASS. This mechanism allows you to define as many modules as you need and group the classes part of the Eiffel system the way you want.
Another feature specific to .NET is the notion of namespace. Any .NET type is associated with a namespace that ensures the uniqueness of the type name in the system. You can define a default namespace for all the classes of the Eiffel system by using the following default ACE option:
system |
In this example, all the classes of the Eiffel system will be generated in the namespace "MyApp.<cluster_name>" where <cluster_name> is the name of the cluster that contains the class. You may override the default namespace for each cluster as follows:
system |
With this ACE file, all the classes part of the cluster root_cluster are generated in the namespace "Root." Note that the name specified in the cluster clause is not appended to the namespace defined in the default clause. Finally, the Eiffel# class might include an alias clause (see "External Classes" for a description of the "alias" keyword) in which case the name specified in the clause overrides any namespace specified in the ACE file.
Another major difference coming from the dynamic nature of the .NET Framework is how contracts behave at runtime. In a classic environment, a contract violation results in a raised exception and the level of assertion checking is decided at compile time. This approach is not satisfactory anymore in the .NET Framework where the caller of a contracted function might be in a different module. The client of a contracted component should be able to decide which level of contract checking should be set, if any. This is the reason for having a standard interface that defines the possible operations available on contracts. This interface must be implemented by contracted components. This interface, called IContract, defines functions to set the level of contract checking:
* Precondition checking: this is the lowest level, only preconditions of functions will be checked.
* Postcondition checking: intermediate level, both the preconditions and the postconditions will be checked.
* Invariant checking: full checking, all the contracts (preconditions, postconditions, and invariants) will be checked.
IContract also allows choosing whether an exception should be raised when a contract violation occurs.
interface
IContract
|
The Eiffel# compiler will automatically generate an implementation of the IContract interface with the default contract checking level specified in the ACE file:
system |
If you omit the assertion option then IContract is not generated. If you choose the option 'no' then IContract is generated, but no contracts will be checked until a client activates contract checking.
We have seen how you can use Eiffel# to build .NET Framework components. Since the compiler generates all the necessary metadata, other languages can reuse the Eiffel# components in any way they like (heritance or client relationship). The next question is "How do I reuse existing components in Eiffel#?" Existing components cover the Microsoft libraries as well as components written by other parties.
ISE will provide Eiffel# libraries that wrap the Microsoft framework. These libraries include wrappers for the Base Class Library, WinForms and Web Forms. The Base Class Library includes the definition of the basic types needed for any system such as collections, remoting services, threading services, security, and IO access.
The WinForms classes allow building a GUI in the .NET Framework. They are a wrapper around the Win32 APIs that provides a clean object model to build a GUI. Web Forms also provide a way to build GUIs on the Web; they include such types as DataGrid or HTMLImage.
These three libraries are distributed with Eiffel# so you can reuse these classes directly in your system.
Obviously, the Microsoft libraries are not the only .NET components that you might want to access. Your system may require the integration of hundreds of components written in all kind of languages. For this reason, ISE provides a tool called Emitter that can analyze any .NET assembly and produce an Eiffel wrapper for every type it defines. The Emitter accesses the metadata bound into each type defined in the assembly, maps them into an Eiffel equivalent, and generates the corresponding classes.
Although an assembly may include references to other assemblies (called external assemblies), the emitter will only generate classes for the types defined in the given assembly. This avoids for example generating the Base Class Libraries for all the assemblies you need to wrap (since almost any assembly has a reference to the Base Class Libraries).
Because any public .NET Framework type must comply with the Common Language Specification (CLS) and because the CLS differs in certain aspects from the Eiffel model, the Emitter has to perform a few nontrivial transformations to map the .NET Framework types into Eiffel classes. Maybe the most important mismatch is the inclusion of overloading into the CLS. The Eiffel model prohibits overloading and requires any overloaded function to be disambiguated. The algorithm used for this purpose is the following:
Let f1, f2, ..., fn be overloaded NGWS functions with the same name (n >= 2) For
1 <= i <= n, let Si be the signature of fi: All the Si are different by the rules of overloading. We
say that a position u is unique for a function fi (for 1 <= u <=
im) We
determine a unique name Ni for fi as follows: |
Simply said, the algorithm appends as much signature type names as needed after the name of the function to obtain a unique name. For example, the following C# function:
public static
void WriteLine (String format, Object
arg0); |
is translated into Eiffel by the Emitter as follows:
WriteLine_System_String_System_Object (format: STRING;
arg0: ANY) |
You will have noticed the basic types mapping that the Emitter does on the argument types. The following table lists all the primitive types as defined in the CLS and their Eiffel equivalent:
CLS Primitive Type (Description) |
Eiffel Type |
System.Char (2-byte unsigned integer) |
CHARACTER |
System.Byte (1-byte unsigned integer) |
INTEGER_8 |
System.Int16 (2-byte signed integer) |
INTEGER_16 |
System.Int32 (4-byte signed integer) |
INTEGER |
System.Int64 (8-byte signed integer) |
INTEGER_64 |
System.Single (4-byte floating point number) |
REAL |
System.Double (8-byte floating point number) |
DOUBLE |
System.String (A string of zero or more characters; null is allowed.) |
STRING |
System.Object (The root of all class inheritance hierarchies) |
ANY |
System.Boolean (true or false) |
BOOLEAN |
There still are a few cases where the emitter cannot generate valid Eiffel code. For these few cases, you will need to manually edit the generated class. Fortunately, such need for editing is rare (only four classes need editing if you try to wrap the whole Base Class Libraries, more than 600 types). The main problem that causes this erroneous generation lies in the use of so-called MethodImpls. This mechanism allows for a type to force the binding of one of its interface functions to a function with a different name or even different signature. These MethodImpls are often used in cases where covariance would have been used should it be available in the common language runtime. Unfortunately, it is not possible so far to access information on MethodImpls through the reflection mechanisms of the .NET Framework.
The Eiffel# classes that the Emitter generates do not include any logic; they are just needed for the Eiffel type system. This means that the Eiffel# compiler does not generate any IL code for these classes. Any call to functions on classes generated by the Emitter are direct calls to the .NET Framework type; there are no indirections and thus no performance penalty. For the compiler to recognize such classes, ISE introduced a new mechanism in Eiffel called external classes. Such classes can only include external features, features that are not written in Eiffel but are rather methods or functions on an already existing .NET Framework type. All the features of an external class should be features belonging to the same .NET Framework type. You can declare such a type with the following syntax:
frozen
external class
|
The string that follows alias contains the name of the .NET Framework type that the Eiffel class wraps. Since .NET types might be sealed (they might forbid other types to inherit from them) and since such a concept does not exist in Eiffel, Eiffel# introduces a new use for the Eiffel keyword "frozen." You may use frozen in front of external to tell the Eiffel# compiler that no Eiffel# class should inherit from the .NET Framework type.
The external class should then list all the features you need to access. All these features are external features. The syntax for an external .NET Framework feature is the following:
frozen
ReadLine: STRING is
|
where frozen indicates that the feature may not be redefined in a descendant (Note: you may redefine external features if they are virtual, in which case frozen should not be used), ReadLine is the Eiffel# feature name and STRING the return type of the feature. The string that follows the "external" keyword specifies the kind of external, the .NET Framework function signature, as well as the .NET Framework type on which the function is defined. The string following the "alias" keyword contains the .NET Framework name of the function. There are different kinds of external features, depending on the type of method they provide access to. Eiffel# defines seven new kinds of externals listed in the following table:
NGWS Function Kind |
Eiffel External |
Method |
"IL signature … use class_name" |
Static Method |
"IL signature … static use class_name" |
Field Getter |
"IL signature … field use class_name" |
Static Field Getter |
"IL signature … static_field use class_name" |
Field Setter |
"IL signature … set_field use class_name" |
Static Field Setter |
"IL signature … set_static_field use class_name" |
Constructor |
"IL signature … creator use class_name" |
Should you need to, you can define such external features in non-external classes. In the special case of external classes, the .NET Framework type name that appears at the end of the string following the "external" keyword should be the same as the one that appears after the "alias" keyword, following the declaration of the class (see above
).The external features can be called from clients or descendants of the class the same way you would call any other Eiffel feature. So if your system includes a feature that needs user input, it can include the following code:
need_user_input is
|
However, because ReadLine is a static external, you do not need to instantiate the wrapper to call it, so the following code is sufficient to make the external call:
need_user_input is
|
This code is also valid for static field access and static field setting. Other kinds of externals do require the wrapper to be instantiated.
The Eiffel# compiler comes together with an Eiffel# Base Class library, which follows the design principles found in the standard EiffelBase library. This library makes extensive use of genericity and contracts to provide a clean and powerful set of data structures you can reuse in your system. This library uses the external classes that wrap the .NET Framework Base Class Library, also provided with Eiffel#.
The two other sets of classes provided with the compiler wrap the WinForms and Web Forms .NET Framework libraries so that you can easily build GUI and Web applications.
As part of this development, we produced a new tool, the Microsoft .NET Framework Contract Wizard, which through the metadata mechanism, enables users to add interactively Eiffel-like contracts to .NET components coming from arbitrary languages. This tool will be described in detail in a separate paper, but is already available for developers who want to apply the benefits of Design by Contractä in languages other than Eiffel. This important extension was made possible by the metadata facilities of .NET Framework.
The aim of this project and the resulting products is to provide a full integration between ISE Eiffel and the NGWS environment. The combined power of the platform and the development environment should yield the dream environment for building the powerful Internet applications that society expects from us today. Eiffel on Microsoft .NET Framework provides flexibility, productivity, and high reliability. It is impossible in particular to overestimate the benefits of Design by Contractä in a distributed environment, where looking for bugs after the fact can be an excruciating and money-wasting experience. Together with the other benefits of the Eiffel method—seamless development, generic programming, information hiding and other software engineering principles, and a powerful inheritance mechanism—Eiffel on Microsoft .NET Framework provides a best-of-breed solution for ambitious Internet software developers.
Bertrand Meyer, Eiffel: The Language, Prentice Hall, 1992.
Bertrand Meyer, Object-Oriented Software Construction, 2nd edition, Prentice Hall, 1997.
Raphael Simon is a senior engineer at Interactive Software Engineering
(Santa Barbara, California), in charge of the Windows applications and tools
division.
Emmanuel Stapf is a senior engineer at ISE, head of the compiler and environment
division.
Christine Mingins is an associate professor and Associate Head of School at
Monash University, Melbourne (Australia).
Bertrand Meyer is CTO of ISE and a professor at Monash.
To contact the authors, use info@eiffel.com.
Related links
Programming the .NET framework: Tutorial details announced for TOOLS USA.
© 1985-2012 Eiffel Software. All rights reserved. -- Privacy Policy |