Modules

Berlin est presque entièrement fabriqué de modules branchables, remplaçables ; donc il vous faut avoir quelques connaissances sur l'endroit où les modules résident, comment les charger, et ce que l'on peut faire avec eux, une fois chargé.

Client serveur

Si vous écrivez une application pour berlin, vous étes en train d'écrire en fait un programme "client" corba qui demande à accéder à un service particulier (accéder à l'écran, au bureau, et à l'utilisateur) à partir du "serveur" d'affichage. Nous allons maintenant parler de l'aspect du modèle client/serveur, comment cela est présent dans berlin.

Services multipléxés

L'ordinateur que vos utilisateurs auront en face d'eux présente des services d'entrée, d'affichage graphique, audio, etc. qui ne peuvent pas dans leur mode d'opération par défaut être partagés entre de multiples processus. Puisque les utilisateurs veulent fréquemment faire plus d'une chose à la fois, ils dépendent sur un serveur d'affichage comme berlin du multiplexage des services entre les clients. Plutôt que de présenter une métaphore bas niveau pour les périphériques multipléxés, berlin offre aux clients des abstractions de plusieurs concepts d'interface utilisateur, et les traduits en interne en représentations concrètes en utilisant les ressources accessibles. Ainsi, votre programme ne manipulera pas (généralement) des coordonnées de pixel spécifiques, d'indices de table de couleur, de buffers de sons audio, d'indices de glyphes de polices, ou d'autres méchanismes bas niveau lors de communications avec le serveur. A la place, il traitera plus souvent avec des objets logiques uniquement qui vous inquiète en résumé pour la présentation à l'utilisateur, et qui dépendent des modules de berlin pour construire des représentations appropriées.

Utilisation distante

Les clients de Berlin communiquent avec le serveur (et les autres clients) en utilisant corba. Corba est un protocole réseaux transparents pour des appels distants de méthodes d'objets dans un important panel de langages. Cela signifie que votre programme client sera capable d'accèder à des ressources multiplexées par un serveur berlin tournant sur un ordinateur distant, et n'aura pas besoin de faire d'ajustement particulier dans son code pour fonctionner de la sorte. Les serveurs distants et locaux apparaîssent pour le programmeur exactement de la même manière. De plus, vous pouvez utiliser n'importe quel langage pourvu qu'il existe un lien corba.

La première chose que votr eprogramme aura à faire est d'obtenir ses repères dans l'"univers de berlin", en cherchant un context de service de nommage de corba et de contacter un serveur d'affichage. Ensuite, vous aurez besoin d'obtenir un "context de serveur" à partir du serveur, et de le présenter avec un "context de client" construit en local, que le serveur utilisera pour procéder aussi bien à des vérifications de sécurités, que pour surveiller le comportement de clients inutiles et garder une trace des objets devant être désallouer lorsque le client se deconnectera. Voici un exemple de code et un diagramme de séquence pour vous montrer ce qui est fait.

Example 2-2. Se connecter et établir des contexts (côté client)

  CORBA::ORB_ptr orb = CORBA::ORB_init(argc,argv,"omniORB2");
  CORBA::BOA_ptr boa = orb->BOA_init(argc,argv,"omniORB2_BOA");
  boa->impl_is_ready(0,1);
  ServerContextManager_ptr manager = getManager(orb);
  ClientContextImpl *myClientContext = new ClientContextImpl;
  myClientContext->_obj_is_ready(boa);
  ServerContext_ptr myServerContext = manager->newServerContext(myClientContext->_this());

Centralisation

Les contexts de serveur vous permez d'obtenir des ressources qui ont été centralisé, dans le serveur. Comme mentionné pr&écedemment, berlin centralise beaucoup plus de choses que les autres systèmes de fenêtrage ; ceci pour permettre à l'utilisateur aussi bien de choisir entre plusieurs politiques d'interface utilisateurs complet que de spécifier une personnalisation simple des styles-préférences, qui s'appliqueront de façon semblable sur tous les clients connectés. Ainsi, lorsque vous cosntruisez un élément d'interface utilisateur, vous le faites dans le processus du serveur d'affichage plutôt que dans une bibliothèque cliente.

Modules côté serveur

Puisque nos buts étaient d'être extensible et personnalisable, le serveur d'affichage charge ses politiques centralisées et des objets à partir de modules dynamiquement linkés, à la volée.

Objets dynamiques

Les objets dynamiques (ou "objets partagés") ont été initialement développé pour réduire la consommation d'espace sur les ordinateurs qui avaient beaucoup de code dans des bibliothèques communes linkés dans chaque programme. Ils vous laissent isoler une certaine quantité de code et de données statiques dans des objets que le système d'exploitation lie dans le programme à la volée. Modifier le "fichier .so" sur le disquepeut modifier le comportement du programme lors de sa prochaine utilsiation, puisqu'il sera relinké avec le nouveau code. De cette manière, nous pouvons aussi utiliser les objets dynamiques comme un méchanisme de personnalisation. Pour changer le comportement du serveur, vous avez simplement besoin de déplacer un "fichier .so" à l'extérieur et d'en mettre un autre à sa place. Le linker dynamique prendra en compte le reste.

Interface commune

Au delà de personnaliser simplement le serveur, nous voulons aussi être capable d'ajouter de nouvelles fonctionnalités dynamiquement aux serveurs existants, sans les recompiler. Pour cela, nous avons besoin d'un méchanisme pour le serveur pour inspecter un objet dynamique arbitraire qu'il trouverait sur le disque, décider s'il contient de nouvelles fonctionnalités pour le serveur, et dans le cas positif, charger l'objet et le rendre accessibles pour les clients et les autres objets dans le serveur. Pour cela, nous avons besoin d'un manière commune pour inspecter les objets : une interface commune que le serveur peut chercher à l'intérieur du fichier objet. Cette interface contients 2 fonctions : getName() et getPlugin(), et est inséréer dans n'importe quel "greffons" pour le serveur de berlin en utilisant la macro préprocesseur EXPORT_PLUGIN(implementationName,interfaceName).

dlopen

On scanne un "répertoire de greffons" lors du démarrage du serveur, et pour chaque fichier .so que nous y trouvons, nous appelons dlopen() sous unix, qui est un service de linkage dynamique manuel. Il charge l'objet dans le serveur, et nous le passons en revue pour l'interface spéciale (getName() et getPlugin()) pour déterminer quelle interface il supporte et pour garder un pointeur sur une fonction qui peut être utilisée pour instancier l'interface. Donc, par exemple, si un greffons supporte l'interface IDL WidgetKit, sa méthode getName() retournera "IDL:omg.org/WidgetKit:1.0" comme libellé de l'interface statique. Pour construire une instance de WidgetKit supporté par ce greffon, nous appelerons la fonction associée getPlugin(). Les clients peuvent accéder aux modules en utilsiant leurs noms, comme nous le verrons dans la partie suivante.

Les factorys

Les Factorys favorise le découplage d'un créateur d'objet de son utilisateur. Nous utilisons abondamment les factorys pour négocier où, quand et quels objets sont construits. Un objet factory possède un certain nombre de méthodes qui construisent d'autres objets, par délégation. Par exemple, un peut déléguer la constructions des boutons à un "factory d'élément", et tous les objets qui seront intérésés par des boutons appelleront widgetFactory-newButton(). Cela a des subtiles, mais extrêmement importante, implications.

Création abstraites

Dans les langages OO "normaux", pour fabriquer un nouvel objet, vous appelez l'"opérateur new", qui appelle directement le constructeur pour le type concret de l'objet que vous frabriquez. Le problème avec cela, c'est qu'il est difficile d'ettendre le système en ajoutant des sous-classes ou différentes implémentations, parce que le "client" construira toujours le même objet. Par exemple, si vous code en dure la construction d'un "simpleTextBuffer" dans vos programmes, et que quelqu'un arrive et fabrique un nouveau fancyTextBuffer qui implémente extacement la même interface mais qui est plus intuitif pour l'utilisateur, vous devrez manuellement revenir et éditer tous les appels que vous faites avec new simpleTextBuffer(). Si, d'un autre côté, vous utilisez une factory, il y aurait un point de contrôle central sur la construction du simpleTextBuffer, et vous pourrez altérer l'objet factory (ou en fournir un entièrement nouveau) qui retournera un nouveau fancyTextBuffer.

Factory générique

Le serveur d'affichage de berlin possède une "factory générique" qu'il utilise poru construire tous les autres objets (incluant leur propre factorys). La factory générique implémente une spécification de l'OMG appelée "cycle de vie", qui signifie qu'elle est une sorte de factory que les autres systèmes s'attendent à voir, lors de communication corba. C'est quelque chose de peu commode à utiliser, puisqu'il utiliseuniquement despaires nom/valeur comme arguments de construction, et retourne des objets d'un type très générique, nécessitant de faire un changement de cast. Notre factory générique accepte un unique argument, qui est le nom de l'interface dont il est supposé en retourner une instance. Il cherche alors dans son cache de noms d'interface trouvé dans les objets dynamiques, et en construits une instance en utilisant un pointeur de fonction qu'il trouve.

Factorys spéciales (kits)

Puisque la factory générique est si pue commode à utiliser dans les cas généraux, nosu utilisons des factorys particulier appelées "kits" pour la plupart de notre travail. Tout de suite après que votre client ait établit un context de serveur, il demandera au factory générique de créer des kits, tel que le DesktopKit, le WidgetKit, le FigureKit, le LayoutKit, et le TextKit. Ces kits ne sont rien de plus que des factorys particuliers , et sont chargés à partir de greffons ( donc entièrement remplaçable) exactement comme n'importe quel autre que la factory générique produit. Les kits ont des méthodes fortement typéesqui accepte un certain nombre de paramètres significatifs et retourne des instances des types les plus utilisés. Cela rend la programmation beaucoup plus pratique pour vous. Voici un exemple opur obtenir les kits et les utiliser pour construire des objets.

Example 2-3. Obtenir et utiliser les kits

  // use the "obtain" macro to talk to genericFactory
  DesktopKit_var dk = obtain(myServerContext,DesktopKit);
  LayoutKit_var lk = obtain(myServerContext,LayoutKit);
  WidgetKit_var wk = obtain(myServerContext,WidgetKit);
  FigureKit_var fk = obtain(myServerContext,FigureKit);
  CommandKit_var ck = obtain(myServerContext,CommandKit);

  Graphic_var hbox = lk->hbox();
  Window_var window = dk->shell(Graphic_var(lk->marginFlexible(hbox, 10., 50., 10.)));

Comme exemple, le DesktopKit possède une méthode appelée Graphic_ptr shell(Graphic_ptr g), qui prend une unique illustration en paramètre et retourne un objet "decortiquer" sur le bureau -- c'est-à-dire, un objet avec des sortes de méchanismes de contrôle d'application de niveau haut comme les barres de titre, les contrôles de fenêtres, ou d'autres décorations. L'application cliente est complétement isolée du "shell" placé autours de leurs illustrations, ou même si leurs illustrations sont encore visibles. En remplaçant le DesktopKit ou en y modifiant quelques configurations, un utilisateur final ou un personaliseur peut alterer totallement son aspect, et plusieurs autres de l'environnement du bureau. Le shell est frequemment la dernière méthode qu'une application appelera, immédiatemment après avoir construit son interface utilisateur et limite les rappels (callbacks) à plusieurs composants de l'UI.