.. highlightlang:: us .. index:: Funktionen .. _funktionen: Funktionen ========== Funktionen sind ein Mittel, die Übersicht bei größeren Programmen nicht zu verlieren. Eine Funktion ist eine Folge von Anweisungen, denen ein Name gegeben wird. Sie kann als Black-Box verwendet werden, ohne ihre Funktionsweise genau zu kennen. Um die Sinus-Funktion anzuwenden, benötigen Sie nicht das Wissen über ihre interne Arbeitsweise, Sie brauchen nur ihren Namen und ihre Schnittstelle zu kennen: Sie brauchen nur zu wissen, **was** die Funktion macht und nicht **wie** sie es macht. .. index:: def .. _def: .. _syntax-von-funktionen: Syntax von Funktionen --------------------- In UniScript kann eine Funktion sehr einfach definiert werden :: def dB(rvSignal) { return 20.0 .* log10(rvSignal); } Hinter dem Schlüsselwort :keyword:`def` steht der Name der Funktion, gefolgt von einer in Klammern eingeschlossenen Parameterliste, die hier nur einem Parameter enthält. Es folgt der sogenannte Funktionsrumpf mit den Anweisungen. Die letzte Anweisung gibt das Resultat der Funktion an den Aufrufer zurück. Die Funktion kann mit dem Aufruf :: rvdB = dB(1:100); ausgeführt werden, wodurch die Zahlen 1 bis 100 in ``dB`` umgerechnet werden. Formal sieht eine Funktionsdefinition folgendermaßen aus:: def name (parameter-liste) { anweisungen } .. index:: C, FORTRAN, C++, SYM_BUILDIN .. index:: symbols, loadlib Für die Namen von Funktionen gelten die gleichen Regeln wie für Variablen-Namen. Er muss mit einem Buchstaben oder Unterstrich anfangen, gefolgt von beliebig vielen Buchstaben, Unterstrichen und Ziffern. Der Name darf nicht identisch mit einem reservierten Namen sein. Der Name darf außerdem nicht identisch mit einer eingebauten Funktion sein. Eine eingebaute Funktion (built-in-function) ist eine in C, C++ oder FORTRAN geschriebene Funktion, die beim Start von UniScript mit der Funktion :ref:`loadlib` geladen wird. Diese Funktionen befinden sich in sogenannten Dynamic Link Libraries (DLLs). Eine Liste der Funktionen können Sie mit dem Aufruf ``symbols(SYM_BUILTIN)`` erhalten. Hinter dem Namen steht eine in Klammern eingeschlossene Parameterliste, die zwischen 0 und 32 durch Komma getrennte Parameter haben kann. Die kürzeste in UniScript mögliche Funktion ist :: def f() { } Die Klammern für die Parameterliste und die Klammern für den Funktionsrumpf sind syntaktisch erforderlich. Wird diese Funktion aufgerufen, gibt sie eine 0 zurück. .. index:: Parameter, Argumente .. _parameter-und-argumente: Parameter und Argumente ----------------------- Als Argumente bezeichnet man die Werte, mit denen eine Funktion aufgerufen wird. Parameter sind die Namen, die in der aufgerufenen Funktion für die Argumente stehen. Eine Funktion kann bis zu 32 Parameter haben. .. index:: dB .. index:: log10 **Beispiel**: :: def dB(rvSignal) { return 20.0 .* log10(rvSignal); } db(1:100) Der Wert ``1:100`` ist das Argument, ``rvSignal`` ist der Parameter der Funktion. Eine Funktion kann mit weniger Argumenten aufgerufen werden als die Funktion Parameter hat. **Beispiel**: :: def Test(a, b, c) { return 1; } Test(); Test(1); Test(1,2); Test(1,2,3); Test(1,2,3,4); Alle Aufrufe sind zulässig bis auf den letzten. Eine Funktion darf nicht mit mehr Argumenten aufgerufen werden, als Parameter in der Parameterliste stehen. Die Anzahl der Argumente kann innerhalb einer Funktion mit der Funktion :ref:`nargsin` festgestellt werden. :: def Test(a, b, c, d, e, f, g) { nArgs = nargsin(); printf("Die Funktion wurde mit %d von 7 " + .. "möglichen Argumenten aufgerufen\n", nArgs); } Es darf nicht auf Parameter zugegriffen werden, für die keine Argumente existieren. Wurde ``Test()`` mit ``Test(1, 2, 3)`` aufgerufen, dürfen die Parameter ``d`` - ``e`` in der Funktion nicht verwendet werden. .. index:: global, lokale variablen .. _global: .. _globale-und-lokale-variablen: Globale und lokale Variablen ---------------------------- Normalerweise sind alle innerhalb einer Funktion definierten Variablen lokal zur Funktion. Sie sind außerhalb der Funktion nicht bekannt. Wenn die Funktion aufgerufen (aktiviert) wird, werden die Variablen erzeugt und wenn die Funktion beendet wird, werden die Variablen aus dem Speicher entfernt. Mit der :keyword:`global`-Deklaration können Variablen definiert werden, die die Funktion überdauern. Außerdem können andere Funktionen oder Anweisungen außerhalb von Funktionen diese Variablen ändern. **Beispiel**: :: def Test() { global Glob1, Glob2; Glob1 = 1; Glob2 = 2; Lokal1 = 3; Lokal2 = 4; print Glob1, Glob2, Lokal1, Lokal2; } Glob1 = 5; Glob2 = 6; Lokal1 = 7; Lokal2 = 8; print Global1, Global2, Lokal1, Lokal2; Test(); print Global1, Global2, Lokal1, Lokal2; Führt man dieses Programm aus, druckt es zunächst die Zahlen 5, 6, 7, 8. In der Funktion ``Test()`` gibt es die Zahlen 1, 2, 3, 4 aus und in der letzten :keyword:`print`-Anweisung die Zahlen 1, 2, 7, 8. Die Funktion ``Test()`` hat die Variablen ``Glob1`` und ``Glob2`` verändert, nicht aber die außerhalb der Funktion definierten Variablen ``Lokal1`` und ``Lokal2``. Globale Variablen sollten sparsam verwendet werden, da es leicht vorkommen kann, daß eine solche Variable außerhalb einer Funktion versehentlich verändert wird. Außerdem schränken globale Variablen in Funktionen die Allgemeinverwendbarkeit der Funktionen ein. .. index:: return .. _die-return-anweisung: .. _return: Die return-Anweisung -------------------- Die :keyword:`return`-Anweisung hat den Zweck, Werte an den Aufrufer zurück zu geben und die Funktion zu beenden. Sie hat die Form :: return ausdruck; Wird ``ausdruck`` weggelassen, gibt die Funktion den Wert 0.0 zurück. Falls eine Funktion keine :keyword:`return`-Anweisung enthält, wird am Ende der Funktion eine Default-:keyword:`return`-Anweisung ausgeführt, die ebenfalls den Wert 0.0 zurückgibt. **Beispiel**: :: def Test(x) { if ((x % 2) == 0) { return "x ist ohne Rest durch 2 teilbar"; } return "x ist NICHT ohne Rest durch 2 teilbar"; } Test(4) Test(5) Falls das Argument von ``Test()`` durch 2 teilbar ist, wird die erste :keyword:`return`-Anweisung ausgeführt und die Funktion beendet, ansonsten wird die zweite :keyword:`return`-Anweisung ausgeführt. .. _funktionen-die-mehrere-werte-zuruckgeben: .. _multiple-return-values: Funktionen die mehrere Werte zurückgeben ---------------------------------------- Funktionen, die Sie bisher kennen gelernt haben, wie z. B. die Funktion :ref:`sin` geben **einen** Wert an den Aufrufer zurück. Es ist jedoch manchmal erforderlich oder praktisch, daß eine Funktion mehrere Werte an den Aufrufer zurückgibt. **Beispiel** Ruft man die Funktion :ref:`eig` in der Form :: * m = [4,7;2,5] * eig(m) 8.2749 + 0.0000i 0.7251 + 0.0000i liefert sie die Eigenwerte der Matrix ``m``. Ruft man die Funktion in der Form :: * = eig(m) auf, schreibt sie die Eigenwerte in die Variable ``e`` und die Eigenvektoren in die Variable ``v``. Definiert werden Funktionen dieser Art in der Form :: def = name(i1, i2) { o1 = ...; o2 = ...; } Die beiden Ausgabeparameter (hier ``o1`` und ``o2``) werden nicht über die :keyword:`return`-Anweisung zurückgegeben, sondern in der Funktion werden die Werte direkt in die Parameter geschrieben. Wie für die Eingabeparameter gilt auch hier, daß nicht auf die Parameter zugegriffen werden darf, wenn die Funktion nicht mit der entsprechenden Anzahl an Parametern aufgerufen wurde. Die Anzahl kann innerhalb der Funktion mit :ref:`nargsout` ermittelt werden. .. index:: nargsout, nargsin :: def = Test(e,f,g,h,i) { nArgsIn = nargsin(); nArgsOut = nargsout(); printf("Die Funktion wurde mit %d von 5 " + .. "möglichen Argumenten aufgerufen\n", nArgsIn); printf("Die Funktion wurde mit %d von 4 " + .. "möglichen Ausgabe-Variablen aufgerufen\n", .. nArgsOut); } Bei der Definition muss darauf geachtet werden, daß die Namen der Ausgabeparameter in den Winkeln ``<>`` nicht noch einmal in der Liste der Eingabeparameter verwendet werden dürfen. Die Rückgabe mehrerer Return-Werte wird selten verwendet, weil die Verwendung von Objekten (siehe :ref:`uniscript-objects`) praktischer ist. Beispiel:: def Test(p1, p2) { o = [.]; // Objekt erzeugen o.a = 123; o.b = "Hello"; return o; } .. index:: DLLs, C, FORTRAN, startup.ic .. index:: RegisterFunction .. _aufruf-von-funktionen-aus-dlls: Aufruf von Funktionen aus DLLs ------------------------------ DLL ist die Abkürzung für Dynamic Link Library. In DLLs befinden sich Funktionen, die mit einem Compiler erzeugt worden sind. Mit UniScript können solche Funktionen direkt aufgerufen werden. Es wird also kein C oder FORTRAN Compiler mehr benötigt, wie in den älteren Versionen der Skript-Sprache. Es gibt jedoch zwei Einschränkungen. Funktionen denen Strukturen oder Zeiger auf Funktionen übergeben werden, können nicht direkt aufgerufen werden. Für Funktionen dieser Art muss weiterhin eine Schnittstelle in C oder FORTRAN geschrieben werden. Wir wollen die Verwendung dieser neuen Schnittstelle an einigen Beispielen zeigen. .. index:: FindWindow **FindWindow** Das erste Beispiel ist die Funktion ``FindWindow()``. Die Funktion befindet sich in einer DLL die zum Windows-Betriebssystem gehört. Die Funktion kann dafür verwendet werden, zu prüfen, ob ein bestimmtes Programm bereits gestartet wurde. Bevor die Funktion aufgerufen werden kann, muss die Funktion aus der DLL bei UniScript registriert werden. UniScript muss wissen, in welcher DLL sich die Funktion befindet, muss den Namen der Funktion kennen und muss die Typen der Parameter und den Typ des Returnwertes kennen. Die Datentypen der Parameter und des Returnwertes werden benötigt, damit UniScript beim Aufruf der Funktion entsprechende Konvertierungen durchführen kann. .. index:: DLL, startup.ic Zur Registrierung bei UniScript dient die Funktion :ref:`RegisterFunction`. Sie können den Funktionsaufruf zum Beispiel in die Startup-Datei :file:`startup.ic` schreiben, damit die registrierte Funktion UniScript bei jedem Start bekannt ist. :: RegisterFunction ("user32","FindWindowA", .. "uint", "FindWindow", ["char*", "char*"]); Die Parameter haben folgende Bedeutung: * Der erste Parameter ist der Name der Dynamic Link Library (DLL). Die Funktion ``FindWindow()`` befindet sich in der DLL :file:`user32.dll` in einem der Windows-Verzeichnisse. In welcher DLL sich die Funktion befindet, kann man der Dokumentation des Windows Software Development Kits (SDK) entnehmen, die bei den Windows-Compilern mitgeliefert wird. * Der zweite Parameter ist der interne Name der Funktion. Die Funktion ``FindWindow()`` hat den internen Namen ``FindWindowA``. Dies wird ebenfalls der SDK-Dokumentation entnommen. * Der dritte Parameter ist ein String, der den Typ des Returnwerts beschreibt. Aus der SDK-Dokumentation kann man entnehmen, daß der Returnwert von ``FindWindow()`` ein ``HWND`` ist (ein Handle auf ein Fenster). Da :ref:`RegisterFunction` den Typ ``HWND`` nicht kennt, kann statt dessen ein ``"uint"`` verwendet werden. Ein ``"uint"`` ist eine vorzeichenlose ganze Zahl mit 32 Bits. Die möglichen Typen, die bei :ref:`RegisterFunction` angegeben werden können, entnehmen Sie bitte der UniScript-Referenz. * Der vierte Parameter ist der UniScript-Name, d. h. der Name mit dem die Funktion in UniScript aufgerufen werden soll. Sie können hier einen beliebigen Namen angeben, der den Regeln für Funktionsnamen entspricht. * Der fünfte Parameter ist ein Vektor mit Strings, der die Parameter der Funktion beschreibt. .. index:: OpusApp, Word, XLMAIN Die Funktion ``FindWindow`` hat zwei Parameter. Der erste ist ein Zeiger auf einen String, der den internen Fensternamen angibt, und der zweite Parameter ist ein Zeiger auf einen String, der die Fenster-Titel-Beschriftung angibt. Der interne Fenstername ist bei dem Programm MS-Excel z. B. ``"XLMAIN"``, bei MS-Word ``" OpusApp"`` und bei UniPlot ``":UniPlot:"``. Nachdem die Funktion registriert wurde, kann die Funktion aufgerufen werden. Die Funktion gibt einen Wert ungleich 0 zurück, wenn Excel gestartet wurde und sonst eine 0. :: // Finde des Fenster von Excel. Für den zweiten // Parameter kann eine 0 angegeben werden (siehe // SDK Dokumentation) h = FindWindow("XLMAIN", 0); if (h == 0) { // Fenster nicht gefunden, d.h. Excel ist // noch nicht gestartet. Dann starte Excel system("excel", SW_SHOW, TRUE); } // an dieser Stelle ist Excel gestartet .. index:: DGEMM **DGEMM** Das nächste Beispiel ist ein komplizierter Aufruf einer mathematischen Funktion, die sich in der mit UniScript mitgelieferten DLL :file:`rs_math.dll` befindet. Sie wertet die folgende Anweisung aus: :: C = alpha * A * B + beta * C ``alpha`` und ``beta`` sind skalare Größen, ``A``, ``B`` und ``C`` sind zueinander passende Vektoren oder Matrizen. Die Funktion dient hier nur als Beispiel, wie FORTRAN-Funktionen von UniScript aufgerufen werden, denn die Anweisung :: C = alpha * A * B + beta * C kann direkt so in UniScript ausgeführt werden. Ein Funktionsaufruf ist also eigentlich gar nicht erforderlich. Das Beispiel hat den Zweck zu zeigen, wie Funktionsaufrufe dieser Art durchgeführt werden. Die FORTRAN SUBROUTINE-Anweisung hat die Form: :: SUBROUTINE DGEMM (TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B,LDB, BETA, C, LDC) Die Funktion gehört zu einer großen Funktions-Bibliothek, der sogenannten BLAS (Basic Linear Algebra System). Funktionen in DLLs müssen eine sogenannte C-Schnittstelle haben, die bei dieser Funktion so aussieht: :: int dgemm_(char *transa, char *transb, int *m, int *n, int *k, double *alpha, double *a, int *lda, double *b, int *ldb, double *beta, double *c, int *ldc); Die entsprechende UniScript Registrierung hat dann den folgenden Aufruf: :: RegisterFunction("rs_math.dll", "dgemm_", "int",.. "_dgemm", .. ["char*", "char*", "int*", "int*", .. "int*", "double*", "double*", .. "int*", "double*", .. "int*", "double*", "double*", "int*"]); Wir rufen die Funktion nicht direkt auf, sondern schreiben eine "Verpackung". UniScript kann beim Aufruf von externen DLL-Funktionen nur ganz wenige Fehlerüberprüfungen machen. Ein fehlerhafter Aufruf kann zum "Absturz" von UniPlot führen (zu einer sogenannten Speicherzugriffsverletzung). Damit ein solches Problem nicht auftreten kann, wird eine sichere Hülle für ``_dgemm()`` geschrieben. Um das Beispiel zu vereinfachen, setzen wir in der Formel:: C = alpha * A * B + beta * C ``alpha`` auf 1 und ``beta`` auf 0. :: // C = A * B def Multiply(a, b) { if (nargsin() != 2) { error("number of args"); } if (type(a) != "real" || type(b) != "real") { error("type error"); } nra = nr(a); nrb = nr(b); nca = nc(a); ncb = nc(b); if (nra != ncb) { error("dimensions must match"); } c = zeros(nra, ncb); _dgemm("N", "N", nra, ncb, nca, 1.0, .. a, nra, b, nrb, 0.0, c, nra); return c; } Die folgenden Zeilen testen die Schnittstelle und vergleichen das Ergebnis mit dem in UniScript eingebauten Multiplikationsoperator. :: a = rand(2, 3); b = rand(3, 2); x1 = Multiply(a, b) x2 = a * b; print x1-x2; .. index:: GetCurrentDirectory, SetCurrentDirectory, kernel32, cd **GetCurrentDirectory, SetCurrentDirectory** Wir wollen nun noch zwei Funktionen schreiben, mit denen man Verzeichnisse wechseln kann, bzw. die das aktuell gesetzte Arbeitsverzeichnis anzeigen: * ``gwd()`` (get working directory) soll den Namen des aktuell gesetzten Verzeichnisses zurückgeben. * ``cd()`` (change directory) soll in ein anderes Verzeichnis wechseln. Die entsprechenden Windows-Betriebssystem-Aufrufe sind ``GetCurrentDirectoryA()`` und ``SetCurrentDirectoryA()``. ``GetCurrentDirectoryA()`` hat zwei Parameter: Der erste ist die Größe des Puffers, in den die Funktion den aktuellen Verzeichnisnamen schreibt, der zweite Parameter ist ein Zeiger auf den Puffer. Die Funktion schreibt nicht mehr Zeichen in den Puffer, als im ersten Parameter angegeben wurde. Sie gibt die Anzahl der Zeichen zurück, die in den Puffer tatsächlich geschrieben wurden. Die Registrierung der Funktion sieht folgendermaßen aus: :: RegisterFunction("KERNEL32", "GetCurrentDirectoryA",.. "uint", "GetCurDir", ["uint", "char*"]); Entsprechend die Registrierung für SetCurrentDirectoryA() :: RegisterFunction("KERNEL32", "SetCurrentDirectoryA",.. "uint", "SetCurDir", "char*"); Wir schreiben nun eine "Verpackung" für die beiden Funktionen: :: def gwd() { ss = "012345678901234567890123456789"; ssBuffer = ""; for (i in 1:10) ssBuffer = ssBuffer + ss; GetCurDir(strlen(ssBuffer), ssBuffer) return ssBuffer; } Die ersten drei Zeilen im Rumpf der Funktion ``gwd()`` erzeugen einen Puffer mit 300 Zeichen, der dann von der Funktion ``GetCurrentDirectoryA()`` gefüllt bzw. überschrieben wird. Der Puffer wird dann an den Aufrufer der Funktion ``gwd()`` zurückgegeben. Die Funktion ``SetCurrentDirectoryA()`` soll ebenfalls eine Verpackung erhalten. Die beiden :keyword:`if`-Abfragen stellen sicher, dass ``SetCurrentDirectoryA()`` nicht mit fehlerhaften Parametern aufgerufen werden kann. :: def cd(ssDir) { if (nargsin() != 1) { error("usage: cd (ssDir)") } if (type(ssDir) != "string") { error("usage: cd (ssDir)") } return SetCurDir(ssDir); } Zum Schluss können die beiden Funktionen durch einige Aufrufe getestet werden:: * cd ("c:/") 1.0000 * cd () >>> cd : usage: cd (ssDir) * gwd() c:\ * cd("d:/uiplot/script") 0.0000 * gwd() c:\ * cd("d:/uniplot/script") 1.0000 * gwd() d:\uniplot\script .. index:: COM .. index:: ActiveX .. _com-interface: .. _activex-schnittstelle: COM-Schnittstelle ----------------- Ab Version 3.0 kann UniScript auf Objekte zugreifen, die ein sogenanntes Dispatch-Interface haben. .. index:: Excel Zugriff auf Excel ^^^^^^^^^^^^^^^^^ Wir wollen die Verwendung dieser neuen Schnittstelle an einigen Beispielen zeigen. Wir verwenden dazu als Beispiel das Tabellenkalkulationsprogramm Excel von Microsoft. Es soll eine Tabelle mit Daten gefüllt werden. Zunächst muss Excel gestartet werden. Dazu wird die Funktion :ref:`CreateObject` verwendet, die einen Zeiger(eine Referenz) auf Excel zurückgibt. Die Funktion erwartet als Argument den Servernamen des Programms das gestartet werden soll. Bei Excel lautet der Servername ``"Excel.Application"``. :: xls = CreateObject("Excel.Application") xls.Visible = 1 Die zweite Anweisung (``xls.Visible = 1``) macht das Fenster der Applikation sichtbar. Es wird die Punktschreibweise verwendet um auf eine Eigenschaft (property) des Objekts zuzugreifen. Die Punktschreibweise wird auch verwendet um auf Methoden (Funktionen) eines Objekts zuzugreifen. Die beiden folgenden Anweisungen fügen Excel eine Arbeitsmappe hinzu. :: wb = xls.Workbooks wb.Add(); Die beiden Anweisungen können auch in einer einzelnen Anweisung geschrieben werden. :: xls.Workbooks.Add() Mit den folgenden Zeilen wird die Tabelle gefüllt. :: xls.ActiveSheet.Range("a1").Value = "x" xls.ActiveSheet.Range("b1").Value = "sin(x)" x = linspace(0, 2*PI) y = sin(x) xls.ActiveSheet.Range("a2:a101").Value = x' xls.ActiveSheet.Range("b2:b101").Value = y' .. index:: DAO .. index:: ADO Zugriff auf Datenbanken ^^^^^^^^^^^^^^^^^^^^^^^ Das folgende Beispiel verwendet die ADO (ActiveX Data Objects) um auf eine Excel-Tabelle wie auf eine Datenbanke mit SQL zuzugreifen. :: // create an Excel 2007 file with the following content and // name the sheet Sheet1: /* a b c 1 2 test1 4 5 test2 3 1 test3 */ def ado_Excel_Test() { //Must be an XLSX file, slash or double backslash ssExcelFile = "c:\\excelfile.xlsx"; ssSheetName = "Sheet1"; // HDR=Yes : First row are the column names of the table strCnn = "Provider=Microsoft.ACE.OLEDB.12.0;Data " + ... "Source=%s;" + .. "Extended Properties=\"Excel 12.0 Xml;HDR=Yes;IMEX=1\";" strCnn = sprintf(strCnn, ssExcelFile); db = db_create("ado"); if (db.connect(strCnn) == 0) { MessageBoxError("Cannot connect: %s", strCnn); return "" } // $-sign is neccessary for the table name // return all data ssSQL = sprintf("SELECT * FROM [%s$];", ssSheetName); smData = db.exec(ssSQL); // return all columns and all records where a > 1 ssSQL = sprintf("SELECT * FROM [%s$] Where a > 1;", ssSheetName); smData = db.exec(ssSQL); // return column b and c where column a > 1 ssSQL = sprintf("SELECT b,c FROM [%s$] Where a > 1;", ssSheetName); smData = db.exec(ssSQL); return smData; } Das folgende Beispiel verwendet die DAO DataBase-Engine um eine Excel-Tabelle zu lesen. Dabei kann mit der Punktschreibweise auf die Objekte der DataBase-Engine zugegriffen werden. :: dao = CreateObject("Dao.DBEngine.36"); if (dao == 0) { error("Dao.DBEngine.3.6 kann nicht erzeugt werden"); } ws = dao.CreateWorkspace("", "Admin", "", 2); ssFile = GetRootDirectory() + "samples\ole\test.xls" dbs = ws.OpenDatabase(ssFile, 0, 0, "Excel 8.0;HDR=No;"); m = dbs.OpenRecordset("test$").GetRows(1000); print m .. index:: Variantmatrizen Variantmatrizen ^^^^^^^^^^^^^^^ In einer Excel-Tabelle können sich unterschiedliche Datentypen befinden. In dem Beispiel oben wurde in die Zelle A1 die Zeichenkette "x" geschrieben und in die Zellen A2 bis A101 wurden Gleitkommazahlen geschrieben. Bei Datenbanken kann jede Spalte einen anderen Datentyp haben. In der ersten Spalte könnte sich beispielsweise ein Datum befinden, in der zweiten Spalten ganze Zahlen und in der 3. Spalte Zeichenketten mit 80 Zeichen Länge. Um diese Art von Daten mit UniScript einfach bearbeiten zu können, wurden in UniScript 3.0 Variant-Matrizen eingeführt. Variant-Matrizen können Elemente der folgenden 4 Datentypen enthalten: .. list-table:: :header-rows: 1 * - Typ - Bedeutung * - real - Doppelt genaue Gleitkommazahlen. Jede Zahl verwendet 8 Bytes. * - complex - Komplexe Zahlen. Das sind Pärchen von doppelt genauen Gleitkommazahlen. Jede komplexe Zahl verwendet also 16 Bytes. * - string - Zeichenketten bis zur Länge 2 hoch 31. * - object - Referenzen auf COM-Objekte. Das folgende Beispiel erzeugt einen Spalten-Vektor mit 3 Gleitkommazahlen, einer komplexen Zahl, einem String und einem Objekt. :: * xls = CreateObject("Excel.Application"); * v = [ConvertToVariant([1,2,3]), 4+5i, "Hello", xls]' * v 1.0000 2.0000 3.0000 4.0000 + 5.0000i Hello (0x001B6FC4) Auf Variantmatrizen kann in der gleichen Art zugegriffen werden wie auf andere UniScript-Matrizen. Mit Variantmatrizen kann jedoch weder gerechnet werden, noch können Vergleichsoperatoren auf Variant-Matrizen angewendet werden. .. index:: VariantConvert, ConvertToVariant, VariantGetType Die folgenden Anweisungen führen also zu einem Laufzeitfehler:: a = [1, "Hallo"] b = a if (a == b) MessageBox("a gleich b") Auch die folgenden Anweisungen sind nicht zulässig, da beim Zugriff auf eine Variantmatrix keine Typumwandlungen durchgeführt werden. :: a = [1, "Hallo"] b = a print a[1] + b[1] ``a[1]`` bzw. ``b[1]`` sind Variant-Matrizen mit jeweils einem Element des Typs ``"real"``. Um mit Variant-Matrizen rechnen zu können müssen sie zunächst mit der Funktion :ref:`VariantConvert` in Real-Matrizen umgewandelt werden. :: a = [1, "Hallo"] b = a print VariantConvert(a[1]) + VariantConvert(b[1]) Die inverse Funktion zu :ref:`VariantConvert` ist :ref:`ConvertToVariant`. Mit :ref:`VariantGetType` kann die Typ-Matrix einer Variant-Matrix erfragt werden. Die UniScript-Variant-Matrizen sind nicht mit dem Datentyp Variant von Visual-Basic bzw. der COM-Schnittstelle zu verwechseln. Der Variant-Datentyp der COM-Schnittstelle hat folgende Datentypen: .. index:: VT_EMPTY, VT_NULL, VT_I2, VT_I4, VT_R4, VT_R8, VT_CY, VT_DATE .. index:: VT_BSTR, VT_ERROR, VT_BOOL, VT_UI1 .. list-table:: :header-rows: 1 * - Typ-Nr. - Name - Bedeutung * - ``0`` - VT_EMPTY - Leer. * - ``1`` - VT_NULL - Null. * - ``2`` - VT_I2 - 2-byte signed int. * - ``3`` - VT_I4 - 4-byte signed int. * - ``4`` - VT_R4 - 4-byte real. * - ``5`` - VT_R8 - 8-byte real. * - ``6`` - VT_CY - Currency. * - ``7`` - VT_DATE - Datum/Zeit. * - ``8`` - VT_BSTR - Binary string. * - ``10`` - VT_ERROR - SCODE. * - ``11`` - VT_BOOL - Boolean. * - ``17`` - VT_UI1 - Unsigned char. Wenn UniScript eine Eigenschaft oder Methode der COM-Schnittstelle verwendet muss UniScript Datentyp-Umwandlungen zwischen den UniScript-Datentypen und den Datentypen der COM-Schnittstelle durchführen. UniScript geht dabei folgendermaßen vor. Die Typen VT_I2, VT_I4, VT_R4, VT_R8, VT_ERROR, VT_UI1 werden in den UniScript-real (doppelt genaue Gleitkommazahl) Datentyp umgewandelt. Typ Nummer VT_BSTR wird in einen UniScript-String umgewandelt. Die Typen VT_EMPTY, VT_NULL, VT_CY, VT_DATE werden in komplexe Zahlen umgewandelt. Der Real-Teil ist dabei der eigentliche Wert und der Imaginärteil die Typ-Nummer. Beispiel: Enthält die Spalte A einer Excel-Tabelle die folgenden Daten, :: A x 1,23 - 01.01.2000 #DIV/0! FALSCH dann liefert UniScript den folgenden Vektor: :: * v = xls.ActiveSheet.Range("a1:a6").Value' * print v x 1.2300 0.0000 + 0.0000i 36526.0000 + 7.0000i -2.147e+009 + 10i 0.0000 Das Feld A3 ist leer, dafür liefert UniScript die komplexe Zahl ``0+0i``. Das Feld A4 enthält ein Datum, dafür liefert UniScript die komplexe Zahl ``36526+7i``. Mit ``DT_Format(real(v[4]))`` kann man diesen Datumswert in eine Zeichenkette umwandeln. Die Zelle A5 (-2.147e+009 + 10i) enthält einen Error-Code, in diesem Fall den Error-Code für die Excel-Formel ``=1/0``. .. index:: VBScript, JavaScript, Visual Basic, C++, AutoLoad, Excel UniPlot als Server ^^^^^^^^^^^^^^^^^^ UniPlot kann auch als Server verwendet werden. Sie können also Funktionen von UniPlot/UniScript in anderen Anwendungen verwenden. UniPlot hat dabei eine duale Schnittstelle, d.h., es kann sowohl von Sprachen wie VBScript, JavaScript, Visual Basic für Applikationen als auch von Sprachen wie C, C++, Visual Basic verwendet werden. Dies soll an einigen Beispielen mit Excel gezeigt werden. Um die Beispiele auszuprobieren, benötigen Sie Excel 97 oder Excel 2000. **Vorbereitung** Damit Sie die Beispiele ausprobieren können, müssen Sie zunächst folgende Schritte durchführen: * Falls UniPlot gestartet ist, beenden Sie UniPlot. * Öffnen Sie ein DOS-Kommandofenster. * Die Beispiele finden Sie im Verzeichnis :file:`UniPlot\\samples\\ole`. Kopieren Sie die UniScript-Dateien deren Namen mit ``AX-`` anfangen in das UniPlot Autoload-Verzeichnis. Sie können den folgenden Befehl im DOS-Kommandofenster eingeben:: copy c:\UniPlot\Samples\Ole\AX-*.ic c:\UniPlot\AutoLoad * Wechseln Sie in das Verzeichnis :file:`\\uniplot\\program`. Starten Sie UniPlot mit der folgenden Kommandozeile:: uniplot /regserver Damit ist UniPlot in der Registrierdatenbank als OLE-Server angemeldet. * Wenn Sie die Beispiele ausprobiert haben, löschen Sie die :file:`ax-*.ic`-Dateien aus dem :file:`uniplot/autoload`-Verzeichnis. **Beispiel 1 (AX-Data)** Das folgende Beispiel zeigt, wie UniScript-Funktionen von Excel aufgerufen werden können und wie Daten an diese Funktionen übergeben werden können. Um das Beispiel auszuführen, gehen Sie wie folgt vor: * Starten Sie UniPlot. Öffnen Sie das UniScript Kommando- Fenster (:ref:`ansichtkommando-fenster`). * Starten Sie Excel. Öffnen Sie die Datei :file:`uniplot\\samples\\ole\\ax-data.xls`. Excel zeigt evtl. ein Dialogfeld mit der Meldung, dass die Datei Makros enthält. Wählen Sie die Schaltfläche **Makros aktivieren**. Ordnen Sie das UniPlot- und das Excel-Fenster nebeneinander an. * Selektieren Sie nun einige Felder der Excel-Tabelle. Sie sehen das im UniScript-Fenster Werte ausgegeben werden, wenn Sie die Selektion ändern. Wie funktioniert dieser Datenaustausch zwischen Excel und UniPlot/UniScript? Es wurden dazu zwei Programme geschrieben. Das Excel-Programm (in Visual-Basic für Applikationen) befindet sich in der Excel-Datei :file:`uniplot\\samples\\ole\\ax-data.xls`. Das UniScript-Programm befindet sich in der Datei :file:`uniplot\\autoload\\ax-data.ic`. Betrachten wir zunächst das Excel-Programm. Sie können es sich ausschauen, wenn Sie in Excel den Befehl **Extras=>Makro=>Visual Basic-Editor** (Alt+F11) ausführen. .. highlightlang:: vb.net :: Dim app As Object Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range) On Error GoTo Error Set app = CreateObject("UniPlot.Application") app.Visible = 1 n = app.Call("AX_DataTest", Target) Error: End Sub Mit ``Dim app As Object`` wird eine globale Variable erzeugt. Die Variable wird nicht innerhalb der Funktion erzeugt, damit die Variable nicht zerstört wird, wenn die Funktion beendet wird. Die Funktion ``Worksheet_SelectionChange`` wird von Excel aufgerufen, wenn sich die Selektion der Tabelle ändert. Als Parameter wird der Funktion ein Bereichs-Objekt übergeben, hier mit dem Namen ``Target``. Mit ``Set app = CreateObject("UniPlot.Application")`` wird eine Referenz auf eine laufende UniPlot-Instanz erzeugt. Die Funktion entspricht der :ref:`CreateObject`-Funktion von UniScript. Mit ``app.Visible = 1`` wird das UniPlot-Fenster angezeigt, falls es nicht bereits sichtbar ist. Mit ``n = app.Call("AX_DataTest", Target)`` wird eine UniScript-Funktion aufgerufen. Der erste Parameter ist der Name der UniScript-Funktion, hier ``AX_DataTest``, danach folgen die Parameter für die Funktion. Hier wurde nur ein Parameter übergeben, da die Funktion ``AX_DataTest`` nur einen Parameter erwartet. Es können aber bis zu 16 Parametern über die Call-Methode an UniScript-Funktionen übergeben werden. Schauen wir uns nun die UniScript-Funktion ``AX_DataTest`` an, die von Excel aufgerufen wird. Die Funktion bekommt eine Range-Objekt von Ecxel übergeben. In der Funktion werden die Rows-Eigenschaft und die Value-Eigenschaft des Range-Objekts verwendet. Schauen Sie sich im Excel-Hilfesystem die Bedeutung dieser Eigenschaften an. .. highlightlang:: us :: def AX_DataTest(t) { if (t.Rows.Count > 100) { print "Rows > 100" } else { print t.Value } } **Beispiel 2 (AX-Curv)** Um das zweite Beispiel auszuführen, gehen Sie wie folgt vor: * Falls UniPlot gestartet ist, beenden Sie UniPlot. * Starten Sie Excel. Öffnen Sie die Datei :file:`uniplot\\samples\\ole\\ax-curv.xls`. Excel zeigt evtl. ein Dialogfeld mit der Meldung, dass die Datei Makros enthält. Wählen Sie die Schaltfläche **Makros aktivieren**. * In der Excel-Tabelle befinden sich zwei UniPlot-Objekte mit jeweils einem Diagramm. Um Daten zum linken Diagramm zu schicken, markieren Sie die Zahlen 1, 4, 2, 2, etc. Zum rechten Diagramm werden die Zahlen geschickt, wenn die erste Zelle einen Wert ungleich 1 enthält. .. image:: S:/uniplot-obj/images/OLE_Excel_Curve.* .. highlightlang:: vb.net :: Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range) Dim arr(100) As Double On Error GoTo Error Worksheets("Sheet 1").OLEObjects(1).Enabled = True Worksheets("Sheet 1").OLEObjects(1).AutoLoad = True Worksheets("Sheet 1").OLEObjects(2).Enabled = True Worksheets("Sheet 1").OLEObjects(2).AutoLoad = True hDocLeft = Worksheets(1).OLEObjects(1).Object.Handle hDocRight = Worksheets(1).OLEObjects(2).Object.Handle t1$ = Cells(22, 2).Value t2$ = Cells(23, 2).Value i = 0 For Each Cell In Target.Cells arr(i) = CDbl(Cell.Value) i = i + 1 Next Set app = Worksheets("Sheet 1").OLEObjects(1).Object.Application n = app.Call("AX_CurveTest", hDocLeft, hDocRight, arr, i, t1$, t2$) Error: End Sub .. highlightlang:: us :: def AX_CurveTest(hDoc1, hDoc2, y, n, t1, t2) { if (y[1] == 1.0) { hPage = DocGetActivePage(hDoc1); } else { hPage = DocGetActivePage(hDoc2); } if (hPage == 0) { return 0; } hLayer = PageGetAllDataLayers(hPage) hData = LayerGetAllDatasets(hLayer); if (hData == 0) { hData = XYCreate("test", 1, 1); LayerAddDataset(hLayer, hData); } if (n > 1) { x = 1:n; ret = XYSetData(hData, x, y[x]); } LayerAutoScale(hLayer) LayerSetAxisTitle(hLayer, "x", t1) LayerSetAxisTitle(hLayer, "y", t2) __FF_FieldUpdate(0, hPage, 0, 0, 0); PageReplot(hPage); return n; } .. index:: Debugger .. _der-debugger-fur-funktionen: .. _debugger: Der Debugger für Funktionen --------------------------- Mit dem Debugger können Sie UniScript-Funktionen im Einzelschritt durchlaufen, Haltepunkte setzen sowie sich die Werte von lokalen und globalen Variablen anschauen. .. image:: S:/uniplot-obj/images/Debugger.* Die Anwendung des Debuggers soll an einem Beispiel gezeigt werden: * Öffnen Sie die Datei :file:`uniplot\\script\\do_new.ic`. * Setzen Sie den Cursor auf die Zeile 62 (``def _DoFileNew()``). Drücken Sie die Funktionstaste :kbd:`F9` (**Toggle-Breakpoint**) um einen Haltepunkt auf diese Zeile zu setzen. Am linken Rand des Editors sehen Sie das Breakpoint-Symbol (rote Kreisfläche). * Führen Sie den Befehl :ref:`dateineu` aus. Am Breakpoint (Haltepunkt) erscheint ein gelber Pfeil. * UniPlot hält die Ausführung des Programms am Breakpoint an. Mit der Funktionstaste :kbd:`F10` können Sie die Funktion im Einzelschritt durchlaufen. Mit :kbd:`F5` können Sie bis zum nächsten Breakpoint bzw. zum Ende des Programms laufen. * Bewegen Sie dazu den Mauszeiger über einen Variablennamen innerhalb der Funktion und halten Sie den Mauszeiger an. Der Wert der Variablen wird in einem kleinen gelben Fenster (Tooltip) angezeigt. Folgende Funktionen stehen beim Debugger zur Verfügung: :kbd:`F5` Laufe bis zum nächsten Haltepunkt (BreakPoint). Falls sich der Haltepunkt in einer Datei befindet die nicht geöffnet ist, wird die Datei vorher geöffnet. :kbd:`F9` Setze/Entferne Haltepunkt in der Zeile, in der sich der Cursor befindet. :kbd:`Strg+F9` Löscht alle Haltepunkte. :kbd:`F10` Aufruf als ein Schritt (Step). :kbd:`F11` In Aufruf springen (Step into). .. index:: FORTRAN, C, C++, call by value, call by reference, startup.ic .. index:: what, load .. _noch-ein-paar-bemerkungen-zu-funktionen: Noch ein paar Bemerkungen zu Funktionen --------------------------------------- Um das Kapitel Funktionen abzuschließen, noch einige Hinweise und Begriffe: * Man unterscheidet zwischen Benutzerfunktionen, *user functions*, die in UniScript geschrieben sind, und eingebauten Funktionen *build in functions* die in C , C++ oder FORTRAN geschrieben sind. * In UniScript können rekursive Funktionen geschrieben werden. Das sind Funktionen, die sich direkt oder indirekt selbst aufrufen. * In UniScript werden im Gegensatz zu C keine Werte an Funktionen übergeben (call by Value) sondern Referenzen (call by Reference). Es ist also möglich, daß die Parameter in Funktionen versehentlich verändert werden. Man sollte hier etwas vorsichtig sein und Kopien der Parameter verwenden. Beispiel:: // FALSCH def asin(x) { // FALSCH !!!!!! x = -1i .* log (1i .* x + sqrt(1 - x .* x)); if (all(all(real(x) == x))) { return real(x); } else { return x; } } // RICHTIG def asin(xx) { x = xx; // Kopie verwenden x = -1i .* log (1i .* x + sqrt(1 - x .* x)); if (all(all(real(x) == x))) { return real(x); } else { return x; } } * Mit der Funktion :ref:`what` können die Namen der geladenen Funktionen im Kommandofenster ausgegeben werden. * Dateien mit Funktionen werden mit der Funktion :ref:`load` geladen. Den Aufruf schreibt man gewöhnlich in die Datei :file:`startup.ic`. :sub:`id-618984`