Eigene Bibliotheken für TurboCalc entwickeln

TurboCalc dürfte wohl jeden Amiga-User ein Begriff sein - lag die Tabellenkalkulation doch im "Magic Bundle" jedem neuen A1200/A4000 bei. Irgendwann ist sicherlich der Punkt gekommen, da möchte man sich eigene Funktionen bzw. Makros basteln, für die es (bis jetzt) noch keine Programm-Funktion gibt. Als Handwerkszeug brauchen wir natürlich TurboCalc (ab V3), einen Assembler und ggf. noch einen Texteditor.

Bitte beachten Sie, das alle Pseudo-Opcodes der DevPac-Syntax entsprechen und ggf. noch angepasst werden müssen, falls Sie einen anderen Assembler verwenden. Der Einfachheit halber wollen wir nun eine eigene Bibliothek (TC-LIB) erstellen, die es uns erlaubt, beliebige Programme/DOS-Befehle aus TurboCalc heraus auszuführen. Mehr soll sie nicht können - aber vielleicht sind Sie ja nach dem Durchlesen dieser Anleitung in der Lage, weitere Funktionen einzubauen.


Unser Ziel: Shell meets TurboCalc


Jede TurboCalc-Bibliothek besitzt einen speziellen Vorspann, auch Header genannt. Dieser muss immer den selben Aufbau haben, ansonsten kann es zu Fehlern und Abstürzen kommen. Legen wir also los:

sysbase equ 4

moveq #-13,d0
rts

Nun, die erste Zeile hat nichts mit dem Header zu tun, sie sagt dem Assembler, das der Ausdruck SYSBASE beim Assemblieren in die Zahl 4 übersetzt wird. In dieser Adresse finden wir die sogenannte EXEC-Base, also die Basis-Adresse des Amiga-Betriebssystems. Diese werden wir später noch gebrauchen, um an die benötigten Funktionen heranzukommen. Was dann folgt ist lediglich ein Schutz. Ruft der Anwender die Library als Programm auf, so wird diese sofort wieder beendet. Alles klar soweit? Dann folgt jetzt der eigentliche Vorspann:

dc.l "TCLB" ; Kennung für TurboCalc
dc.l "FNCT" ; Diese Library enthält Funktionen
dc.w 1,0 ; Version 1, Revision 0
dc.l lib_beschreibung
dc.l lib_version
dc.l lib_author
dc.l 0
dc.l 0
dc.l 0
dc.l 0 ; Init
dc.l 0 ; Exit
dc.l 0 ; Flush
dc.l 0

Die ersten beiden Zeilen teilen TurboCalc mit, das es sich um eine TC-Library handelt, die lediglich Funktionen beinhaltet. Als nächstes können wir festlegen, um welche Version/Revision es sich bei unserem Werk handelt. Wie gesagt, dieser Wert ist für uns gedacht - falls es mehrere Überarbeitungen gibt, können wir so leicht erkennen, welche wir vor uns haben.

Die drei darauffolgenden Zeilen sehen für den Einsteiger ggf. etwas unverständlich aus. Aber keine Sorge. Es ist ganz einfach: Wir haben hier die Adressen auf drei Texte (Strings) abgelegt, die eine kleine Beschreibung, die Version und den Namen des Programmierer beinhalten. Auch hier gilt wieder: Diese Strings sind in erster Linie wieder für uns gedacht.

Interessant wird es erst wieder bei INIT, EXIT und FLUSH. Wir benötigen zwar diese Funktionen nicht (daher die drei Langworte mit 0 gefüllt), trotzdem könnten sie einmal wichtig sein. Die Init-Routine wird von TurboCalc angesprungen, sobald die Library geöffnet wird. Hier könnte man z. B. Speicher reservieren und Programmteile Initialisieren. EXIT wird - wie der Name vermuten lässt - immer beim Schliessen der Bibliothek angesprungen. So könnte man z. B. den belegten Speicher wieder freigeben usw. FLUSH wird immer dann ausgeführt, wenn der Arbeitsspeicher knapp wird. Unsere Library könnte dann teile des belegten Speichers freigeben oder sonstige Vorkehrungen treffen.

dc.l 0
dc.l 1 ; Hier anzahl der Funktionen eingeben!
dc.l fkt_vorgabe
dc.l fkt_shell

lib_beschreibung: dc.b "Unsere erste TCLIB!",0
lib_version: dc.b "$VER: Demo-Lib V1.0 by Brueggi",0
lib_author: dc.b "Brueggi",0

cnop 0,2

In der ersten Zeile können wir bestimmte Flags setzen - wir lassen diese jedoch bitte immer auf 0! Nun legen wir die Anzahl der Funktionen fest, die unsere Bibliothek beinhaltet. Wir haben nur eine, daher kommt in Zeile zwei eine "1". Jetzt kommt das selbe, wie schon weiter oben bei den Beschreibungen: Wir legen nun die Adressen der Funktion(en) ab. TurboCalc springt diese dann über diese Liste an. Der aufmerksame Leser wird sich sicherlich wundern: Warum sagt der Typ, wir hätten nur eine einzige Funktion - legt hier aber zwei an?

Ganz einfach: Die erste Funktion (fkt_vorgabe) wird immer dann von TurboCalc aufgerufen, wenn wir eine ungültige Funktionsnummer (in diesem Beispiel z. B. die zweite, dritte, usw.) anspringen. Erst dann beginnt die Liste unserer Funktion(en). Was nun noch folgt sind die oben angekündigten Texte. Wie gesagt, der Inhalt ist in erster Linie für uns gedacht. Ach ja: Bitte immer die Texte mit einem 0-Byte abschliessen. Schließlich muss TurboCalc (und der Amiga) auch wissen, wo ein Text endet und wo der nächste beginnt.

Wichtig: Vergesst bitte nicht das CNOP 0,2! Damit sagt man dem Assembler, das er die darauffolgenden Befehle/Daten wieder auf eine Langwort-Grenze ausrichten soll. Tut man dies nicht, kommt unser 68000- Prozessor ganz schön ins Trudeln und bedankt sich mit einem wunderschönen Guru!

fkt_vorgabe:
clr.l d0
moveq #3,d2
rts

fkt_shell:
move.l #-1,result ; Rückgabe auf -1 setzen
move.l a0,d0 ; String-Pointer nach D0
tst.l d0 ; Ist da überhaupt ein Befehl übergeben?
beq.s fkt_nostring ; Nein, dann raus.
move.l a0,kommando

movem.l a0-a6/d3-d7,-(a7) ; Register retten
move.l sysbase,a6 ; Basis-Adresse EXEC nach A6
lea dosname,a1
clr.l d0 ; Version egal
jsr -552(a6) ; DOS.library öffnen
tst.l d0
beq.s fkt_nolib
move.l d0,dosbase ; Basisadresse der DOS-lib sichern

move.l d0,a6
move.l #console,d1 ; Pseudo-Dateiname
move.l #1005,d2 ; Zugriffsmodus "Old"
jsr -30(a6) ; "Datei" öffnen
tst.l d0
beq.s fkt_abbruch ; Klappt nicht -> Abbruch
move.l d0,con_handler ; Ein-/Ausgabe Handler sichern

move.l kommando,d1
clr.l d2 ; Input-Kanal auf Vorgabe
move.l d0,d3 ; Ausgabe ist unsere "Datei"
jsr -222(a6) ; Execute
move.l d0,result ; Ergebnis merken

move.l con_handler,d1
move.l #txt0,d2 ; Adresse
move.l #27,d3 ; Länge
jsr -48(a6) ; Textausgabe

move.l con_handler,d1
move.l #buffer,d2
move.l #3,d3 ; Anzahl zeichen lesen (max.)
jsr -42(a6) ; Auf ENTER warten

move.l con_handler,d1
jsr -36(a6) ; Close

fkt_abbruch:
move.l dosbase,a1
move.l sysbase,a6
jsr -414(a6) ; DOS-Library schliessen
movem.l (a7)+,a0-a6/d3-d7 ; Register wiederherstellen
move.l result,d0
moveq #3,d2
rts

fkt_nolib:
moveq #9,d2 ; Error
move.l #1,d0 ; Unknown Error
movem.l (a7)+,a0-a6/d3-d7
rts

fkt_nostring:
moveq #3,d2 ; Rückgabe
move.l #-1,d0 ; -1
rts

result: dc.l 0 ; Fehler-Status
con_handler: dc.l,0 ; Ein-Ausgabe-Handle
kommando: dc.l 0 ; Zeiger auf Befehl
dosbase: dc.l 0 ; Basis-Adresse DOS-Library
console: dc.b "CON:0/0/590/200/TurboCalc Shell/CLOSE",0 ; Unser "Dateiname"
dosname: dc.b "dos.library",0
txt0: dc.b 10,27,"[1m Eingabetaste drücken ",0
cnop 0,2
buffer: dc.l 0,0


Lieber ein Makro...

Assembliert man dieses Listing nun, braucht man nur noch die Endung ".tclib" an den Dateinamen des fertigen Programms anhängen, und das ganze in das Unterverzeichnis TCLIB von TurboCalc kopieren. Zum Einbinden in eigene Tabellen genügt folgender Aufruf:

=TCFkt("name.tclib";funktion;a;b[;"Text"])

Konkret an einem Beispiel erläutert heißt dies: Falls unsere Library z. B. SHELL.TCLIB heißt, und wir ein Shell-Kommando in Zelle A1 ausführen möchten, können wir z. B. in B1 eingeben: =TCFkt("shell.tclib";1;0;0;A1)

Einen Nachteil hat die Sache aber: Sobald die Tabelle erneut geladen wird, oder wir eine Zelle ändern, dann wird jedesmal das Shell-Kommando ausgeführt. Das ist nicht nur nervig, sondern erlaubt es auch, ziemlich böse Dinge zu tun - man denke nur an den Aufruf =TCFkt("shell.tclib";1;0;0;"format drive hd0: name Leer <>nil:") der sofort nach Laden der Tabelle unsere Workbench-Partition formatiert. Daher sollte man lieber ein Makro einbinden (siehe Bild oben) und das Kommando per Knopfdruck ausführen lassen. Das schont die Nerven und der Anwender muss nicht gleich um seine Daten fürchten.

Bleibt am Schluss nur noch eine Sache: Wie kommt unsere Bibliothek eigentlich an die Parameter bzw. wie geben wir etwas an TurboCalc zurück?

Übergabe TurboCalc an TCLib

D0/D1: Parameter1 als Fließkomma
D2: Parameter1 als LongInt
D3/D4: Parameter2 als Fließkomma
D5: Parameter2 als LongInt
D7: Offset der Routine
A0: Adresse auf String (Parameter3) oder 0 wenn dieser leer ist

Rückgabe TCLib an TurboCalc

D2: Typ
D0: Inhalt/Zeiger

Mögliche Typen

0/1: Sollten nicht benutzt werden!
2: D0/D1 ist eine Fließkommazahl
3: D0 ist ein LongInt
4: D0 ist ein Datum
5: D0 ist eine Uhrzeit
6: D0 ist 0 (False) oder 1 (True)
7: D0 zeigt auf einen Text, oder ist 0 wenn dieser leer ist
8: D0 ist eine Spaltenpos., D1 eine Zeilenposition
9: D0 ist ein Fehlercode

Fehlercodes

1: Unknown Error
2: Div/0
3: Version
4: Reference
5: ) fehlt
6: Typenfehler
7: Keine Zelle
8: Arg_no_Range
9: ( fehlt
10: ; fehlt
11: Wert
12: End Of Line

Am Schluss noch ein kleiner Hinweis: Falls unsere Library Textspeicher braucht, dann sollten wir diesen nicht über Exec anfordern (AllocMem) sondern über TurboCalc. TC bietet hierfür diverse Funktions-Vektoren, die wir aufrufen können. Näheres dazu findet Ihr entweder demnächst hier, oder eben in der Doku zu TurboCalc ;-)

Jetzt aber viel Spaß beim Entwickeln von eigenen Funktionsbibliotheken!