Eiffel sur le Web : Intégration des systèmes Eiffel au Microsoft .NET Framework

Raphael Simon
Emmanuel Stapf
Bertrand Meyer
Interactive Software Engineering
Santa Barbara, Californie

Christine Mingins
Université de Monash
Melbourne, Australie

Juillet 2000

Résumé : Cet article décrit l'implémentation et l'intégration des systèmes Eiffel et Eiffel# au Microsoft .NET Framework, créant un environnement qui offre une solution de haut niveau aux développeurs de logiciels Internet.

Table des matières

Introduction
Production de systèmes .NET Framework à partir de Eiffel
Eiffel#, le langage
Recyclage de .NET Framework
Conclusion
Références

Introduction

Depuis l'été 1999, Interactive Sofware Engineering (ISE) travaille en partenariat avec Microsoft, ainsi qu'avec un groupe de l'Université de Monash en Australie, pour intégrer Eiffel au Microsoft® .NET™ Framework.

Eiffel est un environnement complet de développement de logiciels (ISE Eiffel) basé sur une méthode qui couvre la totalité du cycle logiciel : non seulement l'implémentation, mais aussi l'analyse, la conception et la maintenance. L'environnement, basé sur le langage Eiffel, applique tout à fait les principes de la technologie objet, et implémente les concepts de "Design by Contract" pour produire des applications hautement fiables, extensibles et réutilisables. ISE Eiffel est particulièrement destiné aux grands systèmes complexes et il est utilisé par des grandes organisations de la finance, de la défense, du temps réel, ainsi que par d'autres industries, pour des développements sur des projets critiques. Des universités du monde entier (telles que l'Université de Monash) utilisent également Eiffel pour enseigner à tous les niveaux la programmation et l'ingénierie logicielle.

Microsoft .NET Framework est la technologie Web de la future génération, développée chez Microsoft, qui recycle de nombreuses solutions techniques pour la création d'applications Internet. L'environnement inclut l'ASP.NET (Active Server Pages+) et permet de créer des applications Web beaucoup plus rapidement et facilement qu'avec la plupart des approches traditionnelles. La partie centrale de la technologie réside dans un run-time qui interprète et/ou compile le code machine (le langage interne de la machine virtuelle, également appelé "IL") avec les métadonnées. Ces dernières décrivent chaque composant du système, y compris le prototype de toutes ses méthodes, champs et évènements.

L'intégration d'Eiffel au Microsoft .NET Framework fournit une combinaison idéale pour les sociétés désirant tirer parti des technologies de haut niveau dans les systèmes d'exploitation, Internet et les infrasctructures Web, les méthodes de développement de logiciels et les environnements de développement. En particulier, l'ouverture de Eiffel aux autres langages et aux autres environnements, combinée à l'accent mis par .NET Framework sur la neutralité des langages, font du produit résultant un véhicule idéal pour la création d'applications contenant des composants en plusieurs langages différents, Eiffel servant à les "coller" ensemble. Dans cet article, nous décrivons cette combinaison ainsi que les différents challenges auxquels nous avons fait face lors de l'intégration de ISE Eiffel au .NET Framework.

Production de systèmes .NET Framework à partir de Eiffel

Pour un compilateur de langages, le ciblage de .NET Framework signifie réellement la capacité de produire le IL et les métadonnées associées.

Objectifs

La génération du IL serait suffisante si le seul but était de "compiler Eiffel sous le .NET Framework" ; mais notre objectif visant à fournir une structure générale pour l'interopérabilité multi-langage ne serait pas atteint, puisque les autres langages ne pourraient pas réutiliser les types Eiffel sans les métadonnées qui les décrivent. Un des buts fixés par ISE, en ce qui concerne l'intégration de Eiffel, est la capacité de réutiliser les types existants écrits dans n'importe quel langage, ainsi que la génération de types pouvant être compris par n'importe quel autre environnement de développement .NET Framework. Eiffel est un "extenseur" .NET Framework, ce qui signifie que vous pouvez écrire des classes Eiffel qui héritent de classes écrites dans d'autres langages, les étendre, puis les recompiler en IL, permettant la réutilisation du nouveau type dans d'autres environnements.

Cette génération du IL et des métadonnées s'effectue grâce à un switch spécial du fichier système (le fichier qui décrit le système Eiffel, également appelé fichier ACE), d'où une intégration complète du nouveau compilateur Eiffel dans l'environnement de développement intégré (IDE) de ISE, EiffelBench. Les programmeurs Eiffel existants pourront travailler comme ils le faisaient avant l'intégration.

Un autre aspect de la portabilité réside dans l'intégration du compilateur à Microsoft Visual Studio® .NET. Cette intégration aidera les nouveaux développeurs Eiffel qui ne sont pas désireux ou qui n'ont pas le temps d'apprendre un nouvel environnement, mais qui utilisent déjà Visual Studio, à réduire leur durée d'apprentissage. Cette intégration prendra en charge toutes les fonctionnalités spécifiques de Visual Studio telles que la mise en évidence de la syntaxe, le débogueur et certains assistants.

Un autre objectif clé est la possibilité d'écrire des pages ASP.NET dans Eiffel. ISE vise une prise en charge complète (appelée sémantique évoluée) pour permettre aux développeurs Eiffel d'incorporer Eiffel dans des pages ASP.NET en utilisant la directive "@language="Eiffel"". Cette prise en charge inclut également la possibilité d'écrire des services Web en Eiffel.

Stratégie

Avec ces objectifs en tête, ISE a organisé l'intégration autour de deux jalons principaux. La première étape de l'intégration verra la création d'un nouveau langage appelé Eiffel# (prononcer "Eiffel Sharp"). Ce sous-ensemble de Eiffel est spécialement conçu pour cibler le .NET Framework et incorporer tout le concept de Design by Contract. Il est suffisamment puissant pour créer des applications réelles tout en gardant le modèle d'objet natif du .NET Framework. La seconde étape consiste à étendre les résultats pour englober le modèle d'objet entier.

L'intégration dans Visual Studio et dans ASP.NET a commencé avec Eiffel#. L'intégration dans Visual Studio a commencé par une simple encapsulation du compilateur de commande en ligne qui n'inclut pas toutes les fonctionnalités disponibles. La prise en charge d'ASP.NET a commencé par le support des services Web.

La première version non-béta de Microsoft .NET Framework prendra totalement en charge toutes ces technologies, ainsi que le langage Eiffel entier. Eiffel# évoluera vers un recyclage accru des technologies .NET Framework.

Eiffel#, le langage

Le point central de cette section est la définition de la première version de Eiffel#. La spécification évoluera avec les bétas successives de .NET Framework. La condition clé requise pour Eiffel# est l'utilisation exclusive de run-times pour langages classiques qui ne soient pas spécifiques à Eiffel.

Eiffel, une courte introduction

Comme le reste de ce document définit Eiffel# en décrivant sa différence avec Eiffel, il nous faut d'abord voir les principales caractéristiques de Eiffel. Vous trouverez plus de détails dans les livres Object-Oriented Software Construction, 2nd edition et Eiffel: The Language, ainsi que sur le site Web de Eiffel à l'adresse http://www.eiffel.com/, d'où j'ai extrait une partie des informations qui composent cet article.

En tant que langage, Eiffel est un "pur" langage orienté objet (l'application la plus systématique des principes orientés objet dans les langages existants) basé sur un petit nombre de concepts puissants :

  • Le développement en continu. Eiffel couvre le cycle logiciel entier, de l'analyse et de la conception haut niveau à l'implémentation et à la maintenance, offrant un environnement conceptuel unique d'un bout à l'autre du processus.
  • Les classes servant de base unique à la fois pour la structure de modules et le système de types.
  • L'héritage pour la classification, la création de sous-types et la réutilisation.
  • Une approche prudente et effective de l'héritage multiple (changement de nom, sélection, redéfinition, indéfinition, héritage répété).
  • Les assertions pour l'écriture de logiciels corrects et robustes, avec débogage et documentation automatiques.
  • Le traitement discipliné des exceptions pour recouvrement gracieux des cas anormaux.
  • La création de types statiques sans sortie de boucles dans le système de types.
  • La liaison dynamique pour la souplesse et la sécurité.
  • La généricité, contrainte et non contrainte, pour la description des structures Container flexibles.
  • Une architecture ouverte, offrant un accès facile aux logiciels écrits dans d'autres langages tels que C, Microsoft Visual C++® et d'autres.

Pour avoir un avant-goût de la syntaxe et du style du langage (dont le but est d'être clair et simple), voici dans les grandes lignes une simple classe COUNTER décrivant un compteur :


indexing
description: "Compteurs que vous pouvez incrémenter
ou diminuer de 1, ou encore initialiser"
class
COUNTER
feature
 -- Access

item: INTEGER
 -- Valeur du compteur.

feature
 -- Modification de l'élément

increment
is
 -- Incrémenter le compteur de 1.
 
do
item := item + 1
end
decrement
is
 -- Diminuer le compteur de 1.
 
do
item := item - 1
end
reset
is
 -- Initialiser le compteur.
 
do
item := 0
 
end
end
 -- classe COUNTER

Au moment de l'exécution, cette classe aura des instances ; chaque instance est un objet qui représente un compteur séparé. Pour créer un compteur, vous déclarez l'entité correspondante,


mon_compteur: COUNTER

vous créez l'objet correspondant (où create est l'opération de création d'objet),


create
mon_compteur

puis vous lui appliquez les opérations de la classe (ses fonctions) :


mon_compteur.increment
...
mon_compteur.decrement
...
print (mon_compteur.item) 

De telles opérations apparaîtront dans les fonctions d'autres classes, appelées les clients de la classe COUNTER. Quelques commentaires supplémentaires sur cet exemple ; toutes les valeurs sont initialisées par défaut et ainsi chaque objet compteur commencera sa vie avec sa valeur, Item, initialisée à zéro (vous n'avez pas besoin d'appeler Reset au début). De plus, Item est un attribut qui est exporté en mode lecture seule : les clients peuvent dire Print (mon_compteur.item) mais non, par exemple, mon_compteur .item := 657, ce qui serait une violation du "masquage des informations". L'auteur de la classe peut, bien sûr, décider d'offrir cette fonctionnalité en ajoutant une fonction :


set (some_value: INTEGER) 
is
 -- Set value of counter to valeur_quelconque.
 
do
item := valeur_quelconque
end

auquel cas les clients utiliseront simplement my_counter.set (657). Mais ce sont les auteurs de la classe COUNTER qui décident quelles fonctionnalités fournir à leurs clients. La clause Indexing au début de la classe n'affecte pas sa sémantique (les propriétés des objets run-time correspondants), mais elle attache une documentation supplémentaire à la classe.

De tous les langages et de toutes les méthodologies de conception, Eiffel est le seul à appliquer le concept Design by Contract par l'intermédiaire de constructeurs tels que les invariants de classes, les préconditions et les postconditions. Supposons, par exemple, que nous voulions que nos compteurs soient toujours positifs. La classe aura maintenant un invariant :


indexing ... class

COUNTER
feature
 
 ...
invariant
item >= 0
end

À présent, Feature Decrement a besoin d'une précondition pour assurer que les clients ne tentent pas d'opérations illégales. Le mot-clé "require" introduit la précondition :


decrement
is
 -- Diminuer le compteur de 1.
 
require
item > 0
do
item := item - 1
ensure
item = 
old
item - 1
end

Le mot-clé "ensure" introduit la postcondition.

La préconditon dit au client : "Ne pense même pas à m'appeler à moins d'être sûr que le compteur est strictement positif". La postcondition dit : "Si tu es gentil (si tu observes la précondition), voici ce que je promets de faire en retour : je diminuerai le compteur de un".

L'invariant ajoute la promesse que "Et aussi, toutes mes opérations maintiendront le compteur positif ou à zéro". Les préconditions, les postconditions et les invariants sont appelés des assertions.

Eiffel# contre Eiffel

Cette brève introduction à Eiffel soulève quelques problèmes intéressants pour son intégration dans Microsoft .NET Framework. Le plus difficile est peut-être la prise en charge de l'héritage multiple, puisque le run-time pour langages classiques a été conçu pour prendre en charge uniquement le simple héritage. Comme Eiffel# doit seulement utiliser le run-time pour langages classiques, il doit suivre le modèle d'objet .NET Framework et par conséquent ne pas permettre l'héritage multiple de classes effectives ou partiellement différées. Vous pouvez malgré tout utiliser l'héritage multiple de classes purement différées ; dans ce cas, elles sont générées comme interfaces. La classe parente effective ou partiellement différée, s'il en existe, est le type de base.

Eiffel# ne prend pas en charge les nouveaux éléments Eiffel qui ont été ajoutés après la publication de l'édition actuelle de Eiffel: The Language. Ces constructeurs comportent les agents et les classes associées, la conformité générique et la création d'arguments génériques.

Autre différence entre le run-time pour langages classiques et le modèle d'objet Eiffel : le manque de reconnaissance de la covariance dans le premier. Pour cette raison, Eiffel# ne prend pas en charge la covariance. C'est-à-dire que vous ne pouvez pas redéfinir les types des arguments ou les résultats des fonctions dans le descendant d'une classe.

La dernière différence entre les deux langages réside dans la sémantique des types étendus. Dans le .NET Framework, les types étendus sont directement mappés à ce qu'on appelle les types de valeurs. Bien que fondamentalement identiques, les types étendus Eiffel et les types de valeurs .NET Framework n'ont pas exactement le même comportement. En particulier, les types de valeurs sont scellés, ce qui signifie qu'on ne peut pas hériter d'eux. En conséquence, vous ne pouvez pas hériter de types étendus dans Eiffel#.

Il n'existe pas d'autres différences entre Eiffel et Eiffel# ; en fait, Eiffel# prend en charge les contrats, le traitement des exceptions et la généricité, quelques-unes des caractéristiques de la programmation Eiffel.

Spécifications du .NET Framework

Eiffel# a également des fonctionnalités spécifiques destinées au recyclage du .NET Framework. La première différence importante est le packaging des applications. Tandis que dans un environnement standard, donner le choix entre créer un DLL ou un EXE est suffisant, le .NET Framework définit de nouveaux concepts, tels que les assemblées et les modules, que tout compilateur ciblant l'environnement doit prendre en charge. Une assemblée se compose d'un groupe de modules et correspond à une application. Un module peut être soit un DLL ou un EXE. Pour cette raison, le fichier ACE pour Eiffel# introduit de nouvelles options pour décrire les différents modules qui feront partie de l'assemblée. Le compilateur Eiffel# génère une assemblée dont le nom est le nom de système tel qu'il est donné dans le fichier ACE. Vous pouvez spécifier si l'assemblée doit être un EXE ou un DLL dans l'option Default il_generation comme suit :


system


sample
root
ROOT_CLASS: "make"
default
il_generation
 ("exe") -- "dll" pour générer un DLL
 ...

Dans cet exemple, le compilateur génère un fichier unique "sample.exe" contenant à la fois l'assemblée et le module. Si vous désirez spécifier des fichiers différents pour des modules multiples, vous pouvez utiliser l'option module pour chaque cluster et outrepasser l'option pour chaque classe du cluster :


system


sample
root
ROOT_CLASS: "make"
default
il_generation
 ("exe") -- "dll" pour générer un DLL
 
cluster


root_cluster: "c:\my_app"
default

module
 ("my_app")
 
option

module
 ("root"): ROOT_CLASS
end
 ...

Ce fichier ACE définit trois modules :

  • Le premier module, qui comprend le manifeste de l'assemblée, est "sample.exe".
  • Le second module, "my_app.dll", comprend toutes les classes du cluster root_cluster excepté la classe ROOT_CLASS.
  • Le dernier module, "root.dll", comprend la classe ROOT_CLASS. Ce mécanisme vous permet de définir autant de modules que vous le voulez et de grouper comme vous le désirez les classes du système Eiffel.

Une autre fonction spécifique de .NET Framework est la notion d'espace-nom. Chaque type .NET Framework est associé à un espace-nom qui assure le caractère unique du nom de type dans le système. Vous pouvez définir un espace-nom par défaut pour toutes les classes du système Eiffel en utilisant l'option ACE Default suivante :


system


sample
root
ROOT_CLASS: "make"
default
il_generation
 ("exe") -- "dll" pour générer un DLL
 
namespace
 ("MyApp")
 ...

Dans cet exemple, toutes les classes du système Eiffel seront générées dans l'espace-nom "MyApp.<cluster_name>" où <cluster_name> est le nom du cluster qui contient la classe. Vous pouvez outrepasser l'espace-nom par défaut pour chaque cluster, comme suit :


system


sample
root
ROOT_CLASS: "make"
default
il_generation
 ("exe") -- "dll" to generate a DLL
 
namespace
 ("MyApp")
 
cluster


root_cluster: "c:\my_app"
default

module
 ("my_app")
 
namespace
 ("Root")
 
option

module
 ("root"): ROOT_CLASS
end
 ...

Avec ce fichier ACE, toutes les classes du cluster root_cluster sont générées dans l'espace-nom "Root". Vous noterez que le nom spécifié dans la clause Cluster n'est pas ajouté à l'espace-nom défini dans la clause Default. Enfin, la classe Eiffel# pourrait inclure une clause Alias (pour avoir une description du mot-clé "alias", reportez-vous à la rubrique "Classes externes"), auquel cas le nom spécifié dans la clause outrepasse tout espace-nom spécifié dans le fichier ACE.

Une autre différence majeure provenant de la nature dynamique de .NET Framework est le comportement des contrats au moment de l'exécution. Dans un environnement classique, une violation de contrat a pour résultat de créer une exception, et le niveau de contrôle d'assertion est décidé au moment de la compilation. Cette approche n'est plus satisfaisante dans .NET Framework où l'appelant d'une fonction contractée peut être dans un module différent. Le client d'un composant contracté doit pouvoir choisir le niveau de contrôle de contrat, s'il y en a un. D'où la nécessité d'avoir une interface standard qui définit les opérations possibles disponibles sur les contrats. Cette interface doit être implémentée par les composants contractés. Cette interface, appelée IContract, définit les fonctions de paramètrage du niveau de contrôle de contrat :

  • Le contrôle de précondition : c'est le niveau inférieur, seules les préconditions des fonctions seront contrôlées.
  • Le contrôle de postcondition : niveau intermédiaire, les préconditions et les postconditions seront contrôlées.
  • Le contrôle d'invariant : contrôle total, tous les contrats (préconditions, postconditions et invariants) seront contrôlés.

IContract permet également de choisir si une exception doit être créée en cas de violation de contrat.


interface
IContract
{
 // subject to modification
 
public
bool precondition_activated;
 
public
bool postcondition_activated;
 
public
bool invariant_activated;

 
public
void enable_precondition();
 
public
void
enable_postcondition();
 
public
void
enable_invariant();

 
public
void
disable_precondition();
 
public
void
disable_postcondition();
 
public
void
disable_invariant();

 
public
bool
exception_on_violation();
 
public
void
enable_exception_on_violation();
 
public
void
disable_exception_on_violation();

 
public
ContractException
last_contract_exception();
}

Le compilateur Eiffel# génèrera automatiquement une implémentation de l'interface IContract avec le niveau de contrôle de contrat par défaut spécifié dans le fichier ACE :


system


sample
root
ROOT_CLASS: "make"
default
il_generation
 ("exe")
 
assertion
 (require) -- peut être 'no', 'require', 'ensure' ou 
 'invariant'.
 ...

Si vous omettez l'option Assertion, IContract n'est pas généré. Si vous choisissez l'option "no", IContract est généré, mais aucun contrat ne sera contrôlé tant que le contrôle de contrat n'aura pas été activé par le client.

Recyclage de .NET Framework

Nous avons vu comment vous pouvez utiliser Eiffel# pour créer des composants .NET Framework. Puisque le compilateur génère toutes les métadonnées nécessaires, d'autres langages peuvent réutiliser comme ils le veulent (héritage ou relation client) les composants Eiffel#. La question suivante est : "Comment réutiliser des composants existants dans Eiffel# ?" Les composants existants couvrent les bibliothèques Microsoft, ainsi que les composants écrits par d'autres parties.

Stratégie

ISE donnera à Eiffel# des bibliothèques qui encapsulent le Microsoft framework. Ces bibliothèques comportent des wrappers pour la Bibliothèque de classes de base, les Win Forms et les Web Forms. La Bibliothèque de classes de base inclut la définition des types de base nécessaires à tout système, tels que les collections, les services distants, les services des tâches, la sécurité et l'accès E/S.

Les classes Win Forms permettent de créer une GUI dans le .NET Framework. Elles sont un wrapper autour des API Win32 qui fournit un modèle d'objet propre pour créer une GUI. Les Web Forms permettent également de créer des GUI sur le Web ; ils comprennent des types tels que DataGrid ou HTMLImage.

Ces trois librairies sont distribuées avec Eiffel# pour que vous puissiez réutiliser ces classes directement dans votre système.

L'Émetteur

Il est clair que les bibliothèques Microsoft ne sont pas les seuls composants .NET Framework auxquels vous voulez accéder. Votre système peut nécessiter l'intégration de centaines de composants écrits dans toutes sortes de langages. Pour cette raison, ISE offre un outil appelé Émetteur capable d'analyser toute assemblée .NET Framework et de produire un wrapper Eiffel pour chaque type qu'elle définit. L'Émetteur accède aux métadonnées liées dans chaque type défini dans l'assemblée, crée un équivalent Eiffel et génère les classes correspondantes.

Bien qu'une assemblée puisse inclure des références à d'autres assemblées (appelées assemblées externes), l'Émetteur ne génèrera des classes que pour les types définis dans l'assemblée donnée. Cela évite, par exemple, de générer les Bibliothèques de classes de base pour toutes les assemblées à encapsuler (puisque pratiquement toute assemblée a une référence aux Bibliothèques de classes de base).

Comme tout type .NET Framework publique doit être conforme à la CLS (Common Language Specification), et comme la CLS diffère par certains aspects du modèle Eiffel, l'Émetteur doit accomplir quelques transformations non triviales pour traduire les types .NET Framework en classes Eiffel. La différence la plus importante est peut-être l'inclusion de la surcharge dans la CLS. Le modèle Eiffel interdit la surcharge et nécessite la levée d'ambiguïté de toute fonction surchargée. Voici l'algorithme utilisé à cette fin :

Laisser f1, f2, ..., fn être des fonctions NGWS surchargées portant le même nom
 (n >= 2)

For 1 <= i <= n, laisser Si être la signature de fi :

 Si = [Ti1, Ti2, ..., Tim]
 (im >= 0) 

Tous les Si sont différents en considération des règles de surcharge.
Nous dirons qu'une position u est unique pour une fonction fi (pour 1 <= u <= im) 
 s'il existe au moins une autre fonction fj (1 <= j <= n, j /= i) 
 telle que u <= jm et Tju /= Tiu.
Nous définirons un nom unique Ni pour fi de sorte que Ni soit sous la forme
 N_Ti1_Ti2..._Tiu (0 <= u <= im), où [Ti1, Ti2, ... ,Tiu] est
 la sous-séquence initiale la plus petite de Si différente de la 
 sous-séquence correspondante pour toutes les autres fonctions. En
 considération des règles de surcharge, une telle sous-séquence 
 existe et caractérise Si de façon unique.

En d'autres termes, l'algorithme ajoute après le nom de la fonction autant de noms de types de signatures qu'il est nécessaire pour obtenir un nom unique. Par exemple, la fonction C# suivante :


public static
void
WriteLine (String
format, Object
arg0);
 
public static
void
WriteLine (int
value);
 
public static

void
WriteLine (String
value);

est traduite en Eiffel, par l'Émetteur, de la manière suivante :


WriteLine_System_String_System_Object (format: STRING; arg0: ANY)
 WriteLine_System_Int32 (value: INTEGER)
 WriteLine_System_String (value: STRING)

Vous remarquez la correspondance des types de base que l'Émetteur établit sur les types d'arguments. Le tableau suivant donne la liste de tous les types de primitives tels qu'ils sont définis dans la CLS et leur équivalent Eiffel :

Type de primitive CLS (Description) Type Eiffel
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

Dans certains cas, pourtant, l'Émetteur ne peut pas générer le code Eiffel valide. Vous devrez alors modifier manuellement la classe générée. Heureusement, ce cas est rare (si vous voulez encapsuler la totalité des Bibliothèques de classe de base, plus de 600 types, il vous faudra modifier seulement quatre classes). Le problème principal, qui est à l'origine de cette génération erronée, réside dans l'utilisation de ladite MethodImpls. Ce mécanisme autorise un type à forcer la liaison de l'une de ses fonctions d'interface à une fonction ayant un nom ou même une signature différente. Ces MethodImpls sont souvent utilisés dans les cas où la covariance aurait été utilisée si elle avait été disponible dans le run-time pour langages classiques. Malheureusement, il est pour le moment impossible d'accéder aux informations des MethodImpls par l'intermédiaire des mécanismes réflexifs du .NET Framework.

Les classes externes

Les classes Eiffel# que l'Émetteur génère n'incluent aucune logique ; elles sont juste nécessaires au système de type Eiffel. Cela signifie que le compilateur Eiffel# ne génère aucun code IL pour ces classes. Tout appel de fonctions sur des classes générées par l'Émetteur sont des appels directs au type .NET Framework ; il n'y a pas d'indirections, et, par conséquent, pas de pénalité d'exécution. Pour que le compilateur reconnaisse de telles classes, ISE a introduit dans Eiffel un nouveau mécanisme appelé classes externes. Ces classes ne peuvent inclure que des fonctions externes (des fonctions qui ne sont pas écrites en Eiffel, mais qui sont des méthodes ou des fonctions d'un type .NET Framework déjà existant). Toutes les fonctions d'une classe externe doivent appartenir au même type .NET Framework. Vous pouvez déclarer un tel type avec la syntaxe suivante :


frozen external class

SYSTEM_CONSOLE
alias


"System.Console"
...

La chaîne qui suit alias contient le nom du type .NET Framework que la classe Eiffel encapsule. Comme les types .NET Framework peuvent être scellés (ils peuvent interdire à d'autres types d'hériter d'eux), et comme un tel concept n'existe pas en Eiffel, Eiffel# introduit une nouvelle utilisation du mot-clé Eiffel "frozen". Vous pouvez utiliser frozen devant external pour indiquer au compilateur Eiffel# qu'aucune classe Eiffel# ne doit hériter du type .NET Framework.

La classe externe doit alors donner la liste de toutes les fonctions auxquelles vous avez besoin d'accéder. Toutes ces fonctions sont des fonctions externes. Voici la syntaxe d'une fonction .NET Framework externe :


frozen
ReadLine: STRING
is
external
"IL static signature :System.String use System.Console"
alias
"ReadLine"
end

Ici frozen indique que la fonction ne peut pas être redéfinie dans un descendant (remarque : vous pouvez redéfinir des fonctions externes si elles sont virtuelles, auquel cas frozen ne doit pas être utilisé), ReadLine est le nom de la fonction Eiffel#, et STRING le type de retour de la fonction. La chaîne qui suit le mot-clé "external" spécifie son type, la signature de la fonction .NET Framework, ainsi que le type .NET Framework sur lequel la fonction est définie. La chaîne qui suit le mot-clé "alias" contient le nom .NET Framework de la fonction. Il existe plusieurs sortes de fonctions externes qui dépendent du type de méthode auquel elles donnent accès. Eiffel# définit sept nouvelles sortes de fonctions externes qui sont répertoriées dans le tableau suivant :

Type de fonction Microsoft .NET Framework Fonction externe Eiffel
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"

Si vous en avez besoin, vous pouvez définir ces fonctions externes dans des classes non externes. Dans le cas spécial de classes externes, le nom de type .NET Framework qui apparaît à la fin de la chaîne suivant le mot-clé "external" doit être le même que celui qui apparaît après le mot-clé "alias", suivant la déclaration de la classe (voir le fragment de code dans la rubrique "Classes externes").

Les fonctions externes peuvent être appelées à partir de clients ou de descendants de la classe, comme vous appelleriez toute autre fonction Eiffel. Ainsi, si votre système inclut une fonction qui nécessite une entrée utilisateur, elle peut inclure le code suivant :


need_user_input
is
 -- Prendre l'entrée utilisateur et lui appliquer les actions souhaitées.
 
local
io: SYSTEM_CONSOLE
input: STRING
do
create
io.make
input := io.ReadLine -- calls 
 System.Console.ReadLine()
 -- Appliquer les actions souhaitées
 
end

Cependant, comme ReadLine est une fonction externe statique, il n'est pas nécessaire d'instancier le wrapper ; le code suivant est donc suffisant pour faire l'appel externe :


need_user_input
is
 -- Prendre l'entrée utilisateur et lui appliquer les actions souhaitées.
 
local
io: SYSTEM_CONSOLE
input: STRING
do

-- Suppression de io
input := io.ReadLine -- calls 
 System.Console.ReadLine()
 -- Appliquer les actions souhaitées
 
end

Ce code est également valide pour l'accès et le paramétrage des zones statiques. Pour d'autres types de fonctions externes, il faut instancier le wrapper.

Les bibliothèques Eiffel#

Le compilateur Eiffel# est accompagné d'une Bibliothèque de classes de base Eiffel# qui suit les principes de conception de la bibliothèque EiffelBase standard. Cette bibliothèque fait un usage étendu de la généricité et des contrats pour offrir des structures de données puissantes que vous pouvez réutiliser dans votre système. Cette bibliothèque utilise les classes externes qui encapsulent la Bibliothèque de classes de base, également fournie avec Eiffel#.

Les deux autres séries de classes fournies avec le compilateur encapsulent les bibliothèques .NET Framework Win Forms et Web Forms pour vous permettre de créer facilement des applications GUI et Web.

Le Microsoft .NET Framework Contract Wizard

Dans le cadre de ce développement, nous avons produit un nouvel outil, le Microsoft .NET Framework Contract Wizard, qui, par l'intermédiaire du mécanisme des métadonnées, permet aux utilisateurs d'ajouter de façon interactive des contrats de type Eiffel aux composants .NET Framework provenant de langages arbitraires. Cet outil sera décrit en détail dans un autre article, mais il est déjà disponible pour les développeurs désireux d'appliquer les avantages de "Design by Contract" dans des langages autres que Eiffel. C'est le système de métadonnées du .NET Framework qui a permis cette extension importante.

Conclusion

L'objectif de ce projet et des produits résultants est d'offrir une intégration totale entre ISE Eiffel et le Microsoft .NET Framework. Le pouvoir combiné de la plate-forme et de l'environnement de développement doit produire un environnement de rêve pour la création des applications Internet puissantes que la société attend de nous aujourd'hui. Eiffel sur le Microsoft .NET Framework fournit une flexibilité, une productivité et une haute fiabilité. En particulier, il est impossible de surestimer les bénéfices de "Design by Contract" dans un environnement distribué, où la recherche des bogues après coup s'avère être une expérience pénible et coûteuse. Eiffel sur le Microsoft .NET Framework, ajouté aux autres avantages de la méthode Eiffel — développement en continu, programmation générique, masquage des informations et autres principes de l'ingénierie logicielle, et puissant mécanisme d'héritage — offre une solution de haut niveau aux développeurs de logiciels Internet ambitieux.

Références

Meyer, Bertrand. Eiffel: The Language. Prentice Hall, 1992.

Meyer, Bertrand. Object-Oriented Software Construction, 2nd edition. Prentice Hall, 1997.

Le site Web de ISE Eiffel à l'adresse http://www.eiffel.com/.

Raphael Simon est ingénieur à Interactive Software Engineering (Santa Barbara, Californie), responsable de la division applications et outils Windows. Emmanuel Stapf est ingénieur à ISE, directeur de la division compilation et environnement. Christine Mingins est professeur adjointe et Directrice adjointe de l'Université de Monash (Australie). Bertrand Meyer est CTO de l'ISE et professeur à Monash.

Dernière mise à jour le mardi 3 octobre 2000