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.
Hier die Grundlegenden Regeln zur Programmierung eines CPX-Moduls im Überblick:
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.
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«.
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.
Ä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)
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
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
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.
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).
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
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.
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.
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
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
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.
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