Eines der revolutionären Konstruktionsprinzipien des IBM Systems /38 war, dass auf diesem Rechner alle Daten in einer Datenbank gespeichert wurden. Selbst die Textverarbeitung basierte auf dieser Art der Datenspeicherung. Mittlerweile haben zwei Namenswechsel stattgefunden, das aktuelle System heißt i-Series, die Textverarbeitung ist verschwunden und dennoch haben andere Speicherungsformen bereits vor Jahren Einzug gefunden. Eines der revolutionären Konstruktionsprinzipien des IBM Systems /38 war, dass auf diesem Rechner alle Daten in einer Datenbank gespeichert wurden. Selbst die Textverarbeitung basierte auf dieser Art der Datenspeicherung. Mittlerweile haben zwei Namenswechsel stattgefunden, das aktuelle System heißt i-Series, die Textverarbeitung ist verschwunden und dennoch haben andere Speicherungsformen bereits vor Jahren Einzug gefunden.
Im Rahmen der Systemöffnung kam zu AS/400 Zeiten ein neues Dateisystem unter dem Namen Integrated Filesystem (IFS) hinzu. Heute könnte man die Welt von QSYS.LIB sogar als eine Unterabteilung des IFS ansehen, als Unterverzeichnis eines Unix Dateisystems. Dieser Schritt wurde von vielen als Integration von PC Dateien in die Welt der AS400 gesehen, entpuppt sich aber im heutigen Rückblick als Beginn der Vereinheitlichung und Konsolidierung der OS/400 und AIX Baureihen von IBM.
Unstrukturierte Dateien, sogenannte Streamfiles leben wieder auf und nehmen sowohl an Struktur als auch an Bedeutung zu. Längst gibt es formulierte Standards für Text-artige Dokumente, angefangen von Property Dateien, über HTML und XML bis hin zu BIFF8 dem Datenformat von Microsoft Office. Die Anforderung solche Formate in RPG Programmen zu verarbeiten, erfordert als erstes immer IFS Dateien überhaupt zu lesen und zu schreiben, was auf den ersten Blick nicht möglich erscheint, die Leseoperationen von RPG sind alle auf Datenbank abgestellt.
RPG programmieren mit C Funktionen
Basis der Verarbeitung von Datenströmen sind C-APIs, die ILE sei’s gedankt, auch aus RPG aufrufbar sind. Die erforderlichen Prototypen für den Aufruf der C Funktionen sind aus den C Prototypen in der Datei H in der Systembibliothek QSYSINC ableitbar. Für mein kleines Beispiel habe ich die Prototypen in der Teildatei IFSAPIS der Quelldatei QRPGLEH abgelegt.
/IF NOT DEFINED (IFSAPIS_QRPGLEH) /DEFINE IFSAPIS_QRPGLEH *----------------------------------------------------- D open_ PR 10I 0 EXTPROC('open') * return: filedescriptor * -1 = error D path * VALUE OPTIONS(*STRING) D openFlags 10I 0 VALUE D accessRights 10U 0 VALUE OPTIONS(*NOPASS) D codePage 10U 0 VALUE OPTIONS(*NOPASS) *----------------------------------------------------- D write_ PR 10I 0 EXTPROC('write') * return: length * -1 = error D fileDescriptor 10I 0 VALUE D dataPointer * VALUE D lengthOfData 10U 0 VALUE *----------------------------------------------------- Dclose_ PR 10I 0 EXTPROC('close') * return: 0 = OK * -1 = error D fileDescriptor 10I 0 VALUE /ENDIF
Es werden die C Funktionen open(), read(), write() und close() benötigt, die jeweils im Fehlerfall den Wert -1 zurück geben. Ich habe bei den RPG Namen der Prozeduren jeweils einen Unterstrich an die Namen angehängt, damit die Prozedurnamen nicht mit RPG Operations Codes in Konflikt geraten und der Freeformat Compiler dann Interpretationsprobleme bekommt. Die C APIs benutzen bei der Dateiverarbeitung zur Identifizierung einer Datei File Descriptoren. Beim öffnen einer Datei wird ein File Descriptor zurück gegeben, der dann bei allen Dateioperationen mit dieser Datei angegeben werden muss.
Beim öffnen einer Datei wird über open Flags gesteuert ob man nur lesen oder auch schreiben will, ob die Datei erstellt werden soll oder vorhanden ist, welche Codepage man beim erstellen haben will und ob die Daten als Text konvertiert werden sollen. Diese Flags lassen sich Unix like als Binärzahlen definieren und dann per Addition überlagern. Ich habe die benutzten Flags ebenfalls in die Copystrecke mit den Prototypen aufgenommen, eine vollständige Aufstellung findet man in der Referenz für die IFS APIs.
* Daten nur lesen DO_RDONLY C CONST(1) * Daten lesen und schreiben DO_RDWR C CONST(4) * Datei erstellen, falls nicht vorhanden DO_CREAT C CONST(8) * Codepage für Dateierstellung wird angegeben DO_CODEPAGE C CONST(8388608) * Datei enthält Textdaten, die konvertiert werden sollen DO_TEXTDATA C CONST(16777216) *----------------------------------------------------- * Ersteller bekommt alle Berechtigungen DS_IRWXU C CONST(448) * Gruppe bekommt alle Berechtigungen DS_IRWXG C CONST(56) * Rest der Welt bekommt alle Berechtigungen DS_IRWXO C CONST(7) *-----------------------------------------------------
Soll eine Datei neu erstellt werden, kann mit angegeben werden welche Zugriffsrechte für diese Datei vergeben werden sollen. Nach Unix Art wird hierbei für die Berechtigung des Erstellers, der Gruppe des Erstellers und für den Rest der Welt gelten soll. Für jeden dieser drei Einträge kann dann festgelegt werden ob Leseberechtigung, Schreibberechtigung und Ausführungsberechtigung vergeben werden sollen.
Das wird wieder über Flags gesteuert, die sich ebenfalls über Addition kombinieren lassen. Für die kompletten Berechtigungs Angaben sei wieder auf die Referenzliteratur verwiesen.
Im letzten optionalen Parameter der open() Funktion kann dann noch angegeben werden mit welcher Codepage ein Streamfile beim Neuerstellen angelegt werden soll. Diese Angabe ist dann wichtig, wenn Textdaten korrekt konvertiert werden sollen.
Bei den read() und write() Aufrufen muss der File Descriptor angegeben werden, damit die Runtime weiß welche der vorher geöffneten Dateien verarbeitet werden soll. Der Datentransfer erfolgt über zwei Parameter, einen Pointer auf das Feld mit den Daten und ein zweiter Parameter, der die Länge der Daten kennzeichnet, auf die der Pointer verweist.
Die Versuchung liegt zwar nahe jetzt einfach drauflos zu programmieren, aber mit ein wenig Überlegung kann man es vermeiden die technischen Details des Streamfile Handlings immer und immer wieder zu programmieren. Ich habe mich beim Design ein wenig von Java leiten lassen und dort Anleihe genommen für meine RPG Implementierung zur Verarbeitung von Datenströmen. RPG verfügt zwar bei weitem nicht über die Mächtigkeit und Einfachheit von Java, dort findet man eine Fülle an fertigen Komponenten für solche elementaren und weit schwierigere Aufgaben, aber man kann aus Java durchaus Ideen schöpfen wie man fehlende Komponenten in RPG selber schreibt.
Ein RPG FileOutputStream
Bevor es was zu lesen gibt, muss etwas geschrieben werden und alles was es in einem OS400 Programm zu schreiben gibt sind letztlich Textdaten. Binäre Datenströme gibt es nur bei Grafiken und bei ausführbaren Programmen und beides ist unter diesem Betriebssystem nicht Gegenstand des Schreibens. Bei Konzentration auf diese Problemstellung, benötigt ein Modul zum schreiben von Streamfiles nur wenige Prozeduren, um alles was mit schreiben zu tun hat zu erledigen.
Zuerst entwirft man sich die Prototypen für alle Prozeduren, die das Modul später exportieren soll, als Beschreibung für die Schnittstellen, die nach Fertigstellung aufgerufen werden können. Diesen Prototyp speichere ich in meiner Umgebung in einer extra Quelldatei QRPGLEH ab. In der allgemein üblichen QRPGLESRC befinden sich bei mir nur wandelbare Quellen. Als Namen für die Quelle mit den Prototypen kann bei dieser Vorgehensweise derselbe Name wie für die Programmquelle verwendet werden. Bei mir hat das Modul den Namen OUTSTREAM in Anlehnung an das Java Vorbild.
/IF NOT DEFINED (OUTSTREAM_QRPGLEH) /DEFINE OUTSTREAM_QRPGLEH /*===================================================================*/ /* OUTSTREAM */ /* schreiben Streamfile */ /* analog zu FileOutputStream */ /*-------------------------------------------------------------------*/ d setOutStream pr n EXTPROC('OUTSTREAM_setOutStream') D path 128a value d varying /*-------------------------------------------------------------------*/ D writeStream pr n EXTPROC('OUTSTREAM_writeStream') d buffer 1024a value d length 10i 0 value d options(*nopass) /*-------------------------------------------------------------------*/ D writeLnStream pr n EXTPROC('OUTSTREAM_writeLnStream') d buffer 1024a value d length 10i 0 value d options(*nopass) /*-------------------------------------------------------------------*/ D closeStream pr n EXTPROC('OUTSTREAM_closeStream') /*-------------------------------------------------------------------*/ /ENDIF
Die Prozedur setOutStream erwartet als Parameter die Pfadangabe des Streamfiles, für die Länge habe ich maximal 128 Byte vorgesehen. Die Prozedur prüft zunächst die Datei auf Existenz. Ist die Datei vorhanden, wird sie zum Schreiben geöffnet, der vorhandene Inhalt also ersetzt, andernfalls wird eine neue Datei erstellt und geöffnet. Ist der Vorgang insgesamt erfolgreich, wird der logische Wert true zurück gegeben, im Fehlerfall erfolgt die Rückgabe von false.
Zum schreiben sind zwei Prozeduren vorgesehen. Die erste heißt writeStream und erwartet als Parameter einen Datenpuffer von maximal 1024 Byte Länge und in einem zweiten Parameter die Länge der zu schreibenden Daten in Byte. Die zweite Prozedur hat dieselbe Schnittstelle und unterscheidet sich in der Funktionalität von der ersten darin, dass sie beim schreiben der Daten automatisch einen Zeilenwechsel anfügt. Der Erfolg der Operation wird bei beiden als Boolean Wert zurück gegeben.
Als letztes fehlt noch closeStream, das keine Parameter benötigt und den Erfolg der Operation zurückmeldet. Der Aufruf einer close Methode ist bei Streamfiles in vielen Betriebssystemen erforderlich, um sicher zu stellen, dass die Daten wirklich weggeschrieben worden sind und nicht noch in einem Puffer stehen.
Für die Existenzprüfung in der Prozedur setOutStream wird ein weiteres C-API verwendet, dessen Prototyp ebenfalls über Copystrecke eingebunden ist. Die entsprechenden Deklarationen sind in einer eigenen Teildatei ACCESS der Quelldatei QRPGLEH gespeichert.
Als Parameter wird der Pfad des zu prüfenden Objektes und der sogenannte Modus übergeben. Über den Modus wird gesteuert ob lediglich auf Existenz, oder auf Berechtigung geprüft wird, diese Prüfung funktioniert auch für Objekte in QSYS.LIB, verwendet allerdings keine übernommene Berechtigung des Programms (adopted authority). Die Flags für den Modus sind in der Header-Datei als Konstanten vordefiniert.
/IF NOT DEFINED (ACCESS_QRPGLEH) /DEFINE ACCESS_QRPGLEH /* Test for read permission D R_OK C CONST(4) /* Test for write permission D W_OK C const(2) /* Test for execute or search permission D X_OK C const(1) /* Test for existence of a file D F_OK C const(0) *----------------------------------------------------- D access PR 10i 0 extproc('access') * return: 0 = command successfull * -1 = access not permitted errno is set * access does not use adopted authority D dPath * value D options(*STRING) D amode 10i 0 value /ENDIF
Die Implementierungen der Prozeduren wird im zweiten Teil des Artikels im folgenden Heft beschrieben.
Den Autor Dieter Bender erreichen Sie unter: dieter.bender@MidrangeMagazin.de oder Redaktion@MidrangeMagazin.de