Im ersten Teil des Artikels haben wir die Prototypen beschrieben, deren Implementierung wir in diesem Teil beschreiben. In der Quelle der Implementierung sieht man als Erstes, dass ich für dieses Modul auch ein eigenes Serviceprogramm erstelle. Dieses Vorgehen sorgt für kurze Ladezeiten und wenig Probleme mit geänderten Signaturen von Serviceprogrammen. Die Programmerstellung ist in der Quelle als Kommentar hinterlegt, kann aber auch von einem Pre-Compiler direkt ausgewertet werden. Solche Utility wurde in einem früheren Heft des Magazins beschrieben, kann aber auch noch als Freeware von der Webpage des Autors (www.bender-dv.de) geladen werden. Für die Programmexporte wird *ALL spezifiziert, bei Änderungen von Schnittstellen wird dann neu gebunden, wenn man nicht gleich auf statisches Binden verzichtet (ebenfalls in einem früheren Artikel beschrieben) und erst zur Laufzeit bindet.

Das Binding Directory QC2LE wird verwendet, um die verwendeten C-Funktionen beim Binden zu lokalisieren.

     H nomain
     D*B   CRTRPGMOD OUTSTREAM
     D*B+       DBGVIEW(*SOURCE)
     D*B   CRTSRVPGM OUTSTREAM
     D*B+       EXPORT(*ALL)
     D*B+       BNDDIR(QC2LE)
     /*-------------------------------------------------------------------*/
     /* Prototypen Exporte
      /COPY QRPGLEH,OUTSTREAM
     /*-------------------------------------------------------------------*/
     /* Prototypen Import
      /COPY QRPGLEH,ACCESS
      /COPY QRPGLEH,IFSAPIS
     /*-------------------------------------------------------------------*/
     /* lokale Prototypen
     d openFile        pr              n
     /*-------------------------------------------------------------------*/
      * Konstanten
     D TRUE            C                   *ON
     D FALSE           C                   *OFF
     D CRLF            C                   x'0D25'
     /*-------------------------------------------------------------------*/
     /* Zustands Variablen
     /*-------------------------------------------------------------------*/
     d path            s            128a
     d fileD           s             10i 0 inz(-1)
     /*-------------------------------------------------------------------*/

Im globalen Deklarationsteil des Moduls werden die erforderlichen Prototypen als Copy-Strecken eingebunden. Als lokale Prozedur ist noch openFile vorgesehen. In den konstanten Deklarationen sind TRUE und FALSE definiert, die ich der Verwendung der Begriffe *ON und *OFF vorziehe, da diese mich zu sehr an Bezugszahlen erinnern. Die letzte Konstante CRLF enthält die Zeichen für Carriage Return und Line feed und findet als Zeilentrenner Verwendung.
Als globale Variablen sind die Zustandsgrößen path für den Pfad und fileD zur Aufnahme des File Descriptors deklariert. Letzterer wird mit -1 vorbelegt, was einer nicht geöffneten Datei entspricht. Globale C-Bestimmungen sind in diesem Modul nicht vorgesehen, es ist in einer H-Zeile nomain eingetragen.

Für die Existenzprüfung in der Prozedur setOutStream wird eine weitere C-API verwendet, deren Prototyp ebenfalls über Copy-Strecke eingebunden ist. Die entsprechenden Deklarationen sind in einer eigenen Teildatei ACCESS der Quelldatei QRPGLEH gespeichert.
Als Parameter werden der Pfad des zu prüfenden Objektes und der so genannte 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

In der Implementierung der Prozedur setOutStream wird der als Parameter übernommene Pfad als Erstes in die globale Zustandsvariable übernommen. Dann wird geprüft, ob die angeforderte Datei existiert. Wenn access nicht den Wert 0 zurückgibt, ist die Datei nicht vorhanden und es wird versucht, sie mit C-API open zu erstellen.

Hierzu werden die Flags zum Erstellen, für die Angabe der Codepage und zum Schreiben/Lesen übergeben. Im dritten Parameter wird über Flags festgelegt, dass sowohl der Benutzer selber als auch dessen Gruppe sowie der Rest der Welt alle Berechtigungen an der neu zu erstellenden Datei bekommen. Im vierten Parameter wird mitgeteilt, dass die CodePage 273 verwendet werden soll. Nach dem Erstellen der Datei wird sie sofort wieder geschlossen; dieser Kunstkniff ist erforderlich, um korrekte Zeichenkonvertierung beim späteren Schreiben sicherzustellen.

     P setOutStream    b                   export
     D setOutStream    pi              n
     D outPath                      128a   varying
     d                                     value
     D result          s               n
     d CodePage        s             10i 0 INZ(273)
      /free
                   path = outPath;
                   if access(%trim(path):F_OK) <> 0;
                      fileD = open_( %trim(path)
                                   : O_CREAT + O_CODEPAGE + O_RDWR
                                   : S_IRWXU + S_IRWXG + S_IRWXO
                                   : CodePage
                                   );
                      close_(FileD);
                   endif;
                      result = openFile();
                   return result;
      /end-free
     P setOutStream    E

Nachdem die Datei nun in jedem Fall existieren sollte, wird die interne Prozedur openFile zum Öffnen der Datei aufgerufen. Meldet diese, dass das Öffnen gelungen ist, wird der Erfolg an die aufrufende Ebene signalisiert.

In der Quelle der Prozedur openFile wird die Datei erneut geöffnet, diesmal mit den Flags für Lesen/Schreiben und Konvertierung als Text. Jetzt wird abgefragt, ob das Öffnen erfolgreich war und das Resultat hochgereicht werden soll.

     P openFile        b
     D openFile        pi              n
     D result          s               n   inz(TRUE)
      /free
                   fileD = open_( %trim(path)
                                : O_RDWR + O_TEXTDATA
                                );
                   if filed = -1;
                      result = FALSE;
                   endif;
                   return result;
      /end-free
     P openFile        E

Die optionalen Parameter für die Zugriffsberechtigungen und die CCSID werden jetzt nicht benötigt und deshalb einfach weggelassen.
Der File Descriptor selber ist als globale Variable deklariert und damit zustandsbehaftet, also bei späteren Aufrufen anderen Prozeduren noch bekannt.
Zu Beginn von writeStream wird geprüft, ob der Parameter bufLen gefüllt ist, ansonsten wird dieser mit der Länge des rechts von Blanks bereinigten Eingabepuffers belegt; es braucht also nur eine Länge übergeben werden, wenn Blanks am Ende des Puffers in die Datei geschrieben werden soll.

     P writeStream     b                   export
     D writeStream     pi              n
     d buffer                      1024a   value
     d bufLen                        10i 0 value
     d                                     options(*nopass)
     d result          s               n   inz(TRUE)
     d bufferP         s               *
     d length                        10i 0
      /free
                   if %parms() = 1;                 
                      length = %len(%trimr(buffer));
                   else;                            
                      length = buflen;              
                   endif;                           
                   bufferP = %addr(buffer);
                   if write_(fileD:bufferP:length) = -1;
                       result = FALSE;
                   endif;
                   return result;
      /end-free
     P writeStream     E

Die Prozedur writeStream verwendet jetzt den bereits erstellten File Descriptor aus dem globalen Zustand, setzt die Zeichenketten-Variable in einem Pointer um und ruft C-API write zum Schreiben in das Streamfile auf.
Beim zeilenweisen Schreiben mit writeLnStream wird der Längenparameter analog zu writeStream ermittelt, falls kein Wert übergeben wurde.

     P writeLnStream   b                   export
     D writeLnStream   pi              n
     d buffer                      1024a   value
     d bufLen                        10i 0 value
     d                                     options(*nopass)
     d result          s               n   inz(TRUE)
     d buff            s           1026a
     d buffP           s               *   inz(%addr(buff))
     d length                        10i 0
      /free
                   if %parms() = 1;                 
                      length = %len(%trimr(buffer));
                   else;                            
                      length = buflen;              
                   endif;                           
                   buff = %subst(buffer:1:length) + CRLF;
                   if write_(fileD:buffP:length +2) = -1;
                       result = FALSE;
                   endif;
                   return result;
      /end-free
     P writeLnStream   E

In dem obigen Code-Ausschnitt sieht man, dass writeLnStream an die Daten lediglich den Zeilenwechsel anhängt. Hierzu hatten wir uns die Konstante CRLF deklariert und die Parameter dann an die bereits besprochene Prozedur writeStream weiter gegeben.
Man sollte sich angewöhnen, bei der Ausgabe in Streamfiles immer eine close-Methode vorzusehen und diese auch zu benutzen. Je nach Betriebssystem kann ansonsten Datenverlust auftreten. Unter Windows sollte man davon ausgehen, dass bei einem vergessenen close die Datei komplett weg ist; unter Unix ist sie vielleicht in Ordnung, unter OS/400 wird das Betriebssystem das irgendwie automatisch in Ordnung bringen. Aber sicher ist sicher, auch unter OS/400.

     P closeStream     b                   export
     D closeStream     pi              n
     d result          s               n   inz(TRUE)
      /free
                   if close_(FileD) <> 0;
                      result = FALSE;
                   endif;
                   fileD = -1;
                   return result;
      /end-free
     P closeStream     E

Ich habe meine close-Methode closeStream genannt, rufe dort lediglich C_API close auf und setze anschließend die globale Variable fileD, die sich den aktuellen File Descriptor merkt, auf den Wert -1. Anschließend wird die Aktion als erfolgreich beendet. Wenn hier was schief geht, dann ist die Datei ebenfalls geschlossen.

OutStream am Beispiel der Erzeugung einer Property-Datei

In Java-Anwendungen und in der Welt von Unix werden häufig so genannte Property Files für Konfigurationsparameter genutzt. In solch einer Streamfile werden Namensparameter abgelegt. Dazu wird der Name des Parameters gefolgt von einem Gleichheitszeichen und dem zugehörigen Wert in eine Textdatei geschrieben. Kommentare werden mit einem Hash-Zeichen, manche sagen auch „Knast“ zu dem Doppelkreuzzeichen #, zu Beginn der Zeile markiert.

In einem kleinen Programmausschnitt sehen wir, wie mit dem Serviceprogramm OutStream solche Streamfiles aus RPG geschrieben werden können.

Bei der Erstellung des Programms muss das Serviceprogramm OUTSTREAM mit gebunden werden. Ich bevorzuge die explizite Angabe direkt im Erstellungsbefehl gegenüber der Verwendung von Binder-Verzeichnissen – da sieht man auf einem Blick, was man hat. Selbstverständlich braucht man dann einen Automatismus, der sicherstellt, dass die Create-Befehle irgendwo hinterlegt werden können und reproduzierbar ausgeführt werden.

Die Prototypen für die gebundenen Prozeduren aus dem Modul OUTSTREAM werden über die Copy-Anweisung eingebunden.

     D*B  CRTRPGMOD TESTOU
     D*B+    DBGVIEW(*SOURCE)
     D*B  CRTPGM TESTOUT
     D*B+    ACTGRP(TESTOUT)
     D*B+    BNDSRVPGM(OUTSTREAM)
     /* Prototypen verwendete
      /COPY QRPGLEH,OUTSTREAM
     /*-------------------------------------------------------------------*/
      * Main
      /free
                   setOutStream('/home/bender/test,properties');
                      writeLnStream('#Visiten Karte Autor');
                      writeLnStream('vorName=Dieter');
                      writeLnStream('nachName=Bender');
                      writeLnStream('Homepage=www.bender-dv.de');
                      closeStream();
                   return;
      /end-free

Das Programm braucht sich nicht mit irgendwelchen C-Internas zu beschäftigen; das erledigt alles das Serviceprogramm OutStream. Das Schreiben in Streamfiles ist damit einfacher als das Schreiben in die Datenbank; anders darf das auch nicht sein.

 ************** Datenanfang ****************
#Visiten Karte Autor                        
vorName=Dieter                              
nachName=Bender                             
Homepage=www.bender-dv.de                   
 ************ Datenende ********************

Die Ausgabe des Programms erfolgt in mein Benutzerverzeichnis, das unterhalb des dafür vorgesehenen IFS-Verzeichnisses /home liegt und meinen Namen trägt. Die Datei heißt dort test.properties, ganz so, wie es das Programm angefordert hat.

Im nächsten Teil des Artikels werden wir uns dann mit dem Lesen von Streamfiles beschäftigen.

Den Autor Dieter Bender erreichen Sie unter dieter.bender@midrangemagazin.de