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 DLLuser32.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 NamenFindWindowA
. 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()
einHWND
ist (ein Handle auf ein Fenster). Da RegisterFunction den TypHWND
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 mitAX-
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 demuniplot/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.
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.
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