C-Programmierung ist (k)ein Kinderspiel - Programmierkurs - Teil 1

Immer wieder haben wir festgestellt, daß eine Programmiersprache zu lernen an einem praktischen Projekt viel mehr Spaß bereitet als aus einem trocken geschriebenem Lehrbuch.

Im Laufe dieses Artikels wird sich hier ein größeres Programm entwickeln, so daß Sie Einblick in Entstehung und Aufbau der wichtigsten Routinen nehmen können. Statt dessen möchten wir die besonders interessanten Aspekte näher betrachten. Ziel dieses Programmierkurses ist ein komplettes Programm, das mit den Quelltexten auf einer Leserservicediskette zu finden sein wird. Es dient zur grafischen Darstellung mathematischer Funktionen, wobei die Benutzerumgebung eine wichtige Rolle spielen wird.

Ein wesentlicher Gesichtspunkt bei der Entwicklung größerer Programme ist die Modularisierung, d.h. die Unterteilung in logisch zusammenhängende Teile. Im Normalfall gibt es ein zentrales Modul, das den Einstieg bildet, die Aktionen des Benutzers überwacht und den weiteren Programmablauf steuert. Außerdem empfiehlt es sich, hier die globalen Variablen zu deklarieren, die andere Teile auch benötigen. Diese weiteren Module enthalten dann die Programmfunktionen. Damit der Compiler die angesprochenen »intermodularen« globalen Variablen erkennt, müssen sie in jedem solchen Nebenmodul erneut deklariert werden, und zwar als extern. Bei sehr großen Programmen ist es vorteilhaft, für diese »extern«-Deklarationen einen eigenen Header anzulegen, der dann mit einer include-Anweisung in die einzelnen Module eingebunden wird. Er sollte auch alle wichtigen Konstantendefinitionen enthalten. Diese Konvention macht spätere Änderungen und Erweiterungen besonders einfach, da nur eine Stelle überarbeitet werden muß. In Listing 1 finden Sie eine Übersicht der Module des hier vorgestellten Programms.

Beginnen wir also mit dem Kernmodul. Die Funktion main() (Listing 2) führt zuerst die üblichen GEM-Initialisierungen durch, um dann das Resource-File zu laden. Dieses File, das mit dem Ressource-Konstruktion-Set erzeugt wurde, enthält die Datenstrukturen der GEM-typischen Features der Benutzeroberfläche wie z.B. Dialogboxen und Menüs. Auf die Datenstruktur der Elemente des Resource-Files werden wir aber später noch einmal zurückkommen. Auf das RCS können wir hier leider nicht eingehen, aber im Handbuch Ihres C-Compilers sollte da etwas zu finden sein, denn dieses Programm stellt eine unverzichtbare Hilfe für die GEM-Programmierung dar. Man kann die Ressourcen zwar auch in C direkt erstellen, der Aufwand ist jedoch immens (siehe tcgp_rsc.c). Nach der Initialisierung einiger Variablen und der Darstellung der Titelbox wird die zentrale Funktion jedes menügesteuerten Programms aufgerufen. Sie heißt üblicherweise event_loop(), zu deutsch »Ereignisschleife«. Mit ihr werden wir uns jetzt etwas eingehender beschäftigen. Nach dem Programmstart findet sich der Benutzer in einem Menü wieder, aus dem er die gewünschten Aktionen auswählt. Die Aufgabe von event_loop() ist es, diese Auswahl festzustellen und an die Funktion do_menu() weiterzuleiten, die dann die Ausführung der entsprechenden Unterfunktionen übernimmt. Bei den handelsüblichen Menüs findet sich hier ein Aufruf der Funktion evnt_mesag(), die nur auf Mitteilungen vom System oder anderen Applikationen wartet. Sie liefert Informationen über den gewählten Menüpunkt, anhand derer das Programm weiter verzweigt. Leider verbietet es das Menü am oberen Bildschirmrand, die gesamte Fläche zu nutzen. Angenehm wäre es, diesen Rand zu nutzen, ohne auf den gewohnten Komfort zu verzichten. Die hier vorgestellte Funktion stellt eine Möglichkeit dar, dies zu realisieren. Hier ein kurzer Überblick über die Funktionsweise. Das Menü ist nur dann sichtbar, wenn die Maus es berührt oder ein Titel selektiert ist. Sobald der Titel geschlossen wird, verschwindet es und der darunter liegende Teil des Bildschirms wird wieder hergestellt. Um diese Operationen richtig durchzuführen, überwacht die Funktion evnt_multi() mehrere Ereignisse. Mit dieser Funktion, die wohl die längste Parameterliste unter den Systemfunktionen ihr eigen nennt, stellt GEM eine sehr komfortable Möglichkeit dar, festzustellen, was der User gerade so alles treibt.

Dies funktioniert natürlich auch selektiv. Je nachdem, welche Flags Sie im ersten Parameter gesetzt haben, reagiert die Funktion nur auf bestimmte Ereigniskombinationen. In unserem Fall auf eine Betätigung der linken Maustaste, auf eine Mitteilung, d.h. wie evnt_mesag(), und ein »Mausereignis«. Bei letzterem handelt es sich um die Position des Mauszeigers in bezug auf ein in den entsprechenden Parametern definiertes Rechteck. In einem weiteren Parameter legen Sie fest, ob auf »Maus innerhalb des Rechtecks« oder »Maus außerhalb des Rechtecks« reagiert werden soll. In unserem Fall umfaßt dieses Überwachungsrechteck den ganzen Bildschirm (0,0,640,400 als Position und Ausdehnung) und zurückgemeldet wird, wenn sich die Maus innerhalb desselben befindet. Na ja, werden Sie sich fragen, wo soll der Mauszeiger wohl sonst sein? Recht haben Sie, er befindet sich immer innerhalb dieses Rechtecks. Aus diesem Grunde liefert uns evnt_multi() auch ständig ein solches Mausereignis zurück.

Da aber die beiden Parameter »mmox« und »mmoy« jeweils die aktuellen Mauskoordinaten enthalten, sind wir somit über alle Aktionen bestens informiert. Leider weigert sich das AES hartnäckig, uns auf diesem Wege über einen wichtigen Mausklick zu unterrichten. Denn ist ein Menütitel selektiert und man klickt ins Leere, d.h. ohne einen Eintrag zu berühren, schließt GEM das Menü, ohne eine Nachricht zurückzuliefern. Daher müssen wir uns selbst vom Zustand der Titelleiste überzeugen, was in den - dem evnt_multi-Aufruf folgenden - Programmzeilen geschieht. Zu diesem Zweck testen wir, ob in der zweiten Pixelzeile irgendein Punkt gesetzt ist, denn dies ist nur dann der Fall, wenn ein Titel selektiert wurde (monochrom). Das Ergebnis halten wir in der Variablen »bool« fest. In »me« wird zusätzlich vermerkt, ob das Menü im Moment versteckt ist oder nicht. Falls sich der Mauszeiger nicht in der Menüleiste befindet, kein Titel invertiert und das Menü dargestellt ist, deaktivieren wir die Leiste und stellen mit der Funktion m_rep den darunterliegenden Bildschirm wieder her. Außerdem vermerken wir in »me« auch den geänderten Zustand des Menüs. Ist dagegen der Zeiger innerhalb der Leiste und des Menüs versteckt, rettet »m_rette« den Bildschirmstreifen, der vom neu aktivierten Menü überschrieben wird. Zum Schluß bearbeiten wir den Fall, daß eine Mitteilung eingetroffen ist. Wir inaktivieren dann das Menü, stellen den Screen wieder her und werten die beiden für die Menükontrolle zuständigen Werte im msg_buf (Message-Buffer) in der Funktion do_menu() aus. An dieser Stelle möchte ich auch auf die Funktion wind_update() hinweisen. Sie blockiert alle Bildschirmaktionen des AES, so daß diese nicht mit einem vom Programm vorgenommenen Bildschirmzugriff kollidieren können. Anschließend läßt sich mit derselben Funktion die Kontrolle selbstverständlich wieder an AES übergeben. Parameter »3« bedeutet, daß die Applikation die Kontrolle über die Maussteuerung übernimmt, »2« macht dies dann wieder rückgängig. Die in evnt_loop() ebenfalls vorgenommene Überwachung eines Mausklicks über die Funktion w_handle() dient der Verwaltung des Fenster-Systems, das später beschrieben wird. Leider arbeitet dieser Menütrick nicht mit allen Accessories fehlerfrei zusammen. Führen diese als ersten Aufruf einen wind_update() durch oder nutzen das AES überhaupt nicht, so hat der Event-Manager keine Möglichkeit, andere Prozesse aus der Warteschlange zum Zuge kommen zu lassen. Daher bleibt bei diesen Accessories das Menü stehen, und manchmal läuft die Selektion der Titel etwas aus dem Ruder. Aber ein evnt_timer()-Aufruf zu Beginn des Accessory stellt diese Probleme ab (leider nur bei denen, deren Quelltext zur Verfügung steht). In der nächsten Ausgabe geht es mit dem Programmieren weiter. (mb)


1:
2:
3:	typedef struct
4:	{
5:		char	*te_ptext;		/* Zeiger auf den Text */
6:		char	*te_ptmplt;	/* Zeiger auf Textmaske */
7:		char	*te _pvalid;	/* Zeiger auf Texttypmaske */
8:		int	te_font;			/* Zeichensatz */
9:		int	te_junk1;			/* reserviert */
10:		int	te_just;			/* Ausrichtung */
11:		int	te_color;			/* Farbe */
12:		int	te_junk2;			/* reserviert */
13:		int	te_thickness;		/* Rahmendicke */
14:		int	te_txtlen;			/* Länge der Textmaske */
15:		int	te_txtlen;			/* template string length */
16:	} TEDINFO;
17:
18:	typedef union
19:	{
20:		long	index;
21:		struct
22:		{
23:			unsigned char        : 8;
24:			signed   framesize   : 8;
25:			unsigned framecol    : 4;
26:			unsigned textcol     : 4;
27:			unsigned textmode    : 1;
28:			unsigned fillpattern : 3;
29:			unsigned interiorcol : 4;
30:		} obspec;
31:		TEDINFO *tedinfo;
32:		ICONBLK *iconblk;
33:		BITBLK  *bitblk;
34:		USERBLK *userblk;
35:		char    *free_string;
36:	} OBSPEC;
37:
38:
39:
40:
41:	typedef struct
42:	{
43:		int          ob_next;	/* -> objects next sibling */
44:		int          ob_head;	/* -> head of object‘s children */
45:		int          ob_teil;	/* -> tail of object‘s children */
46:		unsigned int ob_type;	/* object type: BOX, CHAR, ... */
47:		unsigned int ob_flags;	/* object flags */
48:		unsigned int ob_state;	/* state: SELECTED, OPEN, ... */
49:		OBSPEC       ob_spec;	/* „out“: -> anything else */
50:		int          ob_x;	/* upper left corner of object */
51:		int          ob_y;	/* upper left corner of object */
52:		int          ob_width;	/* object width */
53:		int          ob_height;	/* object height */
54:	} OBJECT;
55:

Dieses Listing (4) findet in der nächsten Ausgabe seine Anwendung

1:
2:	Module
3:
4:	TC_GP.C			Kernmodul mit Menü und Ablaufsteuerung
5:	LSYSTFM.C			Routinen zum Zeichnen der Koordinatensysteme etc.
6:	WINDOW.C			Routinen für das Fenstersystem
7:	I_TCGP_R.C			Interpreter zum Auswerten math. Ausdrücke
8:
9:
10:	Header
11:
12:	m_mouse.h			Routinen zur Mauszeigermanipulation
13:	m_math.h			mathematische Funktionen
14:	m_grafik.h			ein paar Grafikfunktionen
15:	m_dial.h			Funktionen zum Handling der Dialogboxen
16:	m_sonst.h			2 weitere Hilfsfunktionen
17:	tcgp_rsc.h			Konstantendefinitionen für GEM-Objekte
18:
19:	tcgp_rsc.rsc		Resource-File
20:	tcgp_rsc.c			C-Quellcode der Resource-daten, wie er vom RCS erzeugt
21:						wird. Kann im Programm integriert werden (mit kleinen
22:						Syntax-Änderungen)

Listing (1) beschreibt den Einsatz der verschiedenen Module

1:
2:	void   Event_Loop (OBJECT *menu_addr);
3:	void   Event_Loop (menu_addr)
4:	OBJECT *menu_addr;
5:	{
6:
7:		int ev_mwhich,		/* Variablen für get_event */
8:	       mmox, mmoy,
9:	       mmobtn,
10:	       mmokstate,
11:	       mkreturn,
12:	       mbreturn;
13:
14:		int msgbuf[8];		/* Ereignispuffer für get_event */
15:
16:		int bool, me, i;
17:
18:		bool = me = O;
19:		while (1)			/* endlosschleife */
20:		{
21:
22:			/* Ereignis ermitteln und Mausposition */ 
23:			ev_mwhich = evnt_multi (MU_MESAG|MU_M1|MU_BUTTON, 1, 1, 1,
24:			                        0, 0, 0, 640,400,
25:			                        0, 0, 0, 0, 0,
26:			                        msgbuf,
27:			                       0, 0,
28:			                       &mmox, &mmoy,
29:			                       &mmobtn,
30:			                       &mmokstate,
31:			                       &mkreturn,
32:			                       &mbreturn);
33:			wind_update (3);
34:			/* ermitteln ob Menü selektiert */ 
35:			bool = 0;
36:			for (i = O; i <= 20; i++)
37:				if (scr_addr[20+i] != OL) bool = 1;
38: 		wind_update (2);
39:
40:			if (mmoy >= 25 && !bool && me) {	/* Zeiger nicht in Leiste,  */
41:				menu_bar (menu_addr, 0);	/* nicht selektiert */
42:				wind_update (3);
43:				mrep ();
44:				me = 0;
45:				wind_update (2);
46:			}
47:
48:			if (mmoy < 25 && !me) {	/* menu_off, Zeiger in Leiste */
49:				wind_update (3);
50:				me = 1;
51:				mrette ();
52:				wind_update (2);
53:				menu_bar (menu_addr,1);
54:
55:			}
56:
57:
58:			if ((ev_mwhich >> 4) &1 ) {	/* Falls Msg eingeht */
59:
60:				menu_tnormal (menu_addr, msgbuf[3], 1);
61:				menu_bar (menu_addr, 0);
62:				wind_update (3);
63:				mrep ();
64:				bool = O;
65:				me = 0;
66:				wind_update (2);
67:				Do_Menu (msgbuf[3], msgbuf[4]);
68:			}
69:
70:			if ((ev_mwhich >> 1)&1) {	/* Mausclick */
71:		/* Printf („\033Y%c%c%d,%d,%d“,32+25, 32, mmox, mmoy,ev_mwhich); */
72:				w_handle (mmox, mmoy);
73:
74:			}
75:
76:
77:
78:		}	/*  Ende von while-Schleife */
79:	}		/*  Ende von Event_Loop */
80:
81:
82:	void main ()
83:
84:	{
85:		int i, j;
86:		open_vwork ();		/* Öffnet appl. ‚handle‘ */
87:
88:		set_clip (0, 0, 640, 400);
89:
90:		hide_mouse ();
91:		Cls();
92:		show_mouse (); 
93:
94:
95:		graf_mouse (O,OL);	/* Maus als Zeiger */
96:
97:		/* **** rsc-file lade **** */
98:		if (rsrc_load („tcgp_rsc.rsc“) ==0)
99:		{
100:			form_alert(1, “[3][Fehler beim laden | des RSC-Files.][Abbruch)“);
101:			close_vwork ();
102:			PtermO ();
103:		}
104:
105:		rsrc_gaddr (O, MENU,&menu_addr);	/* Adresse des Menubaums */
106:
107:
108:		scr_addr = (long*) Logbase ();
109:
110:
111:		Set_DText (KOORDBOX, XMINTEXT, „-10.0“);
112:		Set_DText (KOORDBOX, XMAXTEXT, „+10.0“);
113:		Set_DText (KOORDBOX, YMINTEXT, „-10.0“);
114:		Set_DText (KOORDBOX, YMAXTEXT, „+10.0“);
115:		Set_DText (KOORDBOX, XSKALT, „+00“);
116:		Set_DText (KOORDBOX, YSKALT, „+00“);
117:
118:		Obj_SetState (KOORDBOX, RSKALINB, SELECTED);
119:		Obj_SetState (KOORDBOX, NULLKRB, SELECTED);
120:
121:		xmin    = -10.0;
122:		xmax    =  10.0;
123:		ymin    = -10.0;
124:		ymax    =  10.0;
125:		xskal   =  1.0;
126:		yskal   =  1.0;
127:		ksflags = 3;
128:
129:		for (i = O; i < 5; i++)
130:			for (j = O; j < 10; j++)
131:				(void) strcpy (funktst[i][j], “\0“);
132:
133:
134:		box_do (EINBOX, 0);
135:
136:		Event_Loop (menu_addr);	/* Ereignisschleife */ 
137:		close_vwork ();
138:	}

Listing (2) erklärt u.a. die Funktionsweise von Kernmodulen

1:
2:	long  mressp [400];
3:
4:	void mrette (void);
5:	void mrette ()
6:	{	int i;
7:
8:		long *scr_addr;
9:
10:		scr_addr = Logbase ();
11:
12:		wind_update (1);
13:		hide_mouse ();
14:		for (i = 0; i < 400; i++)
15:			mressp [i] = (scr_addr + i);
16:		show_mouse ();
17:		wind_update (0);
18:	}
19:
20:
21:	/* mrep stellt den von mrette gespeicherten Zustand der ersten 20
22:	   Pixelzeilen wieder her. Nur in verbindung mit mrette und den
23:	   dazu gehörenden Deklarationen verwenden  */
24:
25:
26:	void mrep (void);
27:	void mrep ()
28:	{	int i;
29:
30:
31:		long *scr_addr;
32:
33:		scr_addr = (long*) Logbase ();
34:
35:		wind_update (1);
36:		hide_mouse ();
37:		for (i = O; i < 400; i++)
38:			*(scr_addr + i) = mressp[i]);
39:		show_mouse ();
40:		wind_update (0);
41:	}
42:
43:
44:
45:	void Obj_unselect (int tree_index, int obj_index);
46:	void Obj_unselect (tree_index, obj_index)
47:	int tree_index, obj_index;
48: {
49:		OBJECT *obj, *tree_addr;
50:		rsrc_gaddr (O, tree_index, &tree_addr);
51:		obj = (OBJECT *) ((long) tree_addr + 24 * obj_index);
52:		obj->ob_state & = ~SELECTED;
53:	}
54:
55:
56:
57:	/* box_do stellt die Box tree_index dar, überwacht die Eingaben,
58:	   löscht sie, stellt den Screen wieder her und gibt den
59:	   Index des Ausgangsobjektes zurück.
60:	   Beim Ausgangsobjekt wird der Status SELECTED gelöscht
61:	   st_obj muß index eines editierbaren Textes oder, falls keiner
62:		vorhanden, 0 sein
63:	*/
64:
65:
66:	int box_do (int tree_index, int st_obj);
67:	int box_do (tree_index, st_obj)
68:	int tree_index, st_obj;
69:	{
70:		OBJECT *box_addr;
71:		int    x, y, w, h, back;
72:		long   *ptr;
73: 		rsrc_gaddr (0, tree_index, &box_addr);
74:		form_center (box_addr, &x, &y, &w, &h);
75;		ptr = rette (x, y, w, h);
76:		form_dial (0, 1, 1, 1, 1, x, y, w, h);
77:		form_dial (1, 1, 1, 1, 1, x, y, w, h);
78:		objc_draw (box_addr, 0, 2, x, y, w, h);
79:		back = form_do (box_addr, st_obj);
80:		form_dial (2, 1, 1, 1, 1, x, y, w, h);
81:		form_dial (3, 1, 1, 1, 1, x, y, w, h);
82:		Obj_unselecet (tree_index, back);
83:		rep (x, y, w, h, ptr);
84:		return (back);
85:	}
86:
87:	/* ------------------------------------------------------------------- */
88:
89:	/* holt editierbaren Text aus tedinfo */
90:
91:	char *Get_DText (int tree_index, int text_index);
92:	char *Get_DText (tree_index, text_index)
93:	int tree_index, text_index;
94:	{
95:		OBJECT  *obj, *tree_addr;
96:		TEDINFO *ted;
97:		rsrc_gaddr (O, tree_index, &tree_addr);
98:		obj = (OBJECT *) ((long) tree_addr + 24 * text_index);
99:		ted = obj->ob_spec.tedinfo;
100:		return (ted->te_ptext);
101:	}
102:
103:	/* ------------------------------------------------------------------ */
104:
105:	/* setzt editierbaren Text in Tedinfo */
106:
107:
108:	void Set_DText (int tree_index, int text_index, char *text);
109:	void Set_DText (tree_index, text_index, text)
110:	int tree_index, text_index;
111:	char *text;
112:	{
113:		OBJECT  *obj, *tree_addr;
114:		TEDINFO *ted;
115:		rsrc_gaddr (O, treee_index, &tree_addr);
116:		obj = (OBJFCT *) ((long) tree_addr + 24 * text_index);
117:		ted = obj->ob_spec.tedinfo;
118:		strcpy (ted->te_ptext, text);
119:	}

Mit diesem Listing (3) arbeiten wir in der nächsten Folge. Es gibt einen kleinen Vorgeschmack auf das was noch kommt...


Ulrich Finkler
Aus: ST-Magazin 07 / 1990, Seite 20

Links

Copyright-Bestimmungen: siehe Über diese Seite