Scripting .NET Customizing Lösungen für .NET-Anwendungen mit JScript
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
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 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
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 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
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