Grundlagen: Programmierung von CPX-Modulen in C - Teil 2

In diesem Kursteil lassen wir die graue Theorie links liegen und wenden uns diesmal der praxisnahen Seite zu. Wir befassen uns mit der Programmierung von XForm-Modulen und nehmen den Quelltext eines CPX-Moduls genauer unter die Lupe.

Wie im letzten Kursteil schon erwähnt, besteht ein CPX-Modul aus einem 512 Byte langen Header und einer normalen GEMDOS-Programmdatei. Bisher haben wir diese beiden Teile quasi zu Fuß mit »MAKE_CPX.TOS« verbunden. Um das Ganze ein wenig komfortabler zu gestalten und weil ein weiteres Demonstrationsobjekt zum Thema CPX-Module nicht schaden kann, präsentieren wir Ihnen ein neues Modul namens »LINK_CPX.CPX« (Bild 1). Sie finden es auf der TOS-Diskette im Archiv »CPX-Kurs«. Um dieses Modul wird sich in diesem Kursteil alles drehen. Mit LINK_CPX definieren, ändern und linken Sie einen beliebigen CPX-Header. Selbstverständlich ist auch ein Icon-Editor für die Bearbeitung der hübschen Bildchen integriert. Um unser Allround-Talent zu starten, kopieren Sie das Modul »LINK_CPX.CPX« zu Ihren anderen CPX-Modulen. Nach einem Reset oder Neuladen der Module finden Sie es dann im erweiterten Kontrollfeld »XControl«.

Eine kleine Toolbox

Unter »Header laden« verbirgt sich ein PopUp-Menü, mit dessen Hilfe Sie einen CPX-Header entweder aus einem vorhandenen CPX-Modul oder einer Header-Datei (Endung *.HDR) laden können. Ist der gewünschte Header geladen oder wollen Sie einen neuen Header generieren, können Sie nun mit den Buttons, Slidern und editierbaren Strings die Einstellungen verändern. Nach Möglichkeit werden die Veränderungen sofort angezeigt.

Wer auf »Sichern« klickt, wird voll Entsetzen abermals auf ein PopUp-Menü treffen, diesmal mit vier Wahlmöglichkeiten: »Als Default Header« sichert den Header in einen internen Puffer, damit dieser Header beim nächsten Aufruf sofort zur Verfügung steht, auch wenn LINK_CPX nicht als RAM-resident markiert ist. »Nur Header sichern« schreibt den Header in eine Datei mit der Endung ».HDR«. An CPX schreiben« ersetzt den Header eines vorhandenen CPX-Moduls. Achtung: Der alte Header geht dabei natürlich verloren. Mit dem Eintrag »Header mit CP linken« erzeugen Sie aus dem aktuellen Header und einem GEMDOS-Programmteil (Endung ».CP«) ein vollständiges CPX-Modul.

Wie bereits erwähnt, befindet sich auch ein Icon-Editor an Bord, den Sie durch einen Klick auf das Icon aufrufen. Dieses gestalten Sie nach Gutdünken. Unter »Datei« steckt wiederum - wie könnte es auch anders sein - ein PopUp-Menü. »Quellcode sichern« speichert das Icon als C-Quellcode (Endung »*.ICN«). »Quellcode laden« lädt ein Icon im C-Quellcode. Dieses Quellcode-Format unterstützen auch andere Icon-Editoren wie zum Beispiel »Interface« von Shift. Die einzige Beschränkung beim Datentausch: Die Größe des Icons muß exakt 32 x 24 Pixel betragen.

Der Quelltext von LINK_CPX besteht aus 4 Teilen:

  1. »CPXSTART.S« Der Startupcode für CPX-Module
  2. »LINK_CPX.C« Der Hauptteil mit cpx_init() und cpx_call()
  3. »ED_ICON.C« Der Icon-Editor
  4. »OP_FILE.C« Routinen für die Verwaltung von Dateien

Dank »CPXSTART.S« steht am Anfang des Textsegments ein Sprung zu cpx_init(). Am Anfang des Datensegments halten wir 512 Byte zum Sichern eines Default-Headers via CPX_Save() frei. Die Variablen »errno« und »_FilSysVec« werden von Pure-C-Funktionen intern benötigt.

cpx_init()

Nun zum eigentlichen Kern in »LINK_CPX.C«: Während des Bootvorgangs wird »cpx_init()« zum erstenmal von XControl mit gesetztem »booting«-Flag aufgerufen. Da wir in unserem Fall keine besonderen Boot-Aufgaben bearbeiten, geht die Kontrolle sofort an XControl zurück. Der Rückgabewert 1 zeigt XControl, daß es sich bei LINK_CPX nicht um ein »Set_only«-Modul handelt.

Ruft der Anwender unser CPX-Modul auf, wird es ein wenig komplizierter: Wieder springt XControl zunächst zu cpx_init(), diesmal mit gelöschtem Booting-Flag. Es ist Zeit, die Resourcen anzupassen. Um ein mehrfaches Anpassen zu vermeiden, etwa bei RAM-residenten Modulen, ist das »SkipRshFix«-Flag zu beachten. Die eigentliche Anpassung erledigt die XControl-Funktion »rsh_fix()«, die einen kompletten Baum anpaßt. Als Parameter erwartet die Funktion die Felder eines RSH-Files. Wichtig: Die RSH-Datei muß mit dem Format des Digital Research Resource-Construction-Sets übereinstimmen.

Damit der Anwender auch einen Header zur Bearbeitung vorfindet, holen wir über »get_cpx_list()« einen Zeiger auf den Anfang der internen CPX-Liste von XControl und vergreifen uns sogleich am ersten Header. In der CPX-Liste sind die Header aller geladenen CPX-Module und zusätzliche Informationen gespeichert. Diese verkettete Liste betrachten wir besser als »read only«. ändern Sie die Werte dieser Liste im falschen Augenblick, sind in der Regel bombastische Erfolge zu erwarten. Wahrscheinlich ist das auch der Grund, warum Atari diese Funktion nicht offiziell dokumentiert hat, sie selbst aber sehr wohl verwendet. Die einzelnen Einträge der Liste sind über den Zeiger »next« verkettet. Der letzte Eintrag in der Liste ist durch den Wert Null gekennzeichnet.

Da der Anwender die Anzahl der Einträge selbst bestimmt, stehen gegebenenfalls auch mehr Einträge als CPX-Module zur Verfügung. In einem leeren Eintrag nimmt das erste Byte in f_name den Wert Null an. Die einzelnen Einträge der Liste sind in C folgendermaßen definiert:

typedef struct cpxlist
{
 char f_name[14]; /* Dateiname des Moduls
 WORD head_ok; /* Sind die Header-Daten ok? */
 WORD segm_ok; /* Steht das Modul im Speicher? */
 struct
 {
 /* Über diese Zeiger kann auf die Segmente des jeweiligen Moduls
zugegriffen werden.
 Natürlich nur wenn es im Speicher steht!
 */

 void *text_seg; /* Anfang des Textsegemts */
 long len_text; /* Länge das Textsegments */
 void *data-seg; /* Anfang des Datensegments */
 long len_data; /* Länge des Datensegments*/

 void *bss_seg; /* Anfang des BSS */
 long len_bss; /* Länge des BSS */
 } *segm;

 struct cpxlist *next; /* Zeiger nächsten Eintrag */
 CPXHEAD header; /* Der Header */
} CPX_LIST;

Damit ist die Initialisierung abgeschlossen. Um XControl zur Weiterarbeit zu überreden, steht in der Struktur CPXINFO ein Zeiger auf unseren Hauptteil. Unbenutzte Felder - in unserem Beispiel alle anderen - sind null. Mit der Rückgabe eines Zeigers auf diese CPXINFO Struktur geben wir die Kontrolle erneut an XControl.

cpx_call()

Was treibt nun XControl mit der Kontrolle? Es gibt sie schleunigst wieder an uns ab, indem es die Funktion cpx_call() aufruft. Hier startet das eigentliche CPX-Modul. Die erste Amtshandlung von cpx_call() sollte das Initialisieren und Zeichnen des verwendeten Dialogs sein. Zu diesem Zweck übergibt XControl im Parameter »rect« einen Zeiger auf eine »GRECT«-Struktur mit den Koordinaten des XControl-Fensters. Sinnvollerweise sichern wir diesen Zeiger in einer globalen Variablen, damit auch die anderen Funktionen über den Standort des Fensters im Bilde sind. XControl aktualisiert diese Koordinaten automatisch. Daher sollte man nur via Pointer auf sie zugreifen. Nachdem die aktuellen Werte in den Dialog eingetragen sind, zeichnet »objc_draw« den Objektbaum. Als Clipping-Bereich für objc_draw() verwenden wir die Fenster-Koordinaten.

Die eigentliche Verwaltung unseres Dialogs überlassen wir der XControl-Funktion »Xform_do()«. Sie arbeitet ähnlich wie form_do(), jedoch verwaltet diese Funktion den Dialog in einem Fenster. Dadurch darf sich der Rechner auch noch anderen Aufgaben widmen. Da während der Arbeit in einem Fenster etwas mehr geschieht als in einer Dialogbox, benötigt Xform_do() einen Zeiger auf einen Nachrichtenpuffer. Im Normalfall gibt Xform_do() die Nummer des vom Anwender gewählten Exit-Objekts zurück. Für den Fall einer Nachricht im erwähnten Puffer gibt die Funktion -1 zurück. Wichtig: Da ein Doppelklick auf ein Exit-Objekt mit gesetztem Bit 15 signalisiert wird, dürfen wir dieses nicht einfach mit & 0x7FFF ausmaskieren, da sonst auch die -1 beim Teufel wäre. Also zuerst auf -1 testen und dann ausmaskieren:

if ((button != -1) && (button & 0x8000))
button &= 0x7FFF;
handle_dialog();

In unserem Fall verarbeitet die Funktion »handle_dialog()« die Rückgabewerte von Xform_do(). Die Nummer des Exit-Objekts steht in der Variablen »button«, den Zeiger auf den Nachrichtenpuffer übergeben wir in der Variablen »msg«. Jetzt beseitigen wir den eventuellen Doppelklick nach obiger Methode.

Die erste interessante Aktion ist ein Klick auf den »Touch-Exit-Button« eines PopUp-Menüs. Das Zeichnen und die Verwaltung übernimmt XControl. Wir teilen XControl über die Funktion »Popup()« lediglich das Aussehen und die gewünschte Position des Menüs mit. Hier die Parameter von Popos() in der Reihenfolge ihres Erscheinens: Die Texte der einzelnen Menüpunkte erwartet die Funktion in einem Zeiger-Feld. Alle Einträge müssen am Anfang mindestens zwei, am Ende mindestens ein Leerzeichen aufweisen. Natürlich will XControl auch wissen, wieviele Menüpunkte es zu bearbeiten hat. Wer ein Häkchen vor einem Menüpunkt haben möchte, übergibt statt -1 die Nummer des Menüpunktes. Der nächste Parameter ist für die Größe des verwendeten Zeichensatzes verantwortlich (3 = groß, 5 = klein). In der Regel erscheint ein PopUp an der Stelle des auslösenden Buttons. Seine Koordinaten berechnen wir mit »objc_offset()«. Es steht Ihnen natürlich frei, das PopUp an einer beliebigen Stelle innerhalb des XControl-Fensters auftauchen zu lassen. Je nach Platzangebot klappt es nach oben oder nach unten auf. Als Rückgabewert liefert Popup() den ausgewählten Menüpunkt oder -1.

Auch für die Sliderverwaltung hält XControl leistungsstarke Funktionen bereit. Klicken Sie einen der Pfeile eines Sliders an, berechnet und zeichnet »Sl_arrow()« die neue Position. Als besonderes Schmankerl übergeben Sie Sl_arrow() einen Zeiger auf eine Funktion, die etwa einen Text im Slider den neuen Werten anpaßt.

Der Anwender sieht - auch während er den Slider betätigt - immer den aktuellen Wert.

Sl_arrow() tritt auch bei einem Mausklick auf die Basis eines Sliders in Aktion. In diesem Fall müssen wir aber noch feststellen, ob der Anwender den Wert erhöhen oder vermindern möchte. Dazu vergleichen wir die Mausposition und die Position des Schiebers. Bei einem horizontalen Slider gilt: Klick links vom Schieber = minus, Klick rechts vom Schieber = plus. Den Rest erledigt wieder Sl_arrow().

Nun könnte der Anwender ja auch noch auf die Idee kommen, den Schieber selbst zu bewegen. Auch dafür stellt XControl Funktionen zur Verfügung. Für einen horizontalen Slider kommt »Sl_dragx()« zum Einsatz, analog dazu »Sl_dragy()« für vertikale Slider. Im Gegensatz zu »Sl_arrow()« dürfen wir diese Funktionen aber nur aufrufen, wenn sich der Mauszeiger tatsächlich über dem Schieber befindet und die Maustaste gedrückt ist. Wie schon bei »Sl_arrow()«, übergeben wir optional einen Zeiger auf eine Funktion zur Aktualisierung der Werte im Slider. Um dem Anwender den Beginn der Schieberei zu signalisieren, verwandeln wir den Mauszeiger in die »flat hand«. Da aber die aktuelle Mausform nicht bekannt ist und keine entsprechende Auskunft in Sicht ist, sichert »MFsave« den alten Mauszeiger. Für die restlichen Exit-Objekte gelten ähnliche Methoden. Mehr Informationen dazu finden Sie im kommentierten Quelltext.

Die Nachrichten

Es folgen die Tagesthemen. Erhält handle_dialog() im Parameter button -1, findet sich im Puffer eine Nachricht. Die Mitteilungen »WM_CLOSED« und »AC_CLOSE« muß jedes XForm-Modul bearbeiten, alle weiteren sind optional auszuwerten. Schließt der Anwender das XControl-Fenster, erhält das aktive XForm-Modul die Nachricht WM_CLOSED. Nach den Vorgaben von Atari behandeln wir diese Nachricht wie einen Klick auf den »OK«-Button.

Will TOS XControl unterbrechen, etwa bei Beendigung oder Start einer Hauptapplikation, kommt die Nachricht AC_CLOSE bei unserem Modul an. Diesen Fall behandeln wir entsprechend dem Button »ABBRUCH«. Für beide Nachrichten gilt: Allen verwendeten Speicher freigeben und das Modul beenden. Drückt der Anwender eine Sondertaste (Help, Undo usw.), teilt XControl dies mit der Nachricht »CT_KEY« mit. Die Nachricht »WM_REDRAW« zeigt an, daß XControl den Dialog neu gezeichnet hat. Wer in seinem Dialog keine selbstgezeichneten Objekte hat, kann diese Nachricht vergessen, ansonsten sind diese selbst zu restaurieren.

Der Icon-Editor

Wer noch nicht mit LINK_CPX gespielt hat, sollte jetzt einen kleinen Blick riskieren und auf das ICON - im Quellcode CICON genannt - klicken. Schon haben Sie die Funktion »ed_icon()« aufgerufen, in der sich der Icon-Editor versteckt.

Auch hier steht am Anfang das Anpassen und Zeichnen des Dialogs. Bei der vergrößerten Darstellung des Icons müssen wir selbst Hand anlegen. Mit dem Öffnen einer virtuellen VDI-Workstation leiten wir die Verwendung der VDI-Zeichenfunktionen ein. Leider gewährt uns XControl bei unserer Zeichenaufgabe keine allzugroße Unterstützung. Es gelten daher die gleichen Regeln wie in allen anderen GEM-Fenstern. Um einem verunstalteten Bildschirm und Bomben vorzubeugen, müssen wir uns um Clipping und Redrawing selber kümmern.

paint_icon()

Die Funktion »paint_icon()« zeichnet das vergrößerte Icon unter Beachtung der Rechteckliste des XControlFensters. Da das Window-Handle des XControl-Fensters nicht bekannt ist, bleibt auch seine Rechteckliste geheim. Als Ersatz stellt XControl die beiden Funktionen »GetFirstRect()« und »GetNextRect()« zur Verfügung. Als kleines Trostpflaster übernehmen diese Funktionen auch noch das leidige Vergleichen des Rechtecks mit dem zu zeichnenden Bereich, also hat »rc_intersect()« in CPX-Modulen ausgedient.

GetFirstRect() erwartet als Parameter einen Zeiger auf eine GRECT-Struktur mit den Koordinaten des Zeichenbereichs. Solange Rechtecke vorhanden sind, liefern beide Funktionen einen Zeiger auf eine GRECT-Struktur zurück, in der die Koordinaten des jeweiligen Clipping-Rechtecks enthalten sind. Der Wert Null signalisiert das Ende der Liste.

Vermutlich aufgrund eines Fehlers in XControl, ist die GRECT-Struktur in einer eigenen Variablen zu sichern. Wahrscheinlich wird sie von XControl nur als lokale Variable auf dem Stack angelegt und muß vor dem Überschreiben bangen. Solange Rechtecke vorhanden sind, setzen wir mit »vs_clip()« das Clipping auf das aktuelle Rechteck und zeichnen das gewünschte Objekt. Dieses etwas komplizierte Verfahren halten wir auch später beim Setzen und Löschen von einzelnen Punkten (paint_pix()) ein.

Die restlichen Funktionen von XControl finden Sie wieder in der Tabelle. Im nächsten Kursteil gehen wir auf die Programmierung der geheimnisvollen Event-Module ein. Möge XControl mit Ihnen sein.

(ah)

Die verwendeten Funktionen von XControl

get_cpx_list, verwendet in cpx_init()
CPX_LIST *cdecl (get_cpx_list)(void);
Funktion: In dieser Liste stehen die Header der aktiven CPX-Module und weitere Informationen. Diese Funktion sollte recht sparsam verwendet werden, da sie von Atari nicht offiziell bekanntgegeben wurde.
Ergebnis: Zeiger auf den Anfang der CPX-Liste von XControl

save_header
WORD cdecl (save_header)(CPX_LIST *header);
Funktion: Der Header eines aktiven CPX-Moduls wird geschrieben. Diese Funktion wird von »Konfig.CPX« verwendet und sollte recht sparsam verwendet werden, da sie von Atari nicht offiziell dokumentiert wurde.
Ergebnis: FALSE, wenn Fehler aufgetreten sind, ansonsten TRUE

rsh_obfix
void cdecl (*rsh_obfix)(OBJECT *tree, WORD ob);
tree: Zeiger auf den Objektbaum
ob: Nummer des anzupassenden Objekts
Funktion: Einzelne Objekte können hiermit - analog zu rsrc_obfix() - auflösungsunabhängig angepaßt werden.

Sl_size
void cdecl (Sl_size)(OBJECT *tree, WORD base, WORD slider, WORD
vis_ent, WORD hvflag, WORD min_pix);
tree: Zeiger auf den Objektbaum
base: Basis des Sliders
slider: Der Schieber
entrys: Anzahl aller Einträge
vis_ent: Anzahl der sichtbaren Einträge
hvflag: Für horizontale Slider 1, vertikale Slider 0
min_pix: Kleinste Größe des Schiebers in Pixel
Funktion: Einstellung der Größe des Schiebers im
Verhältnis gesamter und sichtbarer Datenmenge

GetFirstRec/GetNextRec
GRECT * cdecl (*GetFirstRect)(GRECT *prect);
GRECT * cdecl (*GetNextRect)(void);
prect: Zeiger auf eine GRECT-Struktur mit den Koordinaten des zu zeichnenden Bereichs.
Funktion: Rechteckliste des XControl-Fensters erfragen. XControl übernimmt auch das Schneiden mit dem gewünschten Bereich.

Ergebnis: NULL, wenn keineRechtecke mehr vorhanden sind, oder ein Zeiger mit den Koordinaten des Rechtecks.

getcookie

WORD cdecl (*getcookie)(long cookie, long *p_value);
cookie: Die Kennung des gesuchten Cookies
p_value: Zeiger auf eine Variable (long), in die der gefundene Wert eingetragen werden soll, oder NULL.
Funktion: Sucht nach einem Cookie und liefert dessen Wert.
Ergebnis: FALSE für fehlgeschlagene Suche, sonst TRUE

Country_Code
WORD Country_Code;
Funktion: Dies ist keine Funktion, sondern eine Variable, in der eine Länderkennung abgelegt ist. Anscheinend bezieht sich diese Kennung aber auf die jeweilige Version von XControl und nicht auf die Länderkennung des Rechners.


Richard Kurz
Aus: TOS 06 / 1992, Seite

Links

Copyright-Bestimmungen: siehe Über diese Seite