Auf den ersten Blick sieht es so aus, als ob es nicht möglich ist, ILE-Prozeduren aus RPG dynamisch aufzurufen. Im Referenzhandbuch von RPG steht für alle ILE-Versionen, dass Prozeduren unter Verwendung eines Prototyps oder mit CALLB aufgerufen werden müssen. Der Name der aufgerufenen Prozedur muss als Literal oder Konstante übergeben werden beziehungsweise im Prototyp auf diese Art und Weise eingetragen werden. Als einzige Ausnahme wird die Verwendung eines Procedure Pointers zugelassen. Bei der Variante mit diesem Aufruf-Mechanismus muss dem Procedure Pointer vor dem Aufruf ein gültiger Wert zugewiesen sein; dieser muss also auf eine Prozedur in einem bereits aktivierten Modul verweisen. Innerhalb eines RPG-Programms kann dies nur durch die Built in Function %PADDR geschehen. Diese hat einen Parameter, in dem der Name der Prozedur spezifiziert werden muss – ebenfalls wieder als Literal und zur Bindezeit statisch auflösbar. Man kann sich also drehen und wenden wie man will, die Prozedur muss zur Umwandlungszeit des Programms, das die Zuweisung vornimmt, bekannt sein.

Procedure Pointer dürfen zwar als Parameter von Programm zu Programm weitergegeben werden, aber das verlagert das Problem lediglich aus einem RPG-Programm in ein anderes. Die aufzurufende Prozedur muss in einem der RPG-Programme zur Bindezeit statisch hinterlegt werden. Diese Lage erscheint auf den ersten Blick logisch und einsichtig. Wenn man die Vorteile des statischen Bindens in Anspruch nehmen will – wie Prüfungen des Compilers und bessere Performance – muss man eben den Preis der geringeren Flexibilität dafür entrichten.

Flexible Benutzeroberflächen erfordern flexible Programmumgebungen
Bevor man ein technisches Problem tiefergehend untersucht, ob es eine Lösung dafür gibt, sollte man sich immer damit beschäftigen, ob man so was überhaupt benötigt, sonst bleibt man auf der Ebene technischer Spielerei stehen. Bei der Verwendung dynamischer Aufrufe hat man die Auswahl, ob man den Programmnamen als Literal angibt oder ob man ihn in eine Variable stellt. Bei der Verwendung einer Variablen für den Aufruf kann man den Namen des Programms aus einer Datei lesen. Vielfach wird das für flexible Benutzersteuerungen eingesetzt. Auf diese Art und Weise kann man Programmabläufe sogar von Administratoren der Fachabteilungen zusammenstellen lassen, die die entsprechenden Daten in Anwendungssteuerungsdateien selber pflegen können.

Beim Übergang zu ILE RPG wurden diese Teile der Anwendung dann meist so belassen, wie sie in OPM entstanden waren, da man eben keine Prozeduraufrufe so ohne weiteres aus einer Datei lesen kann, weil dieses Lesen erst zur Laufzeit passiert. Damit sind viele Techniken fortgeschrittener ILE-Programmierung nur bedingt einsetzbar, wenn man die Flexibilität der Anwendung behalten will. Ich denke da insbesondere daran, dass man für dynamische Aufrufe immer nur einen Einstiegspunkt hat und damit auch nur eine einzige Parameterleiste.

Will man nun die Vorteile dynamischer Anwendungen mit fortschrittlichen ILE-Techniken verbinden, so scheitert man hier ausgerechnet am statischen Binden der modernen Anwendungen, zumindest solange man die Eingangs geschilderte Problematik nicht in den Griff bekommt.

Was RPG nicht kann, kann vielleicht ein API?

Die eigentliche Beschränkung, an der man im ersten Ansatz scheitert, ist die Tatsache, dass man innerhalb von RPG keine direkte Möglichkeit hat, den Procdedure Pointer mit einem gültigen Wert einer aktivierten Prozedur zu belegen, ohne diese statisch im Code eines RPG-Programms zu hinterlegen. Und genau dafür gibt es über APIs einen Weg, der dies ermöglicht. Es gibt dort sogar noch eine weitere Lösungsmöglichkeit, nämlich den direkten Aufruf einer Prozedur mittels des APIs QZRUCLSP (Call Service Program Pocedure). Die Variante mit der Verwendung des Procedure Pointers hat den Vorteil, dass für die Aufrufe der Prozeduren Prototypen verwendet werden können, obwohl man die Prozeduren selber aus einer Datei erst zur Laufzeit ausliest.

Für die vorgesehene Lösung werden insgesamt vier APIs benötigt, bis man einen gültigen Procedure Pointer für einen Prozedur-Namen bekommt, den man soeben aus einer Datei gelesen haben könnte. Im ersten Schritt holt man sich einen System-Pointer auf das Serviceprogramm, das das Modul der Prozedur enthält. Hierzu wird vorbereitend das Object API QLICVTTP (Convert Type) aufgerufen und anschließend mit dem Machine Interface-Aufruf RSVLSP (Resolve System Pointer) der Pointer vom Betriebssystem aufgelöst.

Nachdem man diesen Pointer hat, holt man sich eine Referenz auf die Aktivierung des Serviceprogramms. Bei diesem Schritt wird selbiges auch aktiviert, soweit dies erforderlich sein sollte. Die Arbeit selber erledigt das Program and CL Command API QleActBndPgm (Activate Bound Program). Mit einem weiteren Systemprogramm aus demselben Bereich wird schließlich der Procedure Pointer geholt. Diese Aufgabe erledigt das API QleGetExp (Get Export). (Siehe auch Abbildung 1)

Abbildung 1

Nach all diesen Vorarbeiten geht es dann wieder innerhalb des RPG-Programms mit dem eigentlichen Aufruf der Prozedur unter Verwendung des Procedure Pointers weiter. Am besten lagert man sich den komplizierten technischen Teil in ein eigenes Modul aus, das den Namen der Prozedur und des Serviceprogramms als Parameter übergeben bekommt und den Procedure Pointer zurück liefert. Der Prototyp dieser Prozedur ist in Abbildung 2 zu sehen.

Abbildung 2

Für den Namen der Prozedur im Parameter habe ich 32 Byte vorgesehen. Hier kann man auch eine größere Länge wählen oder den Parameter mit variabler Länge vorsehen. Bevor wir uns die komplette Implementierung ansehen, wollen wir uns zunächst mit der Verwendung der APIs auseinandersetzen.

Systempointer auf OS/400-Objekte

Für die später vorgesehen Aktivierung des Serviceprogramms, das die aufzurufende Prozedur beinhaltet, wird zunächst ein System-Pointer auf das Objekt der Art *SRVPGM benötigt. Das Machine Interface-Programm will dazu die Objektart in verschlüsselter Form haben. Für ein Serviceprogramm könnte man diesen Wert zwar auch hart verdrahten, da dieser aber nicht offiziell dokumentiert ist, sollte man lieber den Weg über das entsprechende API gehen.

Im Prototyp in der Abbildung 3 ist zu sehen, dass das API noch zu den älteren, dynamisch aufzurufenden gehört; erkennbar ist das am EXTPGM-Schlüsselwort in der ersten Deklarationszeile. Der Aufrufname wird im Prototyp aus Gründen der Lesbarkeit in ConvertObjectTypeHex getauscht. Hier wäre durchaus ein deutscher Name möglich, aber die lesbaren Bestandteile der Programmiersprachen sind ohnehin eher englisch oder daran angelehnt.

Abbildung 3

Im ersten Parameter legt man fest, in welche Richtung man umwandeln möchte, der zweite Parameter ist für den Klartext der Objektart vorgesehen und der dritte Parameter für den Hexwert. Bei der Angabe von *SYMTOHEX im ersten Parameter übergibt man den Klartext im zweiten Parameter und bekommt im dritten den Hexwert zurück. Bei *HEXTOSYM übergibt man den Hexwert im dritten Parameter und bekommt das Resultat im Klartext im zweiten Parameter zurück. Der vierte Parameter ist für eventuelle Rückgabe von Fehlerinformationen vorgesehen.

Für die Rückgabe der Fehlerinformationen ist eine Datenstruktur vorgesehen, die in Abbildung 4 zu sehen ist und für mehrere APIs verwendet wird. Die Version aus der QSYSINC ist leider unvollständig und schwer lesbar, besser man überträgt sich diese Sachen selber aus der C-Umgebung der AS/400. Diese Datenstruktur ist in einer eigenen Copy-Strecke hinterlegt, die auch in den Header mit dem Prototyp des APIs eingebunden wird. An dieser Stelle wird sichtbar, dass der Schutz gegen Doppeltkopieren durch die Präprozessor-Anweisungen unbedingt erforderlich ist, um Umwandlungsfehler zu vermeiden.

Abbildung 4

In diese Fehlerdatenstruktur werden die Informationen eingestellt, die bei der Ausführung eventuell nicht funktioniert haben. Es geht um die Frage: Welcher Fehler aufgetreten ist? Der Aufruf des APIs könnte auch ohne Prototyp mit einem CALL-Befehl erfolgen, eleganter ist aber die Variante mit CALLP unter Verwendung eines Prototyps.
Jetzt kommt die komplizierteste Aufgabe: das Holen eines System-Pointers auf das Serviceprogramm. Kompliziert ist es deshalb, weil es nicht so naheliegend ist, wohin man diesen in einem RPG-Programm stellen soll. Zudem ist nur eine Machine Interface Routine dazu in der Lage, diese Aufgabe zu vollziehen. Speichern kann man sich einen System-Pointer in einem Procedure Pointer; wie man diesen deklariert, werden wir noch betrachten. Machine Interface-Funktionen haben ein relativ starres Regelwerk bezüglich Fehlersituationen. Die einfachere Strategie ist es hier, im Vorfeld zu prüfen und damit Fehlersituationen aus dem Weg zu gehen. In unserem kleinen Beispielprogramm ist der Fehler eines nicht vorhandenen Serviceprogramms oder einer nicht ausreichenden Berechtigung nicht berücksichtigt. Hier wäre eine Erweiterung mit vorherigem CHKOBJ oder access() sinnvoll.

In der Abbildung 5 sind die Parameter der MI-Funktion beschrieben. In der ersten Zeile der Deklaration des Prototyps sieht man bei der Beschreibung des Rückgabetyps der Funktion, wie ein Procedure Pointer deklariert wird; für die interne Art wird ein Sternchen (*) eingetragen und im Schlüsselwort beschreibt der Eintrag PROCPTR, um welche Art von Pointer es sich handelt. In der folgenden Schlüsselwortzeile erkennt man an EXTPROC den gebundenen Aufruf; der Parameter ‚rslvsp’ muss in Hochkommata und in Kleinbuchstaben eingetragen werden, sonst gibt es Probleme beim Binden.
Der erste Übergabeparameter erwartet die Objektart in Hexadezimaldarstellung, wie wir sie uns von QLICVTTP besorgt haben; es folgen dann Objektname und Bibliothek im Klartext. Die Angabe von OPTIONS(*STRING) sorgt für die Aufbereitung der Parameter von RPG nach C-üblichen so genannten Null-terminated-Strings. Für den letzten Parameter Authoritie übergeben wir den Wert 0. Die möglichen Werte und deren Bedeutung ergeben sich aus den Deklarationen der Konstanten in der Header-Datei.

Abbildung 5

Aktivierung eines Serviceprogramms

Normalerweise braucht man sich um die Aktivierung von Serviceprogrammen nicht zu kümmern, aber in unserem besonderen Fall kann es durchaus sein, dass das Serviceprogramm unseres Moduls noch nicht aktiviert ist. Zudem brauchen wir eine Verbindung zum aktivierten Programm. Beide Aufgabenstellungen leistet das bindbare API QleActBndPgm für uns.

Für dieses API und das folgende zum Holen des Procedure Pointers sind Prototypen unumgänglich, da es sich um gebundene Funktionen handelt, Die Dokumentation in der Referenzliteratur beschreibt zwar die Parameter, enthält aber keine Prototypen. Diese findet man zumeist in der Bibliothek QSYSINC, soweit man diese installiert hat. Auf die Belange der RPG-Programmierer wird jedoch wenig Rücksicht genommen. C-Prototypen findet man in der Datei H, für unsere beiden Funktionen in der Teildatei QLEAWI. Diese muss man dann allerdings noch nach RPG übertragen.

Der Quelltext der noch fehlenden Header-Datei in Abbildung 6 gliedert sich in 4 Abschnitte. Im ersten Abschnitt werden Konstanten für die beiden APIs definiert, die für Parameter Verwendung finden. Das dort definierte Flag ist für uns nicht wichtig, da es uns nicht interessiert, ob das Serviceprogramm vorher schon aktiv war. Die weiteren Konstanten werden im nächsten Kapitel beschrieben.

Im zweiten Abschnitt wird eine Datenstruktur Qle_ABP_Info_t beschrieben, die für unser Programm nicht weiter wichtig ist, da die so genannte Activation Mark noch mal als separater Parameter vorkommt. Bei der Übertragung von C nach RPG wird ein wesentlicher Unterschied beider Sprachen sichtbar. Die Typ-Definitionen in C lassen sich nicht nach RPG übertragen, da es dort keine Möglichkeiten gibt Datentypen zu definieren. Am ehesten würden externe Datenstrukturen passen, diese kennen aber zum Beispiel keine Pointer. Die Übertragung in Datenstrukturen ist ein Kompromiss, der allerdings Randprobleme bei Namensgleichheiten von Feldern erzeugen kann.
Im dritten Abschnitt findet man den Prototyp für QleActBndPgm. Im Rückgabewert wird zurück gegeben, ob das Programm bereits aktiviert war oder nicht. Da uns dieser Wert nicht sonderlich interessiert, können wir sowohl in einer Zuweisung als auch mit CALLP aufrufen. Wir nutzen diesen Prototyp wieder, um einen sprechenderen Namen zu vergeben und können später ActivateProgram verwenden. Als erster Übergabeparameter wird der System-Pointer auf das Programm übergeben. Im zweiten Parameter kommt in einer Integer-Zahl die so genannte Activation Mark, ein Handle für die Aktivierung zurück. In den Parametern drei und vier kommen weitere Informationen in der bereits erwähnten Datenstruktur zurück. Für die Fehlerinformationen im fünften und letzten Parameter wird die bereits bekannte Datenstruktur ErrorDS verwendet.

Zeige mir, wer Du bist und ich rufe Dich auf

Im letzten Abschnitt der Quelle aus der Abbildung 6 ist der Prototyp des gebundenen APIs QleGetExp beschrieben, dessen Namen wir gleich in der Deklaration in GetExport ändern. Dieses Systemprogramm liefert nicht nur Zeiger auf Prozeduren aktivierter Programme, sondern auch exportierte Variablen zurück, soweit Module solche aufzuweisen haben.

Abbildung 6

Im ersten Parameter muss ein Verweis auf das aktivierte Programm übergeben werden, den wir uns ja bereits mit den bisher beschriebenen Möglichkeiten besorgen konnten. Nun wird entweder im zweiten Parameter die Hausnummer des Exports übergeben oder im dritten und vierten Parameter der Name des Exportes und die Länge des Namens; wir werden uns für den Namen entscheiden und füllen dann die Hausnummer mit 0. Wir übergeben im fünften Parameter den Procedure Pointer, der dann später auf unsere Prozedur zeigt. Die Übergabe eines Pointers auf einen Pointer by Value in C ist nicht so ganz einfach auf die Übergabe by Reference in RPG zu übertragen, aber so wie es aus der Quelle in Abbildung 6 hervorgeht (mit CONST), funktioniert es für uns. Der nächste Parameter enthält später die Information, ob eine Prozedur, eine exportierte Variable oder nichts gefunden wurde – oder ob eventuell kein Zugriff möglich war. Der letzte Parameter für die Rückgabe von Fehlerinformationen kommt uns bereits bekannt vor.
Diese Fehlerparameter sollten übrigens immer mit der Angabe NOOPT vom Optimieren durch den Compiler geschützt werden, damit die Rückgabe auch immer sofort synchron erfolgt. Die Werte für die Rückgabe des vorletzten Parameters sind wieder in Form von Konstanten-Deklarationen zu Beginn der Quelle beschrieben. Diese Technik wird verwendet, um die Lesbarkeit von Programmen zu verbessern.

Modern, dynamisch und hoffentlich schnell und erfolgreich

In Abwandlung einer sattsam bekannten Klassifizierung geht es jetzt an die Umsetzung und Einbindung der in Prototypen beschriebenen Systemprogramme. Die Quelle in Abbildung 7 hat nichts dramatisches mehr an sich. Nachdem wesentliche Teile der Arbeit bereits mit der Erstellung der benötigten Prototypen erledigt ist, geht der Rest (fast) wie von selber.

Abbildung 7

Am Schlüsselwort NOMAIN in der H Zeile ist bereits zu erkennen, dass dieses Modul als Serviceprogramm erstellt werden soll, also später in das verwendende Programm dynamisch gebunden wird. Die Erstellungsbefehle sind als Kommentar in die Quelle eingetragen und könnten auch von einem kleinen Präprozessor aus gelesen werden, wie er in einem der letzten Hefte beschrieben wurde, der die Erstellung automatisiert.
Die folgenden vier /COPY-Anweisungen binden die bereits besprochenen Dateien mit den Prototypen, Konstanten und (Pseudo-) Typdeklarationen – in Form von Datenstrukturen – ein. Das Modul hat dann eine einzige Prozedur GetProcPointer, deren Prozedur-Interface in den PI-Bestimmungen lediglich den Prototyp wiederholt. Der Compiler prüft diese Schnittstellenbeschreibung dann gegen den Prototyp, der bei uns aus der Copy-Strecke kommt.

Die Prozedur wird mit Übergabe der Namen eines Serviceprogramms und einer Prozedur aus diesem Serviceprogramm aufgerufen und liefert einen Procedure Pointer auf die gewünschte Prozedur im aktivierten Serviceprogramm zurück. Gelingt dies wegen eines Fehlers nicht, wird ein Null-Pointer zurück gegeben.

Die gesamte Prozedur besteht lediglich aus den Aufrufen der vier eingangs besprochenen APIs und der Deklaration einiger Variablen, die für die Rückgabe und die Bedienung von Aufrufparametern der Systemroutinen benötigt werden. Bei der Deklaration der Datenstruktur Error ist es wichtig, dass man die neuere Variante mit LIKEDS und der neuen Möglichkeit der Initialisierung mit INZ(*LIKEDS) verwendet. Wenn man noch unter Version V4 des Betriebssystems arbeiten muss, empfiehlt es sich hier, statt einer LIKE-Definition auf die Datenstruktur mit einer direkt einkopierten Struktur zu arbeiten, da es sonst Probleme wegen fehlerhafter Initialisierung kommen kann; zudem kommt man ansonsten auch nicht an die Unterfelder.

Als Erstes wird durch den Aufruf des Prototyps ConvertObjTypeHex der interne hexadezimale Schlüsselwert für die Objektart *SRVPGM ermittelt. Dazu wird die gewünschte Art der Konvertierung mit *SYMTOHEX als Literal und der Wert *SRVPGM in der Variable CharType übergeben. Das Programmfeld HexType dient als Rückgabeparameter und die Datenstruktur Error nimmt im Bedarfsfall die Fehlerinformationen auf und sorgt zudem dafür, dass keine Programmausnahme auftritt. Es wird zwar unter allen Releases, die ich untersucht habe, der Hexwert x’0203’ zurück gegeben, aber ich habe diesen Wert nirgends dokumentiert gefunden, sodass mir dieser Aufruf stabiler erscheint.

Der Aufruf GetSysPointer muss in einer Zuweisung erfolgen, damit man an den Rückgabewert der MI-Funktion überhaupt dran kommt. Diese Zeile erinnert eher an C als an RPG, insbesondere, wenn man sich das eigentlich überflüssige EVAL wegdenkt. Der soeben vorher ermittelte Hexwert wird ebenso übergeben, wie der Name des Serviceprogramms. Als Bibliothek wird *LIBL übergeben, selbstredend wäre hier auch eine qualifizierte Angabe möglich gewesen. Der konstante Wert AUTH_NONE ist in der Copy-Strecke des Prototyps mit deklariert; die Kenntnis des Inhalts ist für das Verständnis nicht erforderlich. Der System-Pointer auf das Serviceprogramm wird dann in die Variable ServicePgmP eingestellt.

An dieser Stelle ist kein Error-Handling für die MI-Funktion möglich, denn diese müsste über einen so genannten Condition Handler erfolgen. An dieser Stelle wäre es wünschenswert, dass die EVAL-Anweisung den Mechanismus mit %error unterstützen würde, was leider nicht der Fall ist. Alternativ wäre es auch möglich vor dem Aufruf von GetSysPointer zu prüfen, ob das Serviceprogramm existiert. Bei den beiden nächsten Aufrufen ist die Lage wieder einfacher, da die verwendeten APIs sich im Fehlerfall kalkulierter verhalten; bei zweckentsprechender Bedienung des vorgesehenen Fehlermechanismus werden keine Ausnahmebedingungen an den Aufrufer gereicht.
Dem Aufruf von ActivateProgram wird der soeben ermittelte Systempointer auf das Serviceprogramm übergeben. Im Rückgabeparameter ActivateResult wird mitgeteilt, ob das Programm vorher schon aktiviert war. Die übrigen Parameter dienen ebenfalls der Rückgabe von Informationen, von denen für uns allerdings nur Activation Mark von Interesse ist; die anderen werden in unserem Fall nur aus formalen Gründen benötigt und um dem minimalen Fehlerhandling zu genügen.

Im letzten wichtigen Schritt wird nun die Activation Mark an den Prototyp-Aufruf GetExport weiter gereicht. Im zweiten Parameter übergeben wir 0 zum Zeichen dafür, dass wir den Export nach Name haben wollen und nicht nach Hausnummer. Folgerichtig müssen die beiden nächsten Parameter mit der Namenslänge und dem Namen des Exports – sprich der zu aktivierenden Prozedur – gefüllt werden. Die drei weiteren Parameter sind Rückgabeparameter, von denen wir Error wieder nur zur Besänftigung benutzen.

Von Interesse sind für uns die Rückgaben in den Variablen „Ergebnis“ und „ExportTyp“. Letztere weist nun die Fehlerinformation auf, die für uns von Belang ist. Hier steht nämlich drin, ob es geklappt hat. Und wenn es geklappt hat, sehen wir, welche Art von Export gefunden wurde. Immer wenn hier keine Prozedur lokalisiert werden konnte, geben wir *NULL zurück, da wir mit einem Variablen-Export nichts anfangen können. Im Falle des Erfolges haben wir nun den begehrten Procedure Pointer auf die spätestens nun auch aktivierte Prozedur.

Einbindung der Prozedur ins Programm

In Abbildung 8 ist an einem Beispiel zu sehen, wie man denn nun eine Prozedur aufrufen kann, ohne sie in die Anwendung zu binden. Zunächst einmal muss der Prototyp von PROCP4NAME eingebunden werden, damit unsere Prozedur, die den Procedure Pointer holen soll, verfügbar ist; diese Prozedur wird dadurch auch gebunden.

Abbildung 8

In unserem Beispiel wollen wir eine Prozedur GetTimestamp aus einem Serviceprogramm GETTIME aufrufen. Diese Prozedur hat einen Rückgabewert, muss also mit Prototyp aufgerufen werden. Diesen Prototyp dürfen wir aber nicht deklarieren, da wir diese Prozedur ja nicht binden wollen. Deshalb deklarieren wir uns erst mal einen Procedure Pointer, hier mit dem Namen CallPointer versehen. Der Prototyp ReflectProz beschreibt nun einen Prototyp, der mit den Aufrufparametern unserer Prozedur GetTimestamp verträglich sein muss. Dieser Prototyp hat also keinen Aufrufparameter und als Rückgabewert einen Timestamp (Datenart Z) – ganz wie die Prozedur, die wir aufrufen wollen. Unter dem EXTPROC-Schlüsselwort wird aber jetzt der Procedure Pointer eingetragen und nicht der Prozedurname. Dieser Pointer hat zum Zeitpunkt der Umwandlung keinen Wert zugewiesen, es kann also auch gar nichts gebunden werden.
Im Programmablauf muss diesem Procedure Pointer erst ein Wert zugewiesen werden, bevor der Prototyp zum Aufruf verwendet werden kann. Diese Zuweisung eines Wertes geschieht dann mit dem Aufruf unserer ausführlich beschriebenen Prozedur GetProcPointer. Die Werte für die Namen der Prozedur und des Serviceprogramms könnte man natürlich auch aus einer Datei lesen, oder selber als Parameter erhalten haben.

Im wirklichen Leben sollte man dann vor Aufruf noch prüfen, ob man eventuell *NULL zurück bekommen hat, bevor man einen Aufruf wagt. Für den Aufruf selber, wird dann einfach der entsprechende Prototyp benutzt. Bei einer CALLB-Anweisung könnte man auch direkt den Procedure Pointer für den Aufruf verwenden. In unserem Beispiel käme man dann aber nicht an den Rückgabewert heran.

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