Bei Programmen die mehrere Dateien im RAM-Speicher verwalten sollen, steht der Programmierer oft vor dem Problem, eine möglichst flexible Lösung zu finden. Speicherchips sind nach wie vor nicht gerade billig, daher sollte ein guter Programmierer darauf bedacht sein, den RAM-Speicher, so gut es programmtechnisch möglich ist, optimal auszunutzen.
Der Idealfall beim Verwalten mehrerer Dateien sollte hierbei so aussehen, daß diese Dateien fein säuberlich im Speicher hintereinander stehen, so daß kein einziges Byte verschwendet wird. Das wäre ja alles kein Problem, wenn der Anwender nicht die Angewohnheit hätte, während der Arbeit Dateien aus dem Speicher löschen zu wollen. Würde er immer die Datei, die er zuletzt in den Speicher laden würde, auch zuerst wieder löschen, wäre auch dies kein Problem, weil man dann lediglich Speicherplatz von hinten „abschneiden“ müßte. Doch es ist ja tragischerweise oft so, daß der Anwender ausgerechnet eine Datei aus der Mitte löschen möchte, wenn er sie nicht mehr benötigt. Im RAM würde beim Löschen eine Lücke entstehen. Ähnliche Probleme haben ja auch die Entwickler der Treiber-Software für Diskettenlaufwerke und Festplatten. Was tun mit den Lücken, die durch das Löschen von Dateien entstehen? Bei Disketten und Festplatten hat man das Problem dadurch gelöst, daß man sich diese Lücken „merkt“ und Dateien, die. neu geladen werden, in diese leeren Stellen einfügt. Das hat jedoch den Nachteil, daß Dateien, die größer sind als diese leeren Stellen, in mehrere Einzelteile zerstückelt werden, was dazu führt, daß die Arbeit mit Disketten und Festplatten, bei häufigem Lesen und Beschreiben, mit derzeit immer langsamer vonstatten geht, da die einzelnen Sektoren, auf denen sich die Daten befinden, erst zusammengesucht werden müssen. Glücklicherweise gibt es für die Programmierer einer RAM-Verwaltung eine komfortablere Möglichkeit der Speicherverwaltung, die in diesem Artikel vorgestellt werden soll.
Die Grundidee der Problemlösung besteht darin, daß man die Lücke, die durch das Löschen einer Datei aus dem Speicher entstehen würde, nicht bestehen läßt. Um diese Lücke sinnvoll auszufüllen, verschiebt man einfach die Dateien, die hinter der gelöschten Datei stehen, an die Anfangsadresse der gelöschten Datei. Bei einem Diskettenlaufwerk würde man bei einer solchen Operation oft mehrere Minuten warten müssen, der Computer selbst kann jedoch auch größere Speicherbereiche relativ schnell verschieben.
Welche Überlegungen müßte man anstellen, um das Ganze programmtechnisch umzusetzen? Damit man es praxisnah nachvollziehen kann, findet der OMIKRON.BASIC-Programmierer am Ende dieses Artikels eine Liste der Befehle, die die entsprechenden Arbeitsgänge zeigen, auf die in dieser Beschreibung durch Nummern (1)..(2)..(3)... usw. Bezug genommen wird.
Zunächst sollte man einen bestimmten Speicherbereich reservieren, der als dynamischer RAM-Speicher dienen soll (1). In diesen Speicherbereich kann man nun die einzelnen Dateien laden (2). Die Anfangsadresse der ersten Datei, die geladen wird, liegt an der Anfangsadresse des reservierten Speicherbereichs. Nachfolgend geladene Dateien würden jeweils an die zuletzt geladene Datei angehängt werden. Die zweite Datei würde also an die Anfangsadresse des reservierten Bereichs + der Dateilänge der ersten Datei geladen werden. Wurden bereits mehrere Dateien geladen, müßte man also die einzelnen Dateilängen zur Anfangsadresse hinzuaddieren, um zu ermitteln, an welche Adresse die nächste Datei geladen werden soll. Das Programm müßte sich also, um die Dateianfänge wiederzufinden, in einem Variablenfeld die einzelnen Dateilängen merken und sie bis zu der Datei, auf die man zugreifen möchte, addieren. Kommen wir nun zu dem Fall, daß eine Datei aus der Mitte herausgelöscht werden soll. Hierzu addiert man die einzelnen Längen der nachfolgenden Dateien und verschiebt den gesamten nachfolgenden Bereich an die Speicheradresse, an der die zu löschende Datei steht (3). Da man die einzelnen Dateilängen sinnvollerweise in einem Datenfeld ablegen sollte, würde sich auch der jeweilige Feldindex verschieben. Die Nachfolgedatei würde also jetzt den Feldindex der gelöschten Datei haben (4). Auch der Feldindex weiterer nachfolgender Dateien würde sich um -1 verschieben. Wie man sieht, ist Flexibilität gar nicht so schwierig, und es wäre schade, wenn man sie nicht in jedem Fall, wo es nötig ist, einsetzt. Warum nicht den Mut haben und die Daten ein bißchen im RAM hin- und herschieben; letztendlich ist der Computer ja da, um für uns zu arbeiten.
Wichtige Arbeitsschritte zur dynamischen RAM-Verwaltung:
(1) CLEAR nnnn : Ram_Startadresse=MEMORY(nnnn)
(2) BLOAD Dateiname$,Datei_Startadresse
(3) MEMORY_MOVEB Adresse_Alf,Gesamtlaenge_Nachfolgedateien TO Adresse_Neu
(4) FOR i=Feldindex_Nachfolgedatei to Feldindex_Letzte_Datei
Dateilaenge(i-1 )=Dateilaenge(i)
NEXT
Das OMIKRON.BASIC-Listing soll noch einmal anhand eines praktischen Beispiels zeigen, wie man dieses Prinzip der dynamischen Speicherverwaltung in eigenen Programmen verwenden kann. Das Beispielprogramm erlaubt es, bis zu 10 Dateien im Speicher zu halten, die Dateien mit einem kleinen Datenmonitor anzusehen und einzelne auch wieder zu löschen. Die Darstellung im Datenmonitor erfolgt im ASCII-Modus, so daß alle möglichen Arten von Dateien in den Speicher geladen und angesehen werden können.
Um mit dem Befehl MEMORY_MOVEB vernünftig arbeiten zu können, benötigt man zur Behebung eines Fehlers im BASIC die Routine Patch_Basic aus der dem OMIKRON.-BASIC mitgelieferten GEM-Library. Für diejenigen, die sowieso mit GEM arbeiten, wird die Routine durch ApplInit automatisch aufgerufen. Diejenigen, die auf GEM verzichten möchten (aus welchem Grund auch immer), müßten sich diese Prozedur aus der GEM-Library rauskopieren und sie folgendermaßen aufrufen:
Patch_Basic $63,32,$6604,$6602:
Patch_Basic $148,38,$6402,$6404
GEM-Programmierer können hierauf verzichten und, wie das Beipiel-Listing zeigt, auf einige weitere Programmierhilfen zurückgreifen, wie zum Beispiel auf die Darstellung von Fenstern mit Titel und Infozeile sowie auf eine erweiterte Fileselectbox mit Titelzeile (für ältere TOS-Versionen).
'*********************************
'* Dynamische Speicherverwaltung *
'* mit OMIKRON BASIC *
'* von Mario Srowig *
'* (c) 1992 MAXON Computer *
'*********************************
'LIBRARY Gem ,"c:\omikron\interpr\GEM.LIB":' GEM-Library in Basic einbinden
CLEAR 150000: 'Genug Speicher für Zugriff durch MEMORY reservieren
Appl_Init:'GEM anmelden
PRINT CHR$(27);"f":'Cursor aus
Pfad$="A:\*.*":Ja%L=-1:Nein%L=0
Ndateien_Max%L=10:Nbytes%L= 100000:'Maximal 10 Dateien in einem
Create_Dyna_Ram(Ndateien_Max%L,Nbytes%L) :'Gesamtspeicher von 100 000 Bytes
'verwalten
Init_Funktionkeys
Titel%L= MEMORY(80):'Speicher für Window Titel-
Info%L= MEMORY(80):' und Infozeile reservieren
MOUSEOFF
Wind_Get(0,4,X%L,Y%L,W%L,H%L)
Wind_Create(17,X%L,Y%L,W%L,H%L,Handle%L)
Wind_Open(Handle%L,X%L,Y%L,W%L,H%L)
'HAUPTMENUE - SCHIRM
REPEAT
Clear_Screen
Wind_Set(Handle%L,2,"Dynamische Speicherverwaltung", Titel%L)
Wind_Set(Handle%L,3,"",Info%L)
LOCATE 5,2: PRINT "F1=Datei laden"
LOCATE 7,2: PRINT "F2=Datei zeigen"
LOCATE 9,2: PRINT "F3=Speicherplatz INFO"
LOCATE 11,2: PRINT "F4=Datei aus RAM löschen"
LOCATE 13,2: PRINT "F5=Programm Verlassen"
Scan_Keyboard
IF Scan_Code%L=F1%L THEN Datei_Laden
IF Scan_Code%L=F2%L THEN Datei_Zeigen
IF Scan_Code%L=F3%L THEN Speicherplatz_Info
IF Scan_Code%L=F4%L THEN Datei_Aus_Ram_Loeschen
IF Scan_Code%L=F5%L THEN Programm_Verlassen
UNTIL Endlos%L
DEF PROC Datei_Laden
Laden%L=Ja%L
Auswahl_Datei(Pfad$, "DATEI LADEN")
IF Ok%L AND Laden%L AND Datei_Vorhanden%L THEN
IF Dateilaenge%L>Nbytes_Frei%L THEN
Hinweis "Es ist nicht genügend|Speicherplatz vorhanden!"
ELSE
Datei_Start%L=Dyna_Ram_Start%L
FOR I%L=0 TO Dateiptr%L
Datei_Start%L=Datei_Start%L+Dateilaenge%L(I%L)
NEXT
BLOAD Dateiname$,Datei_Start%L
Dateilaenge%L(Dateiptr%L) =Dateilaenge%L
Dateiname$(Dateiptr%L)=Name$
Dateiptr%L=Dateiptr%L+1
Nbytes_Frei%L=Nbytes_Frei%L-Dateilaenge%L
ENDIF
ENDIF
RETURN
DEF PROC Auswahl_Datei(R Pfad$,Kopfseile$)
MOUSEON
Fsel_Exinput(Pfad$,Name$,Kopfzeile$,Ok%L)
MOUSEOFF
IF Ok%L AND Laden%L THEN
Dateiname$=LEFT$(Pfad$,LEN(Pfad$)-INSTR(1,MIRROR$(Pfad$)+"\","\"))+"\"+Name$
Datei_Vorhanden Dateiname$
IF NOT Datei Vorhanden%L THEN Hinweis "Die Datei ist|nicht vorhanden!"
ENDIF
RETURN
DEF PROC Datei_Zeigen
Wind_Set(Handle%L,2,"Datei Zeigen",Titel%L)
Wind_Set(Handle%L,3,"F1=Vorblättern F2=Zuruckblättern ESC=Abbruch", Info%L) Clear_Screen
Dateien_Zeigen
PRINT CHR$(27);"e":'Cursor ein
LOCATE 20,2: PRINT "Welche Datei soll gezeigt werden? ";
INPUT Eingabe$ USING "0-0",Ret%L,2
PRINT CHR$(27);'Cursor aus
Dnr%L=VAL(Eingabe$)
IF Dnr%L<=Dateiptr%L THEN
Clear_Screen
Startadr%L=Dyna_Ram_Start%L
FOR I%L=0 TO Dnr%L-2
Startadr%L=Startadr%L+Dateilaenge%L(I%L)
NEXT
Laenge%L=Dateilaenge%L(Dnr%L-1)
Zeichennr%L=0
Daten_Anzeigen
ELSE EXIT
ENDIF
REPEAT
Scan_Keyboard
IF Scan_Code%L=F1%L THEN :' Vorblättern
IF Zeichennr%L>Laenge%L THEN Zeichennr%L=Zeichennr%L-1520
IF Zeichennr%L<0 THEN Zeichennr%L=0
Daten_Anzeigen
ENDIF
IF Scan_Code%L=F2%L THEN :' Zurückblättern
Zeichennr%L=Zeichennr%L-3040
IF Zeichennr%L<0 THEN Zeichennr%L=0
Daten_Anzeigen
ENDIF
IF Scan_Code%L=1 THEN EXIT
UNTIL Endlos%L
RETURN
DEF PROC Daten_Anzeigen
FOR Zeile%L=5 TO 24
FOR Spalte%L=2 TO 77
LOCATE Zeile%L,Spalte%L
IF Zeichennr%L<=Laenge%L THEN
PRINT CHR$(1);CHR$(PEEK(Startadr%L+Zeichennr%L))
Zeichennr%L=Zeichennr%L+1
ELSE PRINT " "
ENDIF
NEXT
NEXT
RETURN
DEF PROC Speicherplatz_Info
Clear_Screen
Wind_Set(Handle%L,2,"Speicherplatz INFO",Titel%L)
Wind_Set(Handle%L,3,"ESC=Abbruch",Info%L)
LOCATE 5,2: PRINT "Freier Speicherplatz: ";Nbytes_Frei%L;" Bytes"
Dateien_Zeigen
REPEAT : Scan_Keyboard: UNTIL Scan_Code%L=1
RETURN
DEF PROC Datei_Aus_Ram_Loeschen
Clear_Screen
Wind_Set(Handle%L,2,"Datei aus RAM löschen",Titel%L)
Wind_Set(Handle%L,3,"ESC=Abbruch F1=löschen",Info%L)
Dateien_Zeigen
REPEAT
Scan_Keyboard
IF Scan_Code%L=F1%L THEN
PRINT CHR$(27);"e"
LOCATE 20,2: PRINT "Welche Datei soll gelöscht werden? ";
INPUT Eingabe$ USING "0-0",Ret%L,2
PRINT CHR$(27);"f"
Dnr%L=VAL(Eingabe$)
IF Dnr%L<=Dateiptr%L THEN
IF Dnr%L=Dateiptr%L THEN
Nbytes_Frei%L=Nbytes_Frei%L+Dateilaenge%L(Dnr%L-1)
ELSE
Gesamtlaenge%L=0
FOR I%L=Dnr%L TO Dateiptr%L-1
Gesamtlaenge%L=Gesamtlaenge%L+Dateilaenge%L(I%L)
NEXT
Datei_Start%L=Dyna_Ram_Start%L
FOR I%L=0 TO Dnr%L-2
Datei_Start%L=Datei_Start%L+Dateilaenge%L(I%L)
NEXT
MEMORY_MOVEB Datei_Start%L+Dateilaenge%L(Dnr%L-1),Gesamtlaenge%L TO Datei_Start%L
Nbytes_Frei%L=Nbytes_Frei%L+Dateilaenge%L(Dnr%L-1)
FOR I%L=Dnr%L-1 TO Dateiptr%L-1
Dateilaenge%L(I%L)=Dateilaenge%L(I%L+1)
Dateiname$(I%L)=Dateiname$(I%L+1)
NEXT
ENDIF
Dateiptr%L=Dateiptr%L-1
Clear_Screen
Dateien_Zeigen
ENDIF
ENDIF
UNTIL Scan_Code%L=1
RETURN
DEF PROC Dateien_Zeigen
FOR I%L=7 TO 7+Dateiptr%L-1
LOCATE I%L,2
PRINT "Datei Nr.: "; USING "###"; I%L-6;
PRINT " Dateiname: ";Dateiname$(I%L-7);
LOCATE I%L,40
PRINT " Dateilänge: "; USING "#######"; Dateilaenge%L(I%L-7);
PRINT " Bytes"
NEXT
RETURN
DEF PROC Clear_Screen
Wind_Get(Handle%L,4,X%L,Y%L,W%L,H%L)
FILL COLOR =0: PBOX X%L,Y%L,W%L,H%L
RETURN
DEF PROC Programm_Verlassen
Appl_Exit
END
RETURN
DEF PROC Init_Funktionkeys
FOR I%L=1 TO 10: KEY I%L=" ": NEXT
'Scan-codes
F1%L=$3B:F2%L=$3C:F3%L=$3D:F4%L=$3E:F5%L=$3F
F6%L=$40:F7%L=$41:F8%L=$42:F9%L=$43:F10%L=$44
RETURN
DEF PROC Scan_Keyboard
REPEAT
A$= INKEY$
IF A$<>"" THEN
Byte1%L= ASC( MID$ (A$,1,1))
Shift_R%L= BIT(0,Byte1%L)
Shift_L%L= BIT(1,Byte1%L)
Control%L= BIT(2,Byte1%L)
Alternate%L= BIT(3,Byte1%L)
Caps_Lock%L= BIT(4,Byte1%L)
Scan_Code%L= ASC( MID$(A$,2,1))
Ascii_Code%L= ASC( MID$(A$,4,1))
ENDIF
UNTIL A$<>""
RETURN
DEF PROC Create_Dyna_Ram(Ndateien_Max%L,Nbytes%L)
Nd_Max%L=Ndateien_Max%L-1
Dyna_Ram_Start%L= MEMORY(Nbytes%L)
Nbytes_Frei%L=Nbytes%L
DIM Dateilaenge%L(Nd_Max%L),Dateiname$(Nd_Max'%L)
Dateiptr®L=0
Dateilaenge%L(Dateiptr%L)=0
RETURN
DEF PROC Datei_Vorhanden(Datei$)
OPEN "F",15,Datei$,0
Datei_Vorhanden%L= NOT EOF(15)
CLOSE 15
IF Datei_Vorhanden%L THEN
OPEN "i",15,Datei$
Dateilaenge%L= LOF(15)
CLOSE 15
ENDIF
RETURN
DEF PROC Hinweis(Meldung$)
MOUSEON
FORM_ALERT (1,"[1]["+Meldung$+"][OK]",Wahl%)
MOUSEOFF
RETURN
LIBRARY CODE Gem