Scripting .NET Customizing Lösungen für .NET-Anwendungen mit JScript

Die Seite wird erstellt Silvester Reinhardt
 
WEITER LESEN
Scripting .NET Customizing Lösungen für .NET-Anwendungen mit JScript
32
Quellcode auf CD     Core.NET                                    Scripting in .NET

                                                                                           Scripting .NET
                                                                                          Customizing Lösungen für
                                                                                      .NET-Anwendungen mit JScript
                                                                                                                                   von Karsten Strobel

                                                                 „Was nicht passt, wird passend gemacht“ ist ein bekannter Spruch vom Bau.
                                                                  Dieses kreative Motto greift immer dann, wenn der Architekt mal nicht daran
                                                                  gedacht hat, in die Gästetoilette ein Fenster einzuzeichnen oder wenn er viel-
                                                                    leicht die Garage gleich ein paar Zentimeter über die Grundstücksgrenze hi-
                                                                   naus geplant hat. Kein Problem, denn für erfahrene Handwerker gibt es fast
                                                                   nichts, was sich nicht schnell noch vor Ort korrigieren ließe. Auch Software-
                                                                 entwickler handeln gerne nach diesem Kredo. Schwierig wird es allerdings bei
                                                                  Standardsoftware, quasi den Fertighäusern der IT-Zunft. „Customizing“ heißt
                                                                        hier das Zaubermittel und Scripting ist das mächtigste Werkzeug dieser
                                                                                                                               Handwerkskunst.

                   Kundenspezifische Anpassungen sind für        scheiden, ob die Skriptprogrammierung         che noch ein paar andere Unterschiede
                   viele Softwareentwickler die (professio-      nur vom Entwickler bzw. geschultem Per-       aufweisen, sollten Sie sich für eine ent-
                   nelle) Existenzberechtigung Nummer eins.      sonal oder von jedem Anwender ausge-          scheiden. Ich verwende JScript.NET, weil
                   Sie schaffen für ihre Anwender Lösungen,      führt werden soll. Ist letzteres der Fall,    die Sprache meinen durch C# geprägten
                   die man so nicht von der Stange kaufen        steigt der Aufwand für Dokumentation          Gewohnheiten näher kommt und weil
                   kann. Was einerseits ein Segen für das Ge-    und Editier-Komfort natürlich erheblich       die Engine in einigen Punkten einfacher
                   schäft ist, entpuppt sich mitunter anderer-   und es sind höhere Maßstäbe in puncto Si-     zu handhaben ist.
                   seits als technische Sackgasse, wenn am       cherheit zu setzen.                               Gegenüber der aus der COM-Welt be-
                   laufenden Band individuelle Programme             Mit Visual Studio for Applications        kannten Scripting Engine hat sich eini-
                   geschaffen werden, die sich mangels ein-      (VSA) bietet Microsoft bzw. dessen Part-      ges geändert. Die Spracheigenschaften
                   heitlicher Codebasis nur schwer warten        nerfirma Summit Software [1] eine sehr        von VB und JScript wurden umgebaut und
                   und weiterentwickeln lassen. Das univer-      professionelle IDE einschließlich Debug-      erweitert, um in das Korsett des .NET
                   selle Produkt zu schaffen ist wiederum        ger für eine endkundentaugliche Scripting-    Frameworks zu passen. Man kann sogar
                   kaum möglich, lebt man doch davon, dass       Integration in .NET-Anwendungen an.           sagen, dass der Begriff „Skript“ nicht
                   es diese gar nicht geben kann. Deshalb        Die ziemlich hohen Lizenzgebühren lassen      mehr ganz zutreffend ist, denn die Pro-
                   sind Lösungen, die eine weitgehende Kon-      diese Komfortvariante leider oft ausschei-    gramme werden nicht mehr interpretiert,
                   figurierbarkeit einer Software erlauben,      den. Glücklicherweise sind die Runtime-       sondern in native IL-Assemblies übersetzt
                   zum Beispiel in Form grafischer Druck-        Komponenten von VSA, also die Objekte         und nicht anders ausgeführt als „echte“
                   vorlagen-Editoren oder der immer wieder       zum Kompilieren und Ausführen von             .NET-Programme, was einen erheblichen
                   anzutreffenden reservierten Datenbank-        Skripten, bereits im .NET Framework ent-      Zugewinn an Performance und Stabilität
                   felder zur sonstigen Verwendung, so ver-      halten, sodass man sie – ohne die glanzvol-   bedeutet. Der größte Vorteil, der sich aus
                   breitet.                                      le Oberfläche – zum Nulltarif nutzen kann.    dem Architekturwechsel ergibt, ist die
                       Will man aber auch für das Unvorher-                                                    quasi nahtlose Integrierbarkeit der Scrip-
                   sehbare vorsorgen, ohne von Fall zu Fall      Man nehme...                                  tingebene in die Host-Anwendung.
                   doch Sonderversionen anfertigen zu müs-       Für eine Scripting-Lösung benötigt man            Um ein Skriptprogramm auszuführen,
                   sen, dann kann man die eigene Software        zwei Zutaten: Eine Scripting Engine und       muss der Host zunächst eine Instanz der
                   mit Scriptingfunktionen ausstatten, um        den Scripting Host. Der Host ist nichts       Scripting Engine, auch Skriptmodul ge-
                   Anpassungen auch nach Fertigstellung ei-      anderes als Ihr eigenes Programm. Von         nannt, erzeugen. Dieses stellt dem Host ei-
                   nes Releases noch per Programmcode –          Hause aus stehen dem Host zwei Engines        ne IVsaEngine-Schnittstelle (siehe Listing
                   und damit sehr flexibel – nachreichen zu      zur Verfügung, nämlich für Visual Basic       1) zur Verfügung. Der Host muss seiner-
                   können, ohne das eigentliche Produkt än-      .NET und für JScript.NET. Da diese En-        seits eine IVsaSite-Schnittstelle implemen-
                   dern zu müssen. Zunächst sollte man ent-      gines abgesehen von der jeweiligen Spra-      tieren:

                                                                                                                                                 Nr.:01/2004
Scripting .NET Customizing Lösungen für .NET-Anwendungen mit JScript
Scripting in .NET                                                 Core.NET              33

public interface Microsoft.Vsa.IVsaSite                        2) schon mitgeliefert wird und nur noch
{                                                              gestartet werden muss, ändern sich Funk-
void GetCompiledState(out Byte[] pe, out Byte[] debugInfo);
                                                               tion und Aussehen der Preisberechnung
object GetEventSourceInstance(string itemName, string
                                                               (Abb. 3) ein wenig.
                                           eventSourceName);
object GetGlobalInstance(string name);
                                                                   Um Ihr Produkt mit solchen Möglich-
void Notify(string notify, object info);                       keiten auszustatten, müssen Sie Ihre An-
bool OnCompilerError(Microsoft.Vsa.IVsaError error);           wendung zu einem Scripting Host ma-
}                                                              chen, der die Scripting Engine erzeugt und
                                                               mit dieser zusammenarbeitet. Es ist sinn-
und diese dem Skriptmodul über die Ei-                         voll, hierfür eine eigene Klasse zu pro-
genschaft IVsaEngine.Site bekannt ma-                          grammieren. Bei unserem Beispielpro-
chen. Damit stehen Host und Skriptmo-                          gramm geschieht dies mit der Klasse Script-
                                                                                                                      Abb. 1: Beispielanwendung vor Starten des
dul in einer Wechselbeziehung, die, ver-                       Host in der Datei ScriptHost.cs (siehe Lis-            Skripts
einfacht gesagt, so aussieht: Der Host ver-                    ting 2, das sich im Begleitquellcode auf der
sorgt das Skriptmodul mit dem Quellcode                        beiliegenden CD findet). Im Konstruktor
und meldet eigene Klassen als globale Ele-                     des ScriptHost-Objekts wird mit                          wendung undeklarierter Variablen, zu-
mente an, auf die das Skriptprogramm zu-                                                                                gunsten einer besseren Geschwindig-
                                                               meineEngine = new Microsoft.JScript.Vsa.VsaEngine();
greifen darf. Der Host kann gezielt Funk-                                                                               keit zu verzichten. Die Option autoRef
tionen des von ihm bereitgestellten Skripts                                                                             erlaubt es der Engine, die im Skript
aufrufen. Umgekehrt ruft das Skriptmo-                         eine Instanz der Scripting Engine für                    mit der import-Direktive angegebenen
dul Methoden des Hosts auf, um Fehler                          JScript erzeugt, die die Schnittstelle IVsa-             Assemblies automatisch einzubinden.
beim Kompilieren oder bei der Skript-                          Engine unterstützt. Ein Zeiger auf dieses                Nach dieser Option habe ich lange su-
Ausführung anzuzeigen, um Instanzen der                        Interface, über das alle weiteren Zugriffe               chen müssen, weil sie sehr schlecht do-
vorher angemeldeten globalen Elemente                          auf die Engine geschehen, landet in der                  kumentiert ist.
zu erfragen oder um Statusinformationen                        privaten Variable meineEngine. Die wei-                • Setzen der Eigenschaft GenerateDebug-
abzurufen.                                                     teren Initialisierungsschnitte sind:                     Info auf true. Dies ist in der .NET Frame-

Vom Ziel aus wandern                                           • Zuweisen eines Strings auf die Eigen-
                                                                                                                      Listing 1
Alles ziemlich theoretisch? Finde ich auch.                      schaft RootMoniker. Dies sollte immer
                                                                                                                       public interface Microsoft.Vsa.IVsaEngine
Damit Sie erst einmal eine Vorstellung von                       die erste Aktion nach dem Erzeugen der                {
dem Ziel erhalten, das ich verfolge, wenn                        Engine sein. Diese Eigenschaft dient zur                Assembly Assembly { get; }
ich gleich die einzelnen Schritte zur Imple-                     eindeutigen Identifikation der Instanz                  Evidence Evidence { get; set; }
mentierung einer Scripting-Anwendung                             des Skriptmoduls. Der String muss dem                   bool GenerateDebugInfo { get; set; }
darstelle, möchte ich mir ausnahmsweise                          Format protokoll://pfad entsprechen,                    bool IsCompiled { get; }
                                                                                                                         bool IsDirty { get; }
selbst vorgreifen und Ihnen gleich zu An-                        also die Syntax eines URI aufweisen.
                                                                                                                         bool IsRunning { get; }
fang die fertige Beispielanwendung vor-                          Das etwas Schrullige daran ist, dass Sie                IVsaItems Items { get; }
stellen. Da sich aus Platzgründen nicht der                      sowohl Protokoll als auch Pfad frei er-                 string Language { get; }
ganze Quellcode abdrucken lässt, emp-                            finden können (und sollen), solange es                  int LCID { get; set; }
fehle ich Ihnen, diesen auf der beiliegen-                       nur eindeutig ist und nicht gegen die                   string Name { get; set; }
den CD einzusehen und damit selbst zu ex-                        Syntaxregeln verstößt.                                  string RootMoniker { get; set; }
                                                                                                                         string RootNamespace { get; set; }
perimentieren.                                                 • Zuweisen der IVsaSite-Schnittstelle zu
                                                                                                                         IVsaSite Site { get; set; }
    Abbildung 1 zeigt das Hauptfens-                             der Site-Eigenschaft der Engine. Diese                  string Version { get; }
ter dieser Mini-Anwendung. Als Beispiel                          Schnittstelle wird die Engine nutzen, um
dient hier eine ganz einfache Preisberech-                       den Host aufzurufen, um zum Beispiel                      void Close();
                                                                                                                           bool Compile();
nung. Der Anwender soll eine Menge, ei-                          Kompilierfehler anzuzeigen. In unserem
                                                                                                                           object GetOption(string name);
nen Stückpreis, Versandkosten und einen                          Beispiel wird self zugewiesen, weil das                   void InitNew();
Rabattsatz eingeben. Heraus kommt ein                            ScriptHost-Objekt die IVsaSite-Schnitt-                   bool IsValidIdentifier(string identifier);
daraus berechneter Gesamtpreis. Diese                            stelle implementiert.                                     void LoadSourceState(Microsoft.Vsa.IVsaPersistSite
Aufgabe ist natürlich trivial, aber gerade                     • Aufruf der Methode InitNew(). Dies soll                                                                    site);
bei solchen Preisberechnungen gibt es in                         immer nach der Zuweisung von Root-                        void Reset();
                                                                                                                           void RevokeCache();
der Praxis oft Anpassungsbedarf. So könn-                        Moniker und Site geschehen.
                                                                                                                           void Run();
te man zum Beispiel auf die Idee kommen,                       • Setzen von Optionen mit der Methode                       void SaveCompiledState(out Byte[] pe, out Byte[] pdb);
dass der Rabatt zwar auf den Wert der Wa-                        SetOption(). In meinem Beispiel wer-                      void SaveSourceState(Microsoft.Vsa.IVsaPersistSite
re, aber nicht auf die Versandkosten ge-                         den die Fast- und die autoRef-Optio-                                                                       site);
währt werden soll. Nach dem Aktivieren                           nen auf true gesetzt. Fast weist das                      void SetOption(string name, object value);
                                                                                                                       }
des JScript-Programms, das bei diesem                            JScript-Modul an, auf einige Features
Beispiel über das Scripting-Fenster (Abb.                        der Sprache, wie zum Beispiel die Ver-

Nr.:01/2004
Scripting .NET Customizing Lösungen für .NET-Anwendungen mit JScript
34      Core.NET                                                Scripting in .NET

                                                                                        Abb. 2: Einfacher       Eigenschaft des IVsaCodeItem zugewie-
                                                                                        Skripteditor
                                                                                                                sen wird. Ein globales Item ist eine be-
                                                                                                                nannte Referenz auf einen Objekttyp, den
                                                                                                                das Skript verwenden kann. Wenn das
                                                                                                                Skript davon Gebrauch macht, erkundigt
                                                                                                                sich die Scripting Engine durch einen Auf-
                                                                                                                ruf der Schnittstellenmethode

                                                                                                                object IVsaSite.GetGlobalInstance(string name)

                                                                                                                unter Angabe des Namens nach der zu
                                                                                                                verwendenden Instanz. Was heißt das al-
                                                                                                                les? Werfen Sie einen Blick auf die Code-
                                                                                                                zeilen, mit denen die Anwendung das
                                                                                                                ScriptHost-Objekt erzeugt. Dies geschieht,
       work-Dokumentation so beschrieben,                       gelieferten Schnittstellen hängt von dem        wenn Sie im Preiseingabeformular den
       als bräuchte man diese Einstellung nur                   Parameter itemType ab, dem einer der            Menüeintrag SCRIPT ENGINE INITIALISIE-
       für Debug-Zwecke. Es hat sich aber her-                  drei Aufzählungswerte VsaItemType.App-          REN anklicken:
       ausgestellt, dass ohne die Debug-Infor-                  Global, VsaItemType.Code oder VsaItem-
       mationen kein Aufruf von Methoden                        Type.Reference übergeben werden kann.           private void miScriptEngineInit_Click(object sender, System.

       des Hosts durch das Skript möglich ist.                  Ein Aufruf mit der Typangabe VsaItem-                                                           EventArgs e)
                                                                                                                {
     • Festlegen der Name- und der Root-Name-                   Type.AppGlobal erzeugt ein Objekt, das
                                                                                                                myScriptHost = new ScriptHost();
       space-Eigenschaften. Hier müssen gülti-                  die Schnittstelle IVsaGlobalItem unter-
                                                                                                                myScriptHost.LerneGlobalesObjekt(this, this.Name);
       ge Bezeichner eingetragen werden. Name                   stützt. Bei VsaItemType.Code wird eine
                                                                                                                /*...*/
       kann beliebig gewählt werden und muss                    IVsaCodeItem-Schnittstelle geliefert. In        }
       nur innerhalb der Hostanwendung ein-                     unserem Beispiel kommen diese beiden
       deutig sein, falls mehrere Scripting En-                 Item-Typen vor, aber kein VsaItemType               Sie sehen hier, dass – nach dem Erzeu-
       gines erzeugt werden (was selten vor-                    .Reference (liefert IVsaReferenceItem), mit     gen des Script Hosts – die Anwendung
       kommen dürfte). RootNamespace legt                       dem es möglich ist, Verweise auf Assemb-        durch Aufruf von LerneGlobalesObjekt
       die Bezeichnung des Stamm-Namens-                        lies, die das Skriptmodul laden soll, zu spe-   (this, this.Name) das Form-Objekt (this)
       raums des Skripts fest. Auch diese Ein-                  zifizieren. Auf diese recht umständliche        unter dem in der Name-Eigenschaft des
       stellung können sie willkürlich festlegen.               Art der Referenzierung können wir dank          Formulars angegebenen Namen (näm-
                                                                der oben erwähnten Option autoRef ver-          lich frmPreisberechnung) als globales Ob-
         Nachdem die Scripting Engine auf die-                  zichten, solange sich die vom Skript ver-       jekt anmeldet. Der Name wird innerhalb
     se Weise initialisiert ist, muss sie gefüttert             wendeten Assemblies im GAC befinden.            von LerneGlobalesObejekt() als name-
     werden, damit sie etwas Nützliches tun                     Der itemFlag-Parameter von CreateItem()         Parameter an CreateItem() übergeben und
     kann. Das Futter kommt in Form von                         hat für die JScript-Engine keine Bedeu-         der Typ des Form-Objekts wird der Type-
     Einträgen in die Items-Aufzählungseigen-                   tung und kann immer mit VsaItemFlag.            String-Eigenschaft des dabei erzeugten
     schaft, die von der IVsaEngine-Schnitt-                    None angegeben werden.                          IVsaGlobalItem zugewiesen. Damit kennt
     stelle angeboten wird. Über solche Items                       In meinem Beispiel wird ein globales        die Engine den Typ, nicht aber die Ob-
     werden Quellcode-Elemente und globale                      Item von dem ScriptHost-Objekt bei Auf-         jektinstanz. Das ScriptHost-Objekt merkt
     Objekte an die Engine übergeben. Erzeugt                   ruf der Methode LerneGlobalesObjekt(),          sich daher die Verbindung zwischen Na-
     werden die Einträge mit der Methode Cre-                   ein Code Item von der Methode ScriptEin-        men und Objektinstanz in der Sorted List
     ateItem, die vom Items-Objekt angeboten                    tragen() erzeugt (siehe nochmals Listing 2      globalObjList und sucht diese anhand
     wird und folgende Signatur hat:                            auf der CD). In beiden Fällen wird mei-         des Namens wieder heraus, wenn die En-
                                                                neEngine.Items.CreateItem() aufgerufen,         gine die IVsaSite.GetGlobalInstance()-
     IVsaItem CreateItem(string name, VsaItemType itemType,
                                                                aber es wird jeweils ein anderer itemType       Methode aufruft. Durch diese Vermitt-
                                       VsaItemFlag itemFlag);
                                                                übergeben. Das Ergebnis wird entspre-           lung wird es dem Skript ermöglicht, auf
                                                                chend im ersten Fall als IVsaGlobalItem,        bestimmte Objekte der Host-Anwen-
         Als Funktionsergebnis erhält der Auf-                  im zweiten Fall als IVsaCodeItem inter-         dung zuzugreifen.
     rufer also eine Referenz auf eine IVsa-                    pretiert. Über die zurückgegebene Schnitt-
     Item-Schnittstelle. Dies ist aber nur ein                  stelle werden dann die Eigenschaften des        Startklar machen
     Basistyp, von dem drei verschiedene spe-                   Items belegt.                                   Der Quellcode wird, wie gesagt, von Script-
     ziellere Schnittstellen abgeleitet sind, so-                   Was ein Code Item sein soll, ist nicht      Host.ScriptEintragen() als Code Item an-
     dass das Ergebnis entsprechend gecastet                    schwer zu erraten, zumal Sie in der Script-     gemeldet. Sie sehen hier, dass beim Auf-
     werden kann. Der Typ des Items und da-                     Eintragen()-Methode sehen, dass der string-     ruf von CreateItem() der vorher als Root-
     mit auch der tatsächliche Typ der zurück-                  Parameter strQuellcode der SourceText-          Namespace festgelegte String als Name

                                                                                                                                                                 Nr.:01/2004
Scripting .NET Customizing Lösungen für .NET-Anwendungen mit JScript
Scripting in .NET                                                 Core.NET        35

für das Item übergeben wird. Das ist eine       Schaltfläche BERECHNEN im Hauptformu-
völlig willkürliche Entscheidung meiner-        lar kommt folgender Code zur Ausfüh-
seits, denn Sie dürfen einen beliebigen         rung:
Namen verwenden, solange dieser für alle
                                                //...
Code Items eindeutig ist. Da mein Scrip-
                                                if ((myScriptHost != null) && myScriptHost.IsRunning)
ting Host nur ein einziges Code Item            {
unterstützt (obwohl mehrere möglich wä-          object[] par = {menge, einzelpreis, versandkosten, rabatt};
ren), brauche ich mir über die Eindeutig-        script_result = myScriptHost.ScriptMethodeAufrufen
keit natürlich keine Sorgen zu machen.                                                  (“PreisBerechnen“, par);
                                                 if (script_result is double) preis = (double)script_result;
Meine Beispielanwendung trägt den Skript-                                                                             Abb. 3: Nach dem Starten des Skripts
                                                                                         else script_result = null;
code ein, sobald Sie in dem Scripting-          }
Fenster (Abb. 2) die entsprechende Schalt-      if (script_result == null)
fläche anklicken. Das kann mehrmals              preis = (menge * einzelpreis + versandkosten) *                      lich der Aufruf der Methode mit Invoke()
hintereinander geschehen, denn in Script-                                                       (1 - rabatt / 100);   unterscheiden sich nicht von der Technik,
Host.ScriptEintragen() wird das vorheri-                                                                              die Sie verwenden könnten, um eine mit C#
ge Code Item vor dem Eintragen des neu-             Wenn also myScriptHost initialisiert                              oder VB.NET programmierte Assembly zu
en gegebenenfalls verworfen. Anschlie-          und das Skriptprogramm mit Run() gestar-                              verwenden.
ßend wird meineEngine.Compile() auf-            tet worden ist, wird mit diesen Codezeilen                                Die Anwendung versucht also, eine
gerufen und die Scripting Engine wandelt        die Methode myScriptHost.ScriptMetho-                                 Scripting-Methode für die Preisberech-
den Quellcode in IL-Code um. Falls es           deAufrufen() aufgerufen, wobei der Me-                                nung zu aktivieren. Wenn dies gelingt,
beim Kompilieren zu Fehlern kommt, ruft         thodenname und ein Array mit den Aufruf-                              wird das Berechnungsergebnis des Skripts
die Engine die IVsaSite.OnCompilerEr-           parametern (die auf die Skriptimplemen-                               verwendet, anderenfalls wird die fest pro-
ror()-Methode auf, um dem Host Gele-            tierung abgestimmt sein müssen) mit ange-                             grammierte Formel angewendet, die even-
genheit zu geben, den Fehler anzuzeigen.        geben werden. Sehen Sie sich die Imple-                               tuell einen etwas niedrigeren Preis liefert.
Das geschieht in meinem Beispiel indi-          mentierung von ScriptMethodeAufrufen()                                Bei dieser (klassischen) Form der Skript-
rekt, indem der LogCompileError-Dele-           an. Die Methode verwendet die Eigen-                                  anwendung liegt die Initiative beim Host-
gate aufgerufen wird, der im Scripting-         schaft Assembly der Engine-Schnittstelle,                             programm. Darin müssen die Möglich-
Fenster die Fehlermeldung in die lbLog-         die eine Instanz des im Namespace Sys-                                keit des Eingriffs mittels Scripting und die
List Box einträgt. Bei fehlerfreier Kom-        tem.Reflection definierten Assembly-Ob-                               Randbedingungen (welche Parameter von
pilierung liefert meineEngine.Compile()         jekts liefert. Diese Assembly ist das kompi-                          welchem Typ gibt es usw.) schon zum
true zurück und die IsCompiled-Eigen-           lierte Skript – ein weiterer Hinweis darauf,                          Zeitpunkt der Anwendungsprogrammie-
schaft zeigt ebenfalls true an. Die Ampel       wie tief das VSA-Scripting in die .NET-Fun-                           rung genau geplant worden sein, damit –
steht also auf Grün. Wir müssen nur noch        damente eingelassen ist. Der Abruf von                                wie in unserem Beispiel bei Klicken auf
losfahren, was durch den Aufruf der Me-         Typinformationen für die „Scriptklasse“                               den BERECHNEN-Button – das Skript in
thode meineEngine.Run() geschieht, so-          (die natürlich im Skriptquellcode genau so                            Aktion treten kann. Etwas eleganter, aber
bald die Schaltfläche SCRIPT STARTEN be-        heißen muss) mit Assembly.GetType(), der                              im Ansatz nicht wesentlich anders wäre
tätigt wird.                                    MethodInfo mit GetMethod() und schließ-                               es gewesen, für die Preisberechnung ein
    Haben Sie auch schon einen Blick auf
den Quelltext des Skripts in Listing 3 (eben-
falls im Begleitcode auf der CD) geworfen?
Für diejenigen, die mit JScript.NET nicht
vertraut sind, empfehle ich die Lektüre der
Einführung in diese Sprache in der SDK-
Dokumentation [2]. Das Skript tritt erst
dann in Aktion, wenn der Host eine Funk-
tion darin aufruft. Meines Wissens ist dies
nur durch Aufruf einer Objektmethode
möglich. Da der Host keine Instanzen der                                                          Anzeige
im Skript programmierten Klassen besitzt,
bietet sich eine statische Objektmethode
als Einsprungspunkt für den Host an, da
für so einen Aufruf keine Instanz erforder-
lich ist.
    Wie Sie sehen, definiert das Beispiel-
skript eine Klasse namens Scriptklasse,
deren erste Funktion die statische Metho-
de PreisBerechnen ist. Beim Betätigen der

Nr.:01/2004
Scripting .NET Customizing Lösungen für .NET-Anwendungen mit JScript
36       Core.NET                                                 Scripting in .NET

                                                                                        Abb. 4: Skript-Debugging   an Größe und Position des schon vorhan-
                                                                                                                   denen Buttons btnBerechnen, um das Lay-
                                                                                                                   out des Formulars nicht zu stören. Die un-
                                                                                                                   gewöhnliche Farbe habe ich diesem Button
                                                                                                                   nur deshalb zugewiesen, damit er in den
                                                                                                                   Abbildungen gleich auffällt. Das Click-
                                                                                                                   Event des neuen Buttons wird ebenfalls von
                                                                                                                   einer Skript-Methode behandelt. Übrigens
                                                                                                                   können statische Methoden nicht als Event
                                                                                                                   Handler dienen.

                                                                                                                   Geister, die ich rief
                                                                                                                   Nachdem nun alles so schön funktioniert,
                                                                                                                   sollten wir auch darüber nachdenken,
                                                                                                                   wie man ein Skript wieder anhält und
                                                                                                                   eventuell gegen eine neue Version aus-
                                                                                                                   tauscht, weil man zum Beispiel den Quell-
     Event zu definieren und dem Skript die                       das defaultmäßige private) gesetzt ist, da-      text geändert hat und die neue Version
     Gelegenheit zu geben, auf dieses Event zu                    mit das Feld von außen auch sichtbar ist.        ausprobieren möchte. Leider ist das nicht
     reagieren. Die von mir gerade angedeute-                     Und drittens ist hier die JScript-spezifische    ganz so einfach, wie man vermuten sollte.
     te Fähigkeit des Skripts, Ereignisse des                     Syntax zu sehen, mit der das Skript sich für     Die IVsaEngine-Schnittstelle bietet zwar
     Hostprogramms zu verarbeiten, eröffnet                       ein Event, nämlich das Leave-Event der           eine Methode namens Reset() an, nach
     aber in anderer Hinsicht sehr interessante                   Text Box, eintragen kann. Es reicht näm-         deren Aufruf (durch Zuweisung von false
     Möglichkeiten.                                               lich aus, dem Event-Namen die Einleitung         an die IsRunning-Eigenschaft des Script-
         Ganz am Ende des JScripts sehen Sie                      add_ voranzustellen und in Klammen die           Host-Objekts) das Code Item ersetzt und
     folgenden Einzeiler:                                         Methode des Skriptobjekts anzugeben,             erneut kompiliert und gestartet werden
                                                                  die das Event behandeln soll. In diesem          kann, aber die Sache hat leider einen oder
     new Scriptklasse();
                                                                  Fall ist dies die Methode VerlassePreisein-      sogar zwei Haken.
                                                                  gabe, deren Signatur (also Parameter und             Erstens führt das fortgesetzte Kompi-
     Diese Programmzeile ist nicht in eine                        Rückgabewert) natürlich mit der Defini-          lieren, Starten, Stoppen, erneut Kompilie-
     Funktion oder eine Methode eingefasst,                       tion des Events übereinstimmen muss.             ren usw. jedes Mal zum Erzeugen einer
     sondern steht völlig für sich allein. Solche                                                                  neuen Assembly, die auch dann im Spei-
     Anweisungen werden von der JScript-En-                       Schmuggelware                                    cher geladen bleibt, wenn sie nicht mehr
     gine unmittelbar beim Aufruf von Run()                       Das Leave-Event wird jedes Mal ausge-            gebraucht wird. Dies ist nicht anders, als
     ausgeführt. Zweck der Übung ist, eine Ins-                   löst, wenn der Focus die Text Box verlässt.      wenn Sie in Ihrem Anwendungsprogramm
     tanz der Scriptklasse zu erstellen, wobei                    Der sender-Parameter von VerlassePreis-          selbst Assembly.Load() aufrufen, denn
     der Konstruktor function Scriptklasse()                      eingabe wird einfach als Referenz auf eine       auch da haben Sie es schwer, dieses Code-
     ausgeführt wird. Die erste Zeile des Kons-                   Text Box aufgefasst und der Inhalt der           Objekt wieder loszuwerden. Es gibt näm-
     truktors lautet:                                             Eingabe wird aus der Text-Eigenschaft            lich keine Unload-Technik für Assemb-
                                                                  ausgelesen. Wenn der Benutzer den Zu-            lies. Gleiches gilt übrigens für Biblio-
     frmPreisberechnung.txtEinzelpreis.add_Leave(this.
                                                                  satz DM oder DEM eingetippt hat, wird            theken bei Win32 (sprich DLLs). Die ein-
                                         VerlassePreiseingabe);
                                                                  eine Umrechnung in Euro vorgenommen              zige Möglichkeit, eine Assembly zuver-
                                                                  und der Euro-Wert wird wieder in die             lässig wieder loszuwerden, ist das Entla-
         Darin liegen gleich drei interessante                    Text-Eigenschaft geschrieben. Auf diese          den der AppDomain, in deren Kontext sie
     Erkenntnisse verborgen: Erstens ist dies                     Weise schmuggelt das Skript eine neue            geladen wurde [3]. In unserem Beispiel
     die erste Stelle, an der auf das globale                     Funktion in die Anwendung. Und im Ge-            gibt es für die ganze Anwendung aber nur
     Objekt frmPreisberechnung zugegriffen                        gensatz zu dem vorherigen Beispiel (Auf-         eine AppDomain und es wäre auch ziem-
     wird, und zwar über genau den Bezeich-                       ruf von PreisBerechnen) musste dies nicht        lich kompliziert, es anders zu machen.
     ner, der beim Erzeugen des entsprechen-                      bereits bei der Programmierung der An-           Daher müssen wir leider in Kauf nehmen,
     den globalen Items als Name angegeben                        wendung vorbereitet werden.                      dass mit jeder neuen Skript-Assembly der
     worden ist. Zweitens ist hier zu sehen,                          Noch augenfälliger ist dies bei dem          Speicher zusätzlich belastet wird, was na-
     dass das Skript ohne weiteres (durch späte                   nächsten Eingriff, den der Konstruktor der       türlich nicht endlos möglich ist.
     Bindung) auch auf Objekte wie das txt-                       ScriptKlasse vornimmt. Beim Starten des              Der zweite Haken ist noch tückischer.
     Einzelpreis-Feld (vom Typ TextBox) zu-                       Skripts wird nämlich auch ein neuer Button       Die Reset()-Methode der Engine löst nicht
     greifen kann. Voraussetzung dafür ist al-                    erzeugt und der Controls-Auflistung des          die manuell (z.B. mit add_Leave) hinzuge-
     lerdings, dass die Eigenschaft Modifiers                     Formulars hinzugefügt. Die Positionierung        fügten Event Handler und sie gibt natürlich
     für dieses Objekt auf public (und nicht auf                  und Größe des Buttons orientiert sich dabei      auch nicht den vom Skript erzeugten But-

                                                                                                                                                     Nr.:01/2004
Scripting in .NET

ton wieder frei. Tatsächlich werden die         führen. Alternativ können Sie den kom-
Event Handler weiterhin aufgerufen, auch        pilierten Zustand des Skripts speichern
dann, wenn bereits ein neues Skript geladen     und später wieder erneut laden. Um Ihnen
ist und läuft. Um mein Beispielprogramm         diese Möglichkeit zu illustrieren, bietet
möglichst einfach zu halten, habe ich hier      das Scripting-Fenster meines Beispiel-pro-
ganz auf die Möglichkeit, mehrere Versio-       gramms eine Schaltfläche an, deren Click-
nen des Skripts während einer Programm-         Handler die ScriptHost-Methode Kompi-
sitzung zu starten, verzichtet. Wenn Sie die-   liertesScriptSpeichern() aufruft. Darin wer-
se Option aber doch schaffen wollen, dann       den mit IVsaEngine.SaveCompiledState()
können Sie vor dem IVsaEngine.Reset()-          der Binärcode und die Debug-Daten des
Aufruf eine Aufräummethode in Ihrem             bereits kompilierten (aber noch nicht ge-
Skript aktivieren (oder vielleicht ein OnBe-    starteten) Skripts in Byte-Arrays übertra-
foreReset-Event anbieten), um dem Skript        gen und dann in eine Datei mit der willkür-
vor dem Ableben die Gelegenheit zu geben,       lich gewählten Endung .bin gespeichert.
seinen Nachlass zu ordnen. Um Event             Die Methode StartAusBinFileVersuchen(),
Handler zu lösen, verwenden Sie in JScript      die nach dem Initialisieren des ScriptHost
einfach das Präfix remove_ (statt add_) vor     aufgerufen werden kann, überprüft, ob die
dem Eventnamen.                                 .bin-Datei vorhanden ist und versucht ge-
                                                gebenenfalls (ohne vorheriges Laden eines
Entwanzen
Nach den schlechten Nachrichten über die
                                                Code Items) die Engine mit IVsaEngine.
                                                Run() zu startet. Ein Starten ohne gelade-
                                                                                               Anzeige
Schwierigkeiten mit dem Entladen eines          nes Programm nimmt die Engine zum An-
Skripts, nun noch eine gute: Skriptpro-         lass, dem Host durch Aufruf von IVsa-
gramme lassen sich hervorrangen debug-          Site.GetCompilesState() um die Binärda-
gen. Alles was Sie tun müssen, ist beim Er-     ten zu bitten, die eben dort wieder aus der
zeugen der Engine mit IVsaEngine.Set-           Datei gelesen und an die Engine übergeben
Option() die Option DebugDirectory zu           werden. Es ist also möglich, ein kompilier-
setzen und einen Pfad anzugeben, in dem         tes Skript laufen zu lassen, ohne dass der
die Skript-Quellcodes für das Debugging         Quellcode vorhanden ist. Natürlich bleibt
zwischengespeichert werden können. Im           es letztlich Ihnen überlassen, ob Sie Quell-
Konstruktor des ScriptHost-Objekts habe         code oder Binärdaten speichern und ob Sie
ich eine entsprechende Anweisung schon          dies in einer Datei oder vielleicht in einer
als Kommentar vorgesehen. Natürlich             Datenbank tun.
könnte auch das Anwendungsprogramm
zur Laufzeit aufgrund einer Einstellung ent-    Fazit
scheiden, ob das Verzeichnis gesetzt werden     Die Einbettung von VSA in die CLR und
soll. Wird hier ein gültiges Verzeichnis ein-   das .NET Framework ermöglichen ein bar-
gestellt, so erzeugt die Engine dort beim       rierefreies Zusammenwirken von Host-
Kompilieren eine Quellcodedatei.                programm und Skript, woraus sich sehr an-
    Sie können sich mit einem geeigneten        spruchsvolle Möglichkeiten für ein Custo-
Debugger (Visual Studio .NET oder Dbg-          mizing von Anwendungssoftware ergeben.
CLR.exe aus dem Framework SDK) mit                  Beim Schreiben dieses Artikels hatte ich
dem Prozess verbinden bzw. die Anwen-           das Szenario vor Augen, bei dem der Ent-
dung unter der Kontrolle des Debuggers          wickler selbst auch die Programmierung
starten. Beim Kompilieren des Skripts wird      des Skripts übernimmt. Überlegungen in
dann in dem festgelegten Verzeichnis eine       Richtung Sicherheit, etwa zur Vermeidung
Datei mit der Endung .js (für JScript) er-      von böswilligen Eingriffen in den Pro-
zeugt. Diese können Sie mit dem Debugger        grammablauf, spielten hier praktisch keine
öffnen und beliebige Haltepunkte darin          Rolle.
setzen, was sogar auch dann funktioniert,
wenn das Anwendungsprogramm ohne
Debug-Informationen umgewandelt wor-               Links & Literatur
den ist. Abbildung 4 zeigt DbgCLR im Ein-       [1] vsa.summitsoft.com/
satz.                                           [2] .NET Framework SDK Dokumentation,
    Es steht Ihnen übrigens frei, nach je-          „JScript .NET“
dem Programmstart den Quellcode des             [3] Jochen Reinelt, „Hin und weg“,
Skripts zu laden, kompilieren und auszu-            dot.net magazin 4.2003

Nr.:01/2004
Sie können auch lesen