Programmieren

Tags in PCQ-Pascal

Mit Kickstart 2.0 haben die sogenannten "Taglists" Einzug in die Funktionen des Betriebssystems gehalten. Anstatt vor einem Funktionsaufruf stets ganze Strukturen initialisieren zu müssen, wie es noch unter Kickstart 1.2/1.3 erforderlich war, steht dem Programmierer nun ein flexibles System zur Übergabe von Parametern zur Verfügung. Der Gedanke hinter den Taglists ist der einer Anreihung einzelner Informationseinheiten, der "TagItems". Ein einzelnes "TagItem" enthält dabei zwei Informationen:

1. Eine Kennung, den eigentlichen "Tag", an dem die Funktion, der das TagItem übergeben wird, erkennen kann, welche Art von Information das TagItem enthält. So signalisiert beispielsweise das "Tag"-Feld WA_WIDTH, daß das TagItem die Breite eines zu öffnenden Fensters festlegen will.

2. Einen Datenwert, die eigentliche Information des TagItems. Im Beispiel unter 1. wäre dies der Wert der Fensterbreite.

Da die Größe beider Felder definitionsgemäß vier Byte beträgt, sind TagItems universell einsetzbar. Eine Taglist besteht nun aus vielen direkt aufeinander folgenden TagItems. Abgeschlossen wird sie mit dem (in Utility/TagItem.i) vorgegebenen Tag-Wert TAG_DONE (oder TAG_END). Zahlreiche ab Kickstart 2.0 neu ins Betriebssystem eingeführte Funktionen und auch viele Funktionen diverser Libraries (z.B. der Picasso96-API-Library) arbeiten bei der Parameterübergabe mit TagItems. Dabei sind grundsätzlich zwei Möglichkeiten denkbar:

1. Die Übergabe der einzelnen Tags und der einzelnen Datenwerte selbst. Das würde für eine fiktive Funktion "CallFunction" etwa so aussehen:

    win:=CallFunctionsTags (tagitem1tag,tagitem1data,tagitem2tag,
                            tagitem2data,...,TAG_DONE);
   

2. Die Übergabe eines Zeigers auf die vorher vom Programm initialisierte Taglist:

    win:=CallFunctionTagList (taglistptr);
   

Erstere Vorgehensweise ist speziell auf die Programmiersprache C zugeschnitten, in der die Übergabe einer unbestimmten Anzahl von Parametern nichts ungewöhnliches ist. Anders bei Pascal. Diese Sprache, die einer sauberen und klar definierten Syntax einen hohen Stellenwert beimißt, kennt die Übergabe beliebig vieler Parameter nicht. Vielmehr erwartet Pascal eine in der Funktionsdeklaration ausdrücklich festgelegte Anzahl von Übergabeparametern. Damit wird der Programmierer zur Kontrolle des eigenen Sourcecodes gezwungen und eine Quelle potentieller Flüchtigkeitsfehler eliminiert. Dieses vom Konzept her sehr sinnvolle Prinzip erweist sich jedoch bei Systemen, in denen, wie eben beim Amiga, zahlreiche Betriebssystem- und auch externe Funktionen standardmäßig von der C-Syntax ausgehen, als Entwicklungshindernis. Zwar sehen sowohl das AmigaOS als auch sämtliche shared libraries in Anknüpfung an die oben getroffene Unterscheidung für jeden Funktionseinsprung sowohl eine Tags- als auch eine Taglist-Variante vor. In Demosourcen, die zumeist in C gehalten sind, wird jedoch regelmäßig nur die Tags-Variante verwendet. Der Pascal-Programmierer ist also gezwungen, den Sourcecode erheblich umzuarbeiten, um ein Pascal-konformes Ergebnis zu erhalten. Um die Verwendung von Taglists unter PCQ-Pascal zu demonstrieren, soll im folgenden ein kleines Programm entwickelt werden, das nichts weiter macht, als ein Fenster auf der Workbench mit Hilfe der OS 2.0-Funktion "OpenWindowTaglist" zu öffnen. (Natürlich könnte dies ohne weiteres auch mit der 1.2-Funktion "OpenWindow" geschehen. Dazu müßte jedoch eine komplette NewWindow-Struktur initialisiert werden, die zudem lediglich die unter 1.2 zulässigen Elemente enthalten würde - der also etwa sämtliche auf Public-Screens bezogenen Initialisierungsmöglichkeiten fehlen würden.)

Um eine Taglist zu erstellen, muß man zunächst wissen, wie ein einzelnes TagItem in Pascal auszusehen hat. Zu seiner Realisierung empfiehlt sich die Verwendung eines Records mit den Elementen "Tag" und "Data" (wobei jeweils ein "ti_" (für "TagItem") vorangestellt wird). Da sowohl der Tag als auch das Datenfeld vier Bytes lang sind, definiert man sie zweckmäßigerweise als Integer-Elemente. Dabei sollte jedoch der Tag zunächst einmal eigenständig definiert werden, um die funktionale Unterscheidung deutlich zu machen und für eine Portierung in andere Sprachen mit möglicherweise anderen Variablentypen vorzubeugen. Es ergibt sich also folgende Typisierung eines Tags und eines TagItems:

    Type
     Tag = Integer;
     TagItem = Record
                ti_Tag  :   Tag;
                ti_Data :   Integer;
               End;
   

Diese Definition wird von der Include-Datei "TagItem.i" im "Utility"-Ver- zeichnis der 2.x/3.x-Includes vorgenommen. Eine Taglist nun ist nichts weiter als ein Feld mit mehrere TagItem-Elementen. Die Größe ist dabei beliebig wählbar, in der Regel wird ein Feld mit 32 Elementen verwendet.

    Type
     TagList =   Array [0..31] Of TagItem:
   

Diese Typisierung muß allerdings gesondert erfolgen. Ein einfaches Programm zum Öffnen eines Fensters der Größe 200 x 100 Pixel mit dem Titel "Test Window" und einigen voreingestellten Flags sähe demnach bei Verwendung der OpenWindowTaglist-Funktion folgendermaßen aus:

    PROGRAM TestTaglist;

    {$I "Include:intuition/intuition.i" }

    Const

     { der Name des Fensters }
     WinTitle        :   String  =   "Test Window";

     { der Name des Public-Screens, auf dem das Fenster sich öffnen soll }
     PubScreenName   :   String  =   "Workbench";

    Type
     TagList = Array [0..31] Of TagItem;

    Var
     testwindow  :   WindowPtr;
     imes        :   IntuiMessagePtr;
     ttags       :   TagList;

    Begin

      { Initialisierung der einzelnen TagItems der TagList }

      ttags[0].ti_Tag:=WA_Width;
      ttags[0].ti_Data:=200;
      ttags[1].ti_Tag:=WA_Height;
      ttags[1].ti_Data:=100;
      ttags[2].ti_Tag:=WA_IDCMP;
      ttags[2].ti_Data:=IDCMP_CLOSEWINDOW;
      ttags[3].ti_Tag:=WA_Title;
      ttags[3].ti_Data:=Integer(WinTitle);
      ttags[4].ti_Tag:=WA_PubScreenName;
      ttags[4].ti_Data:=Integer(PubScreenName);
      ttags[5].ti_Tag:=WA_DepthGadget;
      ttags[5].ti_Data:=Integer(TRUE);
      ttags[6].ti_Tag:=WA_SmartRefresh;
      ttags[6].ti_Data:=Integer(TRUE);
      ttags[7].ti_Tag:=WA_CloseGadget;
      ttags[7].ti_Data:=Integer(TRUE);
      ttags[8].ti_Tag:=WA_DragBar;
      ttags[8].ti_Data:=Integer(TRUE);
      ttags[9].ti_Tag:=WA_RMBTrap;
      ttags[9].ti_Data:=Integer(TRUE);
      ttags[10].ti_Tag:=WA_Activate;
      ttags[10].ti_Data:=Integer(TRUE);
      ttags[11].ti_Tag:=TAG_DONE;

      { Aufruf der Betriebssystemfunktion }

      testwindow:=OpenWindowTagList (NIL,Adr(ttags));

      If testwindow<>Nil Then
      Begin

       { falls alles geklappt hat: warte auf CloseWindow }

       Repeat
        WaitPort (testwindow^.UserPort);
        imes:=Address(GetMsg (testwindow^.UserPort));
       Until imes<>Nil;
       ReplyMsg (Address(imes));
       CloseWindow (testwindow);
      End
      Else
       Writeln ("Couldn't open window.");

    End.
   

Der Vorteil dieser Vorgehensweise liegt in der exakten Einhaltung der strengen Pascal-Syntax. Aber auch der Nachteil liegt auf der Hand - es ist ziemlich viel Schreibarbeit zur Initialisierung der einzelnen TagItems erforderlich - jedesmal muß nämlich neben dem Tag und dem Datenwert auch der Variablenname des TagItems und das zu initialisierende Feld angegeben werden. Dies ließe sich natürlich mit Hilfe einer Funktion vereinfachen, die automatisch zwei zu übergebende Werte als Tag- und als Datenwert einsetzt. So etwas könnte etwa so aussehen:

    Procedure InitTagItem (ti : ^TagItem; titag : Tag; tidata: Integer);

    Begin
     With ti^ Do
     Begin
      ti_Tag:=titag;
      ti_Data:=tidata;
     End;
    End;
   

Dennoch wäre etwa in unserem Beispiel für jedes TagItem eine Source-Zeile der Form

    InitTagItem (Adr(ttags[0]),WA_Width,200);
   

erforderlich. Und auch das erscheint durchaus noch als im Vergleich zu C überflüssiger Programmieraufwand. Dies hat auch Patrick Quaid, der Schöpfer des Pascal-Compiler PCQ erkannt, und mit Version 1.2d die Möglichkeit zur C-konformen Parameterübergabe eingeführt. Vielen Dank an dieser Stelle an

Nils Sjoholm

für den Hinweis auf diese Möglichkeit der Tag-Programmierung. Und zwar besitzt PCQ 1.2d, der seit kurzer Zeit als Freeware im Aminet von Nils auf Wunsch von Patrick Quaid, der seinen Amiga nicht mehr nutzt, wiederveröffentlicht wurde, eine Compiler-Direktive, die die Parameterübergabe nach C-Konventionen aktiviert - {$C+} - bzw. wieder deaktiviert ({$C-}). Zwischen diesen Compilersteuerbefehlen kann man nun nach Belieben Funktionen mit einer unbestimmten Anzahl von Übergabeparametern einbauen. Dazu werden der Parameterliste einfach drei Punkte ("...") hinzugefügt:

    {$C+}
    Procedure VieleArgumente (...);

    Begin
    End;
    {$C-}
    #C21
   

Jedes Argument wird von PCQ automatisch als 4-Byte-Wert verwaltet. Es ist auch möglich, zunächst die Übergabe einiger Parameter zwingend vorzuschreiben und lediglich im Anschluß eine variable Anzahl zuzulassen:

    {$C+}
    Procedure MischArgumente (name : String; alter : Integer; ...);

    Begin
    End;
    {$C-}
   

Um die unbestimmten Parameter nutzen zu können, bietet PCQ 1.2d zwei Funktionen: VA_Start und VA_Arg. VA_Start muß dabei eine Variable vom Typ Address übergeben werden. Diese wird dann mit einem Zeiger auf den ersten der unbestimmten Übergabeparameter initialisiert. VA_Arg dient dazu, den Parameterwert auszulesen und nachfolgende Parameter zu addressieren. Dabei übergibt man einen Zeiger, der zunächst mit VA_Start initialisiert wurde, und den Variablentyp, den man zurückgegeben haben möchte. Nachdem der Parameter entsprechend ausgelesen wurde, erhöht VA_Arg automatisch den übergebenen Zeiger um 4 Byte, so daß er auf den nächsten Parameter zeigt. Patrick Quaid gibt dazu in der PCQ-Anleitung ein Beispiel einer Funktion, der man eine beliebige Anzahl von Integer-Werten übergibt, und die diese dann nacheinander ausgibt. Da es keine PCQ-Funktion gibt, mit deren Hilfe man feststellen kann, wieviele Parameter übergeben wurden, ist es erforderlich, diese Information als zwingenden Übergabeparameter mitzuliefern. Das ganze (der PCQ-Anleitung von Patrick Quaid entnommene) sieht dann so aus:

    {$C+}
    Procedure WriteInts (Num : Short; ...);

    Var
     ArgPtr : Address;
     i      : Short;

    Begin
     VA_Start (ArgPtr);
     For i:=1 To Num Do
      Writeln (VA_Arg(ArgPtr,Integer));
    End;
    {$C-}
   

Diese Funktion kann dann im Programm mit einer Num-Angabe und anschließend beliebig vielen Parametern (die nicht zwingend im Integer-Format sein müssen, diese Festlegung betrifft nur die Art und Weise, wie die Funktion die Übergabeparameter interpretiert) aufgerufen werden. Bei der Übergabe von Strings werden - wie in PCQ üblich - nicht die einzelnen Zeichen, sondern ein Zeiger auf das erste Zeichen des Strings übergeben.

Wie kann man sich diese (nicht unbedingt die Programmsicherheit fördernde) Erweiterung der Pascal-Syntax nun für Taglists zu nutzen machen ? Nils Sjoholm hat zur eleganten Übergabe einer unbestimmten Anzahl von TagItems die Einrichtung einer TAGS-Funktion vorgeschlagen.

    {$C+}
    Function TAGS (...) : Address;

    Var
     ArgPtr : Address;

    Begin
     VA_Start (ArgPtr);
     TAGS:=ArgPtr;
    End;

    {$C-}
   

Diese Funktion wird von Nils aller Voraussicht nach in die neue Version (2.0) der pcq.lib integriert werden, die demnächst im Aminet erscheinen soll. Damit könnte man dann theoretisch innerhalb eines CallFunctionTaglist-Aufrufes statt des Zeigers auf die Taglist einen TAGS-Aufruf einbauen, in dem dann beliebig viele TagItems stehen könnten. Der OpenWindowTaglist-Aufruf unseres Beispiels sähe demnach so aus:

     testwindow:=OpenWindowTags (NIL,TAGS(WA_Width,200,
                                          WA_Height,100,
                                          WA_IDCMP,IDCMP_CLOSEWINDOW,
                                          WA_Title,"Test Window",
                                          WA_PubScreenName,"Workbench",
                                          WA_DepthGadget,Byte(TRUE),
                                          WA_SmartRefresh,Byte(TRUE),
                                          WA_CloseGadget,Byte(TRUE),
                                          WA_DragBar,Byte(TRUE),
                                          WA_RMBTrap,Byte(TRUE),
                                          WA_Activate,Byte(TRUE),
                                          TAG_DONE)
                                );
   

Allerdings funktioniert das ganze nur theoretisch. In der Praxis muß man sich vor Augen halten, was genau passiert. Die Parameter werden an TAGS übergeben und der Zeiger auf die Parameterliste ermittelt. Er wird zum Rückgabewert gemacht und dieser Rückgabewert OpenWindowTags als Zeiger auf eine Taglist "verkauft". Das ist er jedoch nicht. Denn im Gegensatz zur im Beispiellisting komplett initialisierten Taglist findet hier eine solche Initialisierung nur für die Parameterübergabe an TAGS statt, d.h. ein Speicherbereich wird nur für diesen einen Zweck als Taglist behandelt. Sobald TAGS beendet ist, können an dieser Stelle schon wieder ganz andere Werte stehen, so daß der TAGS-Rückgabewert auf einen Speicherbereich zeigt, in dem Daten stehen werden, die mit der eigentlich beabsichtigten Taglist nicht mehr viel zu tun haben.

Aber auch hierfür gibt es eine Lösung. Und zwar muß man das Konzept einer universellen TAGS-Funktion aufgeben und sich für jeden Taglist-Aufruf ein eigenes Tag-Pendant einrichten. Für OpenWindowTaglist sähe OpenWindowTags (eine Funktion, die in Programmiersprachen, welche ohnehin mit Übergaben einer unbestimmten Parameteranzahl rechnen, übrigens vorgesehen ist - insoweit muß man bei der Übertragung derart erstellter PCQ-Sourcen in anderen Sprachen aufpassen) wie folgt aus:

    {$C+}
    Function OpenWindowTags (nw : Address; ...): WindowPtr;

    Var
     ArgPtr      :   Address;

    Begin
     VA_Start (ArgPtr);
     OpenWindowTags:=OpenWindowTagList (nw,ArgPtr);
    End;
    {$C-}
   

Anstatt den Zeiger auf die Parameterliste zurückzugeben, wird also direkt die Taglist-Funktion aufgerufen. Dies macht Sinn, da ja für den Aufruf der C-konformen Funktion der Speicherbereich, welcher die TagItems enthält, unangetastet bleibt und somit OpenWindowTagList die richtigen Daten vorfindet. Allerdings muß man natürlich für andere TagList-Funktionen jeweils entsprechende C-ähnliche Pendants einbauen, so daß diese Lösung im Vergleich zu der mit einer universell einsetzbaren TAGS-Funktion länger werden wird. Dennoch erfordert sie weit weniger Aufwand als die manuelle Initialisierung einer Taglist. Und sie funktioniert - was ja ab und an durchaus als Vorteil angesehen werden kann. Der Source des Beispielprogrammes sähe bei Verwendung dieser Lösung also so aus:

    PROGRAM TestTags;

    {$I "Include:intuition/intuition.i" }
    Var
        testwindow  :   WindowPtr;
        imes        :   IntuiMessagePtr;

    {$C+}
    Function OpenWindowTags (nw : Address; ...): WindowPtr;

    Var
        ArgPtr      :   Address;

    Begin
     VA_Start (ArgPtr);
     OpenWindowTags:=OpenWindowTagList (nw,ArgPtr);
    End;
    {$C-}

    Begin
     testwindow:=OpenWindowTags (NIL,WA_Width,200,
                                     WA_Height,100,
                                     WA_IDCMP,IDCMP_CLOSEWINDOW,
                                     WA_Title,"Test Window",
                                     WA_PubScreenName,"Workbench",
                                     WA_DepthGadget,Byte(TRUE),
                                     WA_SmartRefresh,Byte(TRUE),
                                     WA_CloseGadget,Byte(TRUE),
                                     WA_DragBar,Byte(TRUE),
                                     WA_RMBTrap,Byte(TRUE),
                                     WA_Activate,Byte(TRUE),
                                     TAG_DONE);

     If testwindow<>Nil Then
     Begin
      Repeat
       WaitPort (testwindow^.UserPort);
       imes:=Address(GetMsg (testwindow^.UserPort));
      Until imes<>Nil;
      ReplyMsg (Address(imes));
      CloseWindow (testwindow);
     End
     Else
      Writeln ("Couldn't open window.");
    End.
   

Für welche der beiden Varianten man sich schließlich entscheidet, dürfte eine Geschmacksfrage sein. Pascal-Puristen, die Wert auf eine strenge Syntax legen, werden Taglists manuell initialisieren. Und etwas bequemere Programmierer werden sich für die kürzere, aber dafür mangels einer Überprüfung der Korrektheit der übergebenen Parameter auch unsicherere Alternative entscheiden. Wichtig ist nur eins: daß auch PCQ-Programmierer die Vorteile der Taglist-Funktionen nutzen können.

(c) by Andreas Neumann

Zurück