8. 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.

8.1. Syntax von Funktionen

In UniScript kann eine Funktion sehr einfach definiert werden

def dB(rvSignal)
{
    return 20.0 .* log10(rvSignal);
}

Hinter dem Schlüsselwort 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
}

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 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.

8.2. 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.

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 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.

8.3. 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 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 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.

8.4. Die return-Anweisung

Die 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 return-Anweisung enthält, wird am Ende der Funktion eine Default-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 return-Anweisung ausgeführt und die Funktion beendet, ansonsten wird die zweite return-Anweisung ausgeführt.

8.5. Funktionen die mehrere Werte zurückgeben

Funktionen, die Sie bisher kennen gelernt haben, wie z. B. die Funktion 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 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

* <e, v> = 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 <o1, o2> = name(i1, i2)
{
    o1 = ...;
    o2 = ...;
}

Die beiden Ausgabeparameter (hier o1 und o2) werden nicht über die 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 nargsout ermittelt werden.

def <a,b,c,d> = 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 UniScript-Objekte) praktischer ist. Beispiel:

def Test(p1, p2)
{
   o = [.]; // Objekt erzeugen

   o.a = 123;
   o.b = "Hello";

   return o;
}

8.6. 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.

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.

Zur Registrierung bei UniScript dient die Funktion RegisterFunction. Sie können den Funktionsaufruf zum Beispiel in die Startup-Datei 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 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 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 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.

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

DGEMM

Das nächste Beispiel ist ein komplizierter Aufruf einer mathematischen Funktion, die sich in der mit UniScript mitgelieferten DLL 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;

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 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

8.7. COM-Schnittstelle

Ab Version 3.0 kann UniScript auf Objekte zugreifen, die ein sogenanntes Dispatch-Interface haben.

8.7.1. 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 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'

8.7.2. 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

8.7.3. 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:

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.

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 VariantConvert in Real-Matrizen umgewandelt werden.

a = [1, "Hallo"]
b = a
print VariantConvert(a[1]) + VariantConvert(b[1])

Die inverse Funktion zu VariantConvert ist ConvertToVariant.

Mit 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:

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.

8.7.4. 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 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 \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 ax-*.ic-Dateien aus dem 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 (Ansicht=>Kommando-Fenster).
  • Starten Sie Excel. Öffnen Sie die Datei 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 uniplot\samples\ole\ax-data.xls. Das UniScript-Programm befindet sich in der Datei 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.

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 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.

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 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.

    ../../_images/OLE_Excel_Curve.png
    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
    
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;
}

8.8. 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.

../../_images/Debugger.png

Die Anwendung des Debuggers soll an einem Beispiel gezeigt werden:

  • Öffnen Sie die Datei uniplot\script\do_new.ic.
  • Setzen Sie den Cursor auf die Zeile 62 (def _DoFileNew()). Drücken Sie die Funktionstaste 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 Datei=>Neu aus. Am Breakpoint (Haltepunkt) erscheint ein gelber Pfeil.
  • UniPlot hält die Ausführung des Programms am Breakpoint an. Mit der Funktionstaste F10 können Sie die Funktion im Einzelschritt durchlaufen. Mit 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:

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.
F9
Setze/Entferne Haltepunkt in der Zeile, in der sich der Cursor befindet.
Strg+F9
Löscht alle Haltepunkte.
F10
Aufruf als ein Schritt (Step).
F11
In Aufruf springen (Step into).

8.9. 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 what können die Namen der geladenen Funktionen im Kommandofenster ausgegeben werden.

  • Dateien mit Funktionen werden mit der Funktion load geladen. Den Aufruf schreibt man gewöhnlich in die Datei startup.ic.

id-618984