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

Mit dem neuen Kontrollfeld »XControl« kommt Atari dem Ruf noch mehr Platz für Accessories noch. Damit der Nachschub an Modulen auch munter sprudelt, finden Pure C / Turbo C Programmierer »Knoff-hoff« und Tools zur Entwicklung eigener CPX-Module.

XControl ist ein Accessory, das bis zu 99 weitere Module nachlädt und verwaltet. Für diese CPX-Module hält XControl viele Funktionen bereit und übernimmt eine Menge Verwaltungskram. Bei näherer Betrachtung eines Moduls macht man eine wundersame Entdeckung: Es besteht aus zwei Teilen, einem 512 Byte langen Header und einer einfachen Programmdatei. Der CPX-Header enthält das Icon, das XControl anzeigt, sowie weitere Flags und Informationen über das Modul. XControl lädt zunächst die Header aller aktiven CPX-Module (Endung ».CPX«). Anhand eines ID-Strings und einer Versionsnummer im Header stellt XControl sicher, daß immer nur die aktuelle Version eines Moduls geladen wird, auch wenn es mehrmals vorhanden ist.

Nur wenn in einem Header das Flag »RAM-resident« gesetzt ist, verbleibt das komplette Modul dauerhaft im Speicher. Da sich diese Einstellung jederzeit über das Modul »KONFIG.CPX« ändern läßt, darf sich kein CPX-Modul darauf verlassen, daß es immer im Speicher steht. Also keine Spielereien mit verbogenen Vektoren und Ähnlichem. Nur die Steuerung von vektorverbiegenden Programmen sollen CPX-Module übernehmen. Für die weitere Vorgehensweise von XControl sind zwei Flags von Bedeutung: Ist »Boot-init« gesetzt, startet das Modul bereits während des Bootvorgangs. Anhand eines Flags in der XCBP-Struktur (dazu später mehr) erkennt das Modul die »Bootzeit«. Während des Bootvorgangs sollte ein Modul lediglich Parameter setzen.

Nun kommt das »Set-only«-Flag ins Spiel. Ist dieses Flag gesetzt, nimmt XControl das betreffende Modul nicht in die CPX-Liste auf. Dieses Modul startet also nur ein einziges Mal und erscheint nicht in XControl. Im Gegensatz zu Auto-Ordner-Programmen dürfen aber solche Set-only-Module ohne größere Klimmzüge auf AES und VDI zugreifen - eine nützliche Sache also. Besonders interessante Effekte ergeben sich bei gesetztem Set-only- und gelöschtem Bootinit-Flag: Nichts geht mehr. XControl ignoriert Module mit dieser unsinnigen Einstellung völlig. Tabelle 1 zeigt den Aufbau des CPX-Headers.

Nachdem die Initialisierungsphase abgeschlossen ist, verhält sich XControl wie jedes andere Accessory, es lauert auf einen Aufruf in der Menüleiste. Wenn der Anwender XControl aktiviert und ein Modul aus der Liste wählt, wird es in den Speicher geladen und gestartet. Leider liefert XControl bei zu knappem Speicher keine Fehlermeldung, sondern verweigert kommentarlos die Ausführung des Moduls. Manche Programme wie Calamus 1.9n oder 1st Word bereiten hier Schwierigkeiten, da sie den kompletten Speicher für sich beanspruchen. Einzige Abhilfe: die lebensnotwendigen CPX-Module resident konfigurieren. Nach getaner Arbeitet verschwindet das Modul wieder aus dem Speicher, falls es nicht als resident deklariert ist. Um die Zerstückelung des Speichers zu vermeiden, sollten CPX-Module nur zur Arbeit geöffnet und danach sofort wieder geschlossen werden. Vor allem Besitzer von Rechnern mit TOS kleiner 2.05 müssen sich an diese Vorgehensweise halten. Bei älteren Versionen ergeben sich Probleme beim Start oder Ende einer Applikation und gleichzeitig aktiven XControl. Dieser Haken liegt am TOS, das auf die korrekte Verarbeitung der AC_CLOSE-Meldung durch alle laufenden Applikationen warten sollte.

Spielregeln

Hier die Grundlegenden Regeln zur Programmierung eines CPX-Moduls im Überblick:

  1. Vom Betriebssystem benötigter Speicherplatz ist schnellstmöglich wieder freizugeben, spätestens bei Beendigung des Moduls. Wichtig: Das Öffnen von Dateien und VDI-Workstations fordert ebenfalls Speicher an! Also alle Dateien und Workstations sofort nach Gebrauch wieder schließen.
  2. Resourcen müssen im Modul eingebunden sein. Nachladen mit rsrc_load() ist verboten.
  3. Wenn XControl ein Modul startet, übergibt es ihm eine Reihe von Flags und vor allem Zeiger auf äußerst nützliche Funktionen. Dazu gehören Routinen zur Verwaltung von Resourcen, Dialogen, Slidern, PopUp-Menüs, Cookies usw. Diese sind den Funktionen des Betriebssystems vorzuziehen (etwa xcbp->rsh_obfix statt rsrc_obfix). Dadurch bleiben die Module klein und XControl ist immer darüber im Bilde, was das Modul gerade so treibt.
  4. Das CPX-Modul darf sich nicht darauf verlassen, daß es immer im Speicher steht. Auch bei residenten Modulen muß das Programm jeden Aufruf wie einen Neustart behandeln.
  5. Keine Event-Funktionen mit Xform_do() Aufrufen mischen (mehr dazu in einem der nächsten Kursteile).
  6. Die »cpx_id« im Header muß eindeutig sein. jedes CPX-Modul bekommt eine eigene vier Byte breite Identifikation.
  7. Zur Bedienungsoberfläche: PopUp-Menüs sind mit einem Schatten zu kennzeichnen. Die Buttons »Sichern«, »OK« und »Abbruch« sind stets zur verwenden. AC_CLOSE entspricht »Abbruch«, das Schließen des Fensters (»WM_CLOSED«) als »OK«. Das XControl-Fenster ist immer 256 x 176 Pixel groß. Das Icon eines Moduls hat die Größe von 32 x 24 Pixeln.

Zum Angriff

Auf der TOS-Diskette finden Sie im Archiv »XControl« das Modul »DEMO_CPX.CPX« inklusive Quelltext und Pure C Hilfsdatei »USR.HLP«, die Sie zu Ihren anderen Hilfsdateien des Compilers kopieren.

Wie schon erwähnt, besteht ein CPX-Modul aus dem Header und einer Programmdatei. Die Projektdatei »DEMO_CPX.PRJ« erzeugt zuerst den Programmteil »DEMO_CPX.CP«. Den Header linken wir (im Moment noch zu Fuß) mit dem Programm »MAKE_CPX.TOS« dazu. Eine kurze Anleitung dazu finden Sie im Quellcode »MAKE_CPX.C«.

Bei unserem »DEMO_CPX.CPX« handelt es sich um ein XForm-Modul. Grob kann man CPX-Module in zwei Gruppen einteilen: Event-Module und XForm-Module. Die Bezeichnung »XForm« bezieht sich auf die Xform_do-Funktion, um die sich in diesen Modulen alles dreht. Für die meisten Anwendungen sollten XForm-Module genügen. Alle von Atari stammenden CPX-Module gehören dieser Sorte an. Die Event_Module sind Thema späterer Kursteile.

Struktur eines XForm-Moduls

Als erster Befehl im Textsegment eines CPX-Moduls steht die Initialisierungsroutine cpx_init(), bzw. ein Sprung zu dieser. Für die Funktion CPX_save() sollte am Anfang des Datensegments ausreichend Platz sein. Um diesen Aufbau zu erreichen, verwenden Sie im Compiler anstelle von »PCSTART.O« oder »TCSTART.O« den Startupcode »CPXSTART.S«.

cpx_init()

Der erste Ansprechpartner für XControl ist die Funktion cpx_init(), die jedes CPX-Modul bereitstellen muß. Sie wird von XControl bei jedem Modulstart aufgerufen und erhält auf dem Stack einen Zeiger auf eine Struktur mit diversen Flags und CPX-Funktionen.

	CPXINFO* cdecl cpx_init (XCPB *Xcpb);

Wichtig: Bei der Verwendung von Pure C müssen alle via XControl angesprochenen Funktionen als »cdecl« deklariert sein, da alle Parameter auf dem Stack übergeben werden. In unserem Beispiel betrifft dies im Moment nur cpx_init() und cpx_call().

/*Funktionen und Flags die von XControl */
/* zur Verfügung gestellt werden.     */

typedef struct
{
	WORD handle; /* Handle der Screen-Workstation */
	WORD booting; /* Während des Bootvorganges TRUE */
	WORD reserved;
	WORD SkipRshFix; /* Resourcen bereits angepaßt? */

	CPX_LIST* cdecl (*get_cpx_list) (void);
	WORD cdecl (*save_header) (CPX_LIST *header);
	void cdecl (*rsh_fix) (WORD num_objs, WORD num_frstr, WORD num_frimg, WORD num_tree,
	           OBJECT *rs_object, TEDINFO *rs-tedinfo, char *rs_strings[], ICONBLK *rs_iconblk,
	           BITBLK *rs_bitblk, long *rs_frstr, long *rs-frimg, long *rs_trindex,
	           struct foobar *rs_imdope);
	void cdecl (*rsh_obfix) (OBJECT *tree, WORD ob);
	WORD cdecl (*Popup) (char *items[], WORD num, WORD def_item, WORD font, GRECT *up, GRECT *world);
	void cdecl (*Sl_size) (OBJECT *tree, WORD base, WORD slider, WORD entrys, WORD vis_ent,
	                       WORD hvflag, WORD min_pix)
	void cdecl (*Sl_x) (OBJECT *tree, WORD base, WORD slider, WORD value, WORD min,
	                    WORD max, void (*foo) (void));
	void cdecl (*Sl_y) (OBJECT *tree, WORD base, WORD slider, WORD value, WORD min,
	                    WORD max, void (*foo) (void));
	void cdecl (*Sl_arrow) (OBJECT *tree, WORD base, WORD slider, WORD obj, WORD inc,
	                        WORD min, WORD max, WORD *value, WORD hvflag, void (*foo) (void) );
	void cdecl (*Sl_dragx) (OBJECT *tree, WORD base, WORD slider, WORD min, WORD max,
	                        WORD *value, void (*foo) (void));
	void cdecl (*Sl_dragy) (OBJECT *tree, WORD base, WORD slider, WORD min, WORD max,
	                        WORD *value, void (*foo) (void));
	WORD cdecl (*Xform_do) (OBJECT *tree, WORD eobj, WORD *msg);
	GRECT *cdecl (*GetFirstRect) (GRECT *prect);
	GRECT *cdecl (*GetNextRect) (void);
	void cdecl (*Set_Evnt_Mask) (WORD MOBLK *m1, MOBLK *m2, long time);
	WORD cdecl (*XGen_Alert) (WORD al);
	WORD cdecl (*CPX_Save) (void *ptr, long bytes);
	void *cdecl (*Get-Buffer) (void);
	WORD cdecl (*getcookie) (long cookie, long *p_value);
	WORD Country_Code;
	void cdecl (*MFsave) (WORD flag, MFORM *mf);
} XCPB;

Während des Bootvorgangs (booting == TRUE) sollte cpx_init() nur Einstellungen und Ähnliches vornehmen. In unserem Beispiel initialisieren wir den 64 Byte langen statischen Puffer. Seine Adresse erfragen wir über die Funktion »Get_buffer()«. Ein Rückgabewert ungleich Null zeigt XControl, daß kein Set-only-Modul vorliegt.

Auch bei jedem weiteren Aufruf unseres Moduls steht cpx_init am Anfang. Damit die CPX-Funktionen und Flags im ganzen Programm zugänglich sind, sichern wir den Zeiger auf die XCPB Struktur in einer globalen Variable. Mit »rsh_fix()« passen wir die Resourcen an. Damit binden wir ein RSH-File vollständig in unser Modul ein. Das Flag »SkipRshFix« zeigt an, ob die Resourcen bereits bearbeitet sind. Als Rückgabewert erhalten wir einen Zeiger auf eine Struktur, in der wir die vom CPX-Modul zur Verfügung gestellten Funktionen finden.

/* Funktionen des CPX-Moduls */
typedef struct
{
	WORD cdecl (*cpx_call) (GRECT *rect);
	void cdecl (*cpx_draw) (GRECT *clip);
	void cdecl (*cpx_wmove) (GRECT *work);
	void cdecl (*cpx_timer) (WORD *quit);
	void cdecl (*cpx_key) (WORD kstate, WORD key, WORD *quit);
	void cdecl (*cpx_button) (MRETS *mrets, WORD nolicks, WORD *quit);
	void cdecl (*cpx_m1) (MRETS *mrets, WORD *quit);
	void cdecl (*cpx_m2) (MRETS *mrets, WORD *quit);
	WORD cdecl (*cpx_hook) (WORD event, WORD *msg, MRETS *mrets, WORD *key, WORD *nclicks);
	void cdecl (*cpx_close) (WORD flag);
} CPXINFO;

Für unseren heutigen Kursteil und alle anderen XForm-Module ist nur cpx_call() von Interesse, Die übrigen Funktionen benötigen nur Event-Module. Zeiger auf nicht benötigte Funktionen müssen den Wert Null enthalten.

cpx_call()

Ähnlich wie main() bei einem normalen C-Programm, ist cpx_call() der Hauptteil eines CPX-Moduls. XControl springt diese Funktion nach cpx_init an. Als Rückgabewert muß ein XForm-Modul immer eine 0 abliefern.

	WORD cdecl cpx_call (GRECT rect);

Der Parameter »rect« zeigt auf die Koordinaten des Arbeitsbereichs des XControl-Fensters, an die wir unsere Resourcen anpassen. Den Dialog zeichnen wir wie gewohnt mittels »objc_draw()«. Die Funktion »Xform_do()« kümmert sich um die Abarbeitung des Dialogs. Ähnlich wie »form_do« reißt sie alle Gewalt an sich, nur viel besser. Neben der Verwaltung der Dialogbox kümmert sie sich auch gleich um die Restaurierung des Bildschirms.

Die Behandlung des Exit-Objektes und der Nachrichten erledigt in unserem DEMO_CPX die Funktion handle_dialog(). Auch die Verwaltung der PopUp-Menüs und Slider finden Sie hier. Hiermit sind wir am Ende des ersten Teils angelangt. In Tabelle 2 sind alle bislang verwendeten Funktionen von XControl beschrieben. (ah)

Der CPX-Header

Länge Name Text
2 Byte magic Byte 1 muß 0, Byte 2 muß 100 sein, daran erkennt XControl ein Modul
2 Byte flags Ein Bitfeld mit 16 Bits:
Bit 0 set-only Das Set-only-Flag
Bit 1 boot-init Das Boot-init-Flag
Bit 2 ram-resident Das Ram-resident-Flag
Bit 3-15 reserved Reserviert (Atari)
4 Byte cpx_id ID-String, sollte einzigartig sein! Quasi der Personalausweis
2 Byte cpx_version Die Versionsnummer des Moduls im Hexadezimal-Format: V1.23 = 123
14 Byte i_text Text unter dem Icon
96 Byte icon Das Icon, 3724 Pixel
2 Byte i_info Bitfeld mit Infos über das Icon:
Bit 0-7 i_char Buchstabe des Icon (????)
Bit 8-11reserved Reserviert (Atari)
Bit 12-15 i_color Farbe des Icon
18 Byte text Der Titel (steht neben dem Icon)
2 Byte t_info Bitfeld mit Infos über den Text:
Bit 0-3 c_back Farbe des Hintergrundes
Bit 4-7 pattern Füllmuster
Bit 8-11 c_text Farbe des Textes
Bit 12-15 c_board Farbe des Rahmens
64 Byte buffer Der einzige nicht flüchtige Speicherplatz für CPX-Module
306 Byte reserved Reserviert (Atari)

Tabelle 1. Der Aufbau des CPX-Headers

rsh-fix, verwendet in cpx-init();

void cdecl (*rsh-fix)(WORD num-objs, WORD num-frstr, WORD num-frimg, WORD num-tree, OBJECT *rs-object, TEDINFO *rs-tedinfo, char *rs-strings[], ICONEILK *rs-iconblk, BITBLK *rs-bithlk, long *rs-frstr, long *rs-frimg, long *rs-trindex, struct foobar *rs-imdope); Funktion: Ganze Objekt-Bäume können hiermit, analog zu rsrc_load(), auflösungsunabhängig angepaßt werden. Parameter: Die vom DR-RCS gelieferten RSH-Parameter

Popup, verwendet in handle-dialog();

	WORD cdecl (*Popup)(char *items[],WORD num, WORD def_item, WORD font, GRECT *up, GRECT *world);

items: Eine Liste mit Zeiger auf die einzelnen Einträge. Am Anfang eines Eintrags müssen zwei, am Ende ein Leerzeichen stehen, alle Einträge müssen gleich lang sein. num: Anzahl der Einträge
def_item: Der Eintrag mit dem Häkchen oder -1
font Größe des Zeichensatzes (0=klein, 3=groß)
up: Die Stelle, an der das PopUp erzeugt werden soll (Koordinaten des angeklickten Buttons)
world: Koordinaten der Umgebung (normalerweise der Dialog-Baum)
Funktion: Popup erzeugt und verwaltet ein PopUp-Menü. Werden mehr als vier Einträge verwendet, muß der Anwender blättern.
Ergebnis: Nummer des gewählten Eintrags oder -1.

Sl_x/Sl_y, verwendet in init_dialog();

void cdecl (*SI-x)(OBJECT *tree, WORD base, WORD slider, WORD value, WORD min, WORD max, void (*foo)(void));

tree: Zeiger auf den Objektbaum.
base: Basis des Sliders
slider: Der Slider
value: Der Wert, den der Slider anzeigen soll
min: Kleinster Wert für value
max: Größter Wert für value
foo: NULL oder ein Zeiger auf eine Funktion, die von XControl bei der Positionierung des Sliders aufgerufen wird. Damit läßt sich ein Text im Slider online verändern. Funktion: Beide Funktionen positionieren einen Slider in Abhängigkeit zur darzustellenden Datenmenge (Sl_x horizontal, Sl_y vertikal).

Sl_arrow, verwendet in handle-dialog();

	void cdecl (*Sl_arrow)(OBJECT *tree, WORD base, WORD slider, WORD obj, WORD inc, WORD min, WORD max, WORD *value, WORD hvflag, void (*foo)(void));

tree: Zeiger auf den Objektbaum
base: Basis des Sliders
slider: Der Slider
obj: Dieses Objekt wird invertiert (im Normalfall sollte es das angeklickte Objekt sein), wenn das invertieren unerwünscht ist, kann -1 übergeben werden.
inc: Der Wert, um den *value erhöht werden soll. Für eine Verkleinerung ist ein negativer Wert zu wählen.
value: Zeiger auf den Sliderwert
min: Kleinster Wert für *value
max: Größter Wert für *value
hvflag: Für horizontale Slider 1, vertikale Slider 0
foo: NULL oder ein Zeiger auf eine Funktion, die von XControl bei der Positionierung des Sliders aufgerufen wird. Damit läßt sich ein Text im Slider online verändern.
Funktion: Komfortables positionieren eines Sliders

Sl_dragx/Sl_dragy, verwendet in handle-dialog();

	void cdecl (*SI-dragx)(OBJECT *tree, WORD base, WORD slider, WORD min, WORD max, WORD *value, void (*foo)(void));

tree: Zeiger auf den Objektbaum
base: Basis des Sliders
slider: Der Slider
min: Kleinster Wert für *value
max: Größter Wert für *value
value: Zeiger auf den Sliderwert
foo: NULL oder ein Zeiger auf eine Funktion, die von XControl bei der Positionierung des Sliders aufgerufen wird. Damit läßt sich ein Text im Slider online verändern.
Funktion: Beide Funktionen erlauben das Verschieben des Sliders mit der Maus (Sl_dragx horizontal, Sl_dragy vertikal). Diese Funktionen nur bei gedrückter Maustaste aufrufen.

Xform_do(), verwendet in cpx_call();

	WORD cdecl (*Xform-do)(OBJECT *tree,WORD eobj, WORD *msg);

tree: Zeiger auf Dbjektbaum.
eobj: Erstes Edit-Object oder Null.
msg: Nachrichten-Puffer ähnlich wie bei evnt_mesag().
Ergebnis: Nummer des angewählten Objekts oder -1. Bei -1 ist eine gültige Nachricht in msg[0]. ACHTUNG, ein Doppelklick darf nicht wie bei form_do() ausmaskiert werden, da sonst die -1 nicht erkannt wird (siehe Quellcode).
Nachrichten: 41 AC_CLOSE Modul sofort beenden
22 WM_CLOSED Der Anwender hat den CLOSER angewählt -> Modul beenden (Spielregeln beachten!!).
53 CT_KEY Eine Sondertaste (Help, Undo, Funktionstasten usw.) wurde gedrückt. In msg[3] steht der Tastaturcode.
20 WM_REDRAW XControl zeichnet den Dialog neu. Nur wichtig, wenn Dialogteile selbst gezeichnet werden müssen (Grafiken u.ä.).
Funktion: Der Kern eines jeden XForm-Moduls. Ein Dialog wird inklusive Redrawing von Xform-do im XControl-Fenster verwaltet.

XGen_Alert(), verwendet in save_par();

 WORD cdecl (*XGen-Alert)(WORD al);

al: 0: Voreinstellungen sichern?
1: Fehler bei der Speicherverwaltung
2: Fehler beim Schreiben oder Lesen einer Datei
3: Datei nicht gefunden
Funktion: Zeichnen einer Alertbox für einfache Fehlermeldungen
Ergebnis: FALSE/TRUE für Abbruch bzw. OK

CPX_Save(), verwendet in save_par();

	WORD cdecl(*CPX-Save)(void *ptr, long bytes);

ptr: Ein Zeiger auf die zu sichernden Daten
bytes: Länge der Daten in Byte
Funktion: XControl schreibt die Daten an den Anfang des Datensegments unserer CPX-Datei. Somit stehen sie beim nächsten Start zur Verfügung. ACHTUNG, es muß genügend freier Platz im Datensegment sein (»CPXSTARTS« anschauen).
Ergebnis: FALSE: Es ist ein Fehler aufgetreten
TRUE: Alles in Ordnung

Get_Buffer(), verwendet in cpx_init();

void * cdecl (*Get-Buffer)(void);

Diese Funktion liefert einen Zeiger auf einen 64 Byte langen Puffer. Da dieser im Header des CPX-Moduls plaziert ist, steht er immer im Ram und ist der einzige garantiert nicht flüchtige Speicher, der CXP-Modulen zur Verfügung steht.

MFsave[), verwendet in handle-dialog[]

void cdecl (*MFsave)(WORD flag, MFORM *mf);

flag: TRUE / FALSE, Mausform sichern bzw. wieder herstellen
mf: Zeiger auf Speicherbereich für die Mausdaten. Wird die Form des Mauszeigers verändert, sollte die alte Form mit dieser Funktion gesichert und später wieder hergestellt werden, da es keine Funktion gibt, die eingestellte Mausform zu erfragen.

Tabelle 2. XControl nimmt mit seinen Funktionen jede Menge Arbeit ab


Richard Kurz
Aus: TOS 05 / 1992, Seite 90

Links

Copyright-Bestimmungen: siehe Über diese Seite