← ST-Computer 06 / 1997

Portierung leichtgemacht

Grundlagen

Ein Konzept zum modularen, rechnerunabhÀngigen Programmieren

Im Rahmen der Entwicklung eines grĂ¶ĂŸeren Softwarepaketes stand ich kĂŒrzlich vor dem Problem, ein Modul zu entwerfen, welches mit dem Benutzer ĂŒber Tastatur und Bildschirm kommunizieren, gleichzeitig aber möglichst unabhĂ€ngig vom Betriebssystem sein sollte. Einen Weg, dies zu realisieren, möchte ich im Folgenden vorstellen.

Die BetriebssystemunabhĂ€ngigkeit, auch PortabilitĂ€t genannt, war von großem Interesse, da das Modul auf möglichst vielen verschiedenen Plattformen lauffĂ€hig sein sollte. Leider ist es nun so, daß man bei der Programmentwicklung auf betriebssystemabhĂ€ngige Funktionen angewiesen ist, sobald man Bildschirmausgaben tĂ€tigt, die ĂŒber die Textausgabe (’printf') hinausgehen oder die Tastatur abfragt und sich dabei nicht auf die Standardeingabe (’get...’ oder ’scanf') beschrĂ€nken möchte oder kann. Man kommt also kaum gĂ€nzlich daran vorbei, auch Bibliotheken des Betriebssystems zu nutzen. Daher ist es sinnvoll, sich Gedanken darĂŒber zu machen, wie man den Quellcode so strukturieren kann, daß sich die Anpassungen an andere Systeme in Grenzen halten.

Modularisierung

Viele verstehen unter Modularisierung, den Quelltext in verschiedenen Quelltextdateien unterzubringen, die dann thematisch zusammengehörige Funktionen beherbergen. Das sorgt zwar schon fĂŒr eine Struktur, doch wenn die Anzahl der Quelltextdateien ein gewisses Maß ĂŒberschreitet, wird es trotzdem unĂŒbersichtlich. Deshalb fasse ich mehrere Quelltextdateien zu Bibliotheken zusammen, die ĂŒber eine einfach zu handhabende Schnittstelle zu benutzen sind.

Zum Beispiel könnte man eine Bibliothek schreiben, die es dem Programmierer mit einigen wenigen Funktionsaufrufen erlaubt, ein Rechteck per Tastatursteuerung ĂŒber den Bildschirm laufen zu lassen. Genau dies leistet MOVERECT (Listings 5 - 11). NatĂŒrlich ist dies nur ein vereinfachtes Beispiel. Die Aufgabe der Bibliothek könnte beliebig komplexer sein. Doch beschĂ€ftigen wir uns erst einmal mit der PortabilitĂ€t.

PortabilitÀt

Ziel meines Konzeptes ist es, die einzelnen Bibliotheken betriebssystemunabhĂ€ngig zu halten. Alle betriebssystemabhĂ€ngigen Funktionen sollen „von außen hineingereicht" werden, also von dem Programm zur VerfĂŒgung gestellt werden, das die Bibliothek benutzt. Dies hat den enormen Vorteil, daß die Bibliothek von einer Portierung völlig unberĂŒhrt bleibt und nur das Rahmenprogramm abgeĂ€ndert werden muß.

Die Bibliothek greift also auf Funktionen zu, die ihr von außen zugĂ€nglich gemacht werden. Um dies zu realisieren, hat jede Bibliothek einen sogenannten HOOKTABLE (hook, engl. Haken). Das ist nicht mehr als eine Struktur, die mehrere Funktionspointer, also Zeiger auf Funktionen, enthĂ€lt. Genau eine Instanz dieser Struktur ist als globale Variable definiert. Im HOOKTABLE findet die Bibliothek Zeiger auf all die Funktionen, die ihr von außen zur VerfĂŒgung gestellt werden. Diese Zeiger mĂŒssen natĂŒrlich erst einmal initialisiert werden.

Das funktioniert mit der sogenannten Bibliotheksinitialisierung, in unserem Beispiel wĂ€re dies die Funktion ’Minit-MOVERECT’. Dieser Funktion ĂŒbergibt man einige Funktionspointer, die sich die Bibliothek dann im HOOKTABLE merkt. Wenn es jetzt bei Aufrufen von weiteren Funktionen dieser Bibliothek dazu kommt, daß Funktionen von außen benutzt werden sollen, so schaut die Bibliothek in den HOOKTABLE und fĂŒhrt dann die Funktion aus, auf die der entsprechende Funktionspointer zeigt. Das ist dann eine Funktion des Rahmenprogramms. Sinnvollerweise wird man im HOOKTABLE Zeiger fĂŒr alle die Funktionen einrichten, die betriebssystemabhĂ€ngig sind, da diese dann vom Rahmenprogramm hineingereicht werden können. Somit taucht in der Bibliothek selbst kein Aufruf von Betriebssystemfunktionen auf.

Zum Beispiel erwartet ’MinitMOVERECT' einen Zeiger namens ’DrawRect’. Dieser Zeiger sollte sinnvollerweise auf eine Funktion zeigen, die ein Rechteck malt. Eine solche Funktion sei im Rahmenprogramm (hier Listings 1-4) definiert, in unserem Beispiel wĂ€re dies die Funktion ’FRDrawRect’. Man ĂŒbergibt einen Zeiger auf diese Funktion an die Funktion ’MinitMOVERECT. Wenn nun im weiteren Verlauf des Programms eine andere Funktion von MOVERECT aufgerufen wird, die evtl, ein Rechteck malen muß, so wird diese Funktion im HOOKTABLE nachsehen, einen Zeiger auf die im Rahmenprogramm definierte Funktion ’FRDrawRect’ finden und diese ausfĂŒhren.

SYS

Wollte man nun das Rahmenprogramm auf ein anderes System portieren, so mĂŒĂŸte man nur andere Funktionen bei der Bibliotheksinitialisierung angeben. Das erfordert aber bei jeder Portierung eine AbĂ€nderung des Quellcodes des Rahmenprogramms, was recht unpraktisch ist, wenn man, wie ich, auf zwei Systemen gleichzeitig entwickelt. Aus diesem Grunde definiere ich mir einfach zwei verschiedene Funktionen, die das gleiche machen, den gleichen Prototypen haben, aber fĂŒr verschiedene Betriebssysteme ausgerichtet sind und sorge dafĂŒr, daß je nach System nur eine - und zwar die richtige - compiliert wird.

Der Aufbau des fertigen Programmes

Die andere wird vom Compiler einfach ignoriert. Da beide den gleichen Prototypen haben, erspare ich mir damit eine Anpassung der Parameterliste bei der Bibliotheksinitialisierung. Die ĂŒbergebenen Funktionen heißen immer gleich, es steckt nur je nach System eine andere dahinter. Alle Definitionen dieser verschiedenen Funktionen werden in einer weiteren Quelltextdatei des Rahmenprogramms untergebracht, nĂ€mlich in SYS.C.

Als Beispiel soll wieder ’FRDrawRect’ dienen. Wie in SYS.C (Listing 4) zu sehen ist, ist die Definition von 'FR-DrawRect’ eingeschlossen in eine Compiler-if-Anweisung mit einem else-Zweig. Wenn ich nun SYS.C unter TOS compiliere, so ist das Makro ’_TOS_’ definiert, und der Compiler bearbeitet den if-Zweig, compiliert also das ’FRDrawRect’, das VDI-Ausgaben macht. Sollte ich SYS.C aber unter DOS auf einem anderen Compiler compilieren, so ist '_TOS_’ nicht definiert, der Compiler wĂŒrde also den else-Zweig bearbeiten, der in unserem Beispiel noch leer ist. In diesem else-Zweig wĂŒrde man nun auch eine Funktion ’FRDrawRect’ definieren, die aber DOS-Bildschirmausgaben macht. Der Compiler wird dabei nicht einen Fehler ’doubly defined Symbol’ oder Ă€hnliches ausgeben, da er ja nur einen Zweig der if-else-Konstruktion bearbeitet. Wichtig ist, daß die Prototypen fĂŒr die VDI- und DOS-Funktion gleich sind. Deshalb stehen sie im Header SYS.H auch nicht in einer Compiler-if-Anweisung.

Die Konstanten-Makros hingegen sind wieder in eine if-Anweisung eingeklammert. Sie stellen bestimmte Tastatureingaben dar und sind deshalb betriebssystemabhĂ€ngig. Man kann auch hier den else-Zweig durch entsprechende Neudefinitionen der Makros, die fĂŒr TOS schon aufgefĂŒhrt sind, fĂŒr ein anderes System, z.B. DOS, ergĂ€nzen. Genauso verhĂ€lt es sich mit den ’#include’-Anweisungen. Je nach System werden andere Header eingebunden, daher sind auch diese Anweisungen in eine Compiler-if-else-Konstruktion eingebettet.

Arbeitsweise

Zusammengefaßt funktioniert die ganze Konstruktion wie in Bild 1 dargestellt. Das Hauptprogramm in FRAME.C, das nun gĂ€nzlich betriebssystemunabhĂ€ngig ist, ruft die Bibliotheksinitialisierung auf und teilt der Bibliothek mittels der Parameterliste mit, wo sie bestimmte Funktionen finden kann. Diese Funktionen stammen alle aus der Datei SYS.C. Die Bibliothek kann jetzt jederzeit auf Funktionen von SYS.C zugreifen, und zwar je nach System auf die passende.

Das hat den Effekt, daß das Programm ohne AbĂ€nderungen auf zwei Systemen compilierbar ist und lĂ€uft, wobei die Bibliothek MOVERECT noch nicht einmal neu compiliert werden muß, da sie systemunabhĂ€ngig ist. Sie kann als Object-File dazugelinkt werden.

Falls Sie die Listings abtippen sollten und noch Einsteiger sind, hier noch ein paar Worte zum konkreten Vorgehen: Sichern Sie die Quellcodes unter den Namen wie angegeben. Compilieren und linken Sie dann das Projekt MOVERECT. Evtl, mĂŒssen Sie in der Projektdatei den Destination-Pfad an Ihre Konfiguration anpassen. Sorgen Sie dafĂŒr, daß das Ergebnis des Linkvorgangs in den Ordner der Bibliotheken (meist LIB) geschrieben wird, und kopieren Sie den Header MOVERECT.H in den Ordner der Header (meist INCLUDE). Compilieren und linken Sie nun das Projekt FRAME und es mĂŒĂŸte ein lauffĂ€higes FRAME.TOS entstehen.

Aufgepaßt!

Im Zusammenhang mit der Entwicklung dieses Konzepts stieß ich bei der Portierung auf DOS leider auch auf Probleme. Zum Beispiel erwartete der DOS-Compiler eine Leerzeile nach der letzten Anweisung im Programmtext. Das ist weiter nicht schlimm, aber ein echtes Problem war, daß DOS nur ein 16-Bit-System ist. Das wirkt sich zum Beispiel auf das Reservieren von Speicherplatz aus. Man kann hier nicht einfach 1 MB alloziieren, sondern höchstens 65 KB! Mit einem Trick lĂ€ĂŸt sich diese EinschrĂ€nkung umgehen, nĂ€mlich mit dem sogenannten ’__huge'-Pointern. NĂ€heres dazu entnehmen Sie der Online-Hilfe bzw. den HandbĂŒchern Ihres Compilers.

Fazit

Wie gesagt, dies ist natĂŒrlich nur ein kleines Beispiel. In der Praxis sind bei meiner Arbeit schon weitaus umfangreichere Bibliotheken entstanden. Dazu habe ich Rahmenprogramme entwickelt, die dem hier abgedruckten sehr Ă€hneln. Diese Rahmenprogramme kann ich dank der Compileranweisungen in SYS.C jederzeit auf verschiedenen Systemen compilieren, linken und starten. Außerdem wirken sich Änderungen in den Bibliotheken sofort auf die Programmversionen aller Systeme aus, da von allen Versionen dieselbe Bibliothek benutzt wird. Ich brauche nicht fĂŒr jede Version die Änderungen an der Bibliothek wiederholen.

Dieses Konzept ist also eine durchaus praktische Sache und hat sich in der Praxis bewÀhrt.

Anmerkung:

Die hier veröffentlichten Listings erhalten Sie ĂŒber unsere Spezial-Diskette Nummer 6/97, unser Internet-Angebot oder ĂŒber die Mailbox.

Listings zu "Portierung leichtgemacht"

Listing 1:

; Die Projektdatei von FRAME FRAME.TOS .L[-G -L -Y -F] .C[-C -Y -T -J -Z -M -R -K -P] = PCSTART.O FRAME.C SYS.C MOVERECT.O PCSTDLIB.LIB PCEXTLIB.LIB PCTOSLIB.LIB PCGEMLIB.LIB PCFLTLIB.LIB

Llsting 2:

/************************************/ /* Das Rahmenprogramm zu MOVERECT */ /************************************/ #include <moverect.h> #include "sys.h" /* Hauptprogramm */ int main( void ) { if( !MInitMOVERECT( FRInitGraphicOutput, FRDrawRect, FRExitGraphicOutput, FRGetCommand, 10, 10 ) ) return( -1 ); if( !MInitMove() ) return( -1 ); MDoMove(); MExitMove(); return( 0 ); }

Listing 3:

/************************************/ /* Das Rahmenprogramm zu MOVERECT */ /************************************/ #ifndef __SYS #define _SYS int FRInitGraphicOutput( int *scr_w, int *scr_h ); void FRExitGraphicOutput ( void ); void FRDrawRect( int x, int y, int w, int h ); int FRGetCommand( void ); #ifdef __TOS__ #define KEY_ESCAPE 27 #define KEY_CRSUP 72 #define KEY_CRSDOWN 80 #define KEY_CRSLEFT 75 #define KEY_CRSRIGHT 77 #else #endif #endif

Listing 4:

/************************************/ /* Das Rahmenprogramm zu MOVERECT */ /************************************/ #include <moverect.h> #include "sys.h" #ifdef ___TOS__ #include <aes.h> #include <ext.h> #include <vdi.h> #else #endif int v_handle; #ifdef __TOS__ int FRInitGraphicOutput( int *scr_w, int *scr_h ) { int i; int work_in[11], work out[57], dummy; /* Handle holen */ v_handle = graf_handle{ &dummy, &dummy, &dummy, &dummy ); for( i = 0; i < 10; work_in[i++] = 1 ); work_in[10] = 2; /* Und Workstation öffnen */ v_opnvwk{ work_in, &v_handle, work_out ); /* Falls es nicht geklappt hat, abbrechen */ if ( !v_handle ) return( 0 ); *scr_w = work_out[0] + 1; *scr_h = work_out[1] + 1; return( 1 ); } void FRExitGraphicOutput( void ) { v_clsvwk( v_handle ); } void FRDrawRect( int x, int y, int w, int h ) { int pxy[4]; pxy[0] = x; pxy[1] = y; pxy[2] = x + w - 1; pxy[3] = y + h - 1; vswr_mode ( v_handle, MD_XOR ); vsf_interior{ v_handle, FIS_HOLLOW ); v_bar{ v_handle, pxy ); } int FRGetCommand( void ) { switch( getch() ) { case KEY_ESCAPE: return( M_QUIT ); case KEY_CRSUP: return( M_RECTUP ); case KEY_CRSDOWN: return( M_RECTDOWN ); case KEY CRSLEFT: return ( M_RECTLEFT ); case KEY_CRSRIGHT: return( M_RECTRIGHT ); default: return ( 0 ); } } #else #endif

Listing 5:

- ; Die Projektdatei von MOVERECT F:\EWS\PUREC\LIB\MOVERECT.O .L[-G -L -Y -J -F -V] .C[-C -Y -T -J -Z -M -R -K -P] = M_MAIN.C M_DRAW.C

Listing 6:

/******************************************/ /* Der Header zu MOVERECT */ /******************************************/ #ifndef __MOVERECT #define __MOVERECT #define M_QUIT 1 #define M_RECTUP 2 #define M_RECTDOWN 4 #define M_RECTLEFT 8 #define M_RECTRIGHT 16 /* Das Modul initialisieren */ int MInitMOVERECT { int (*InitGraphicOutput) (int *scr_wf int *scr_h), void (*DrawRect) (int x, int y, int w, int n), void (*ExitGraphicOutput) (void), int (*GetCommand) (void), int rect_w, int rect_h ); /* Das Bewegen eines Rechtecks initialisieren */ int MInitMove( void ); /* Das Rechteck bewegen */ void MDoMove( void ); /* Das Bewegen eines Rechtecks abmelden */ void MExitMove( void ); #endif

Listing 7:

/******************************************/ /* Der Header zu MOVERECT */ /******************************************/ #ifndef __MOVERECT #define __MOVERECT #define M_QUIT 1 #define M_RECTUP 2 #define M_RECTDOWN 4 #define M_RECTLEFT 8 #define M_RECTRIGHT 16 /* Das Modul initialisieren */ int MInitMOVERECT { int (*InitGraphicOutput) (int *scr_wf int *scr_h), void (*DrawRect) (int x, int y, int w, int n), void (*ExitGraphicOutput) (void), int (*GetCommand) (void), int rect_w, int rect_h ); /* Das Bewegen eines Rechtecks initialisieren */ int MInitMove( void ); /* Das Rechteck bewegen */ void MDoMove( void ); /* Das Bewegen eines Rechtecks abmelden */ void MExitMove( void ); #endif

Listing 8:

/**********************************************/ /* Die Strukturdefinitionen von MOVERECT */ /**********************************************/ #ifndef __M_STRUCT #define __M_STRUCT typedef struct { int (*InitGraphicOutput) (int *scr_w, int *scr_h); void (*DrawRect) (int x, int y, int w, int h); void (*ExitGraphicOutput)(void); int (*GetCommand)(void); } M_HOOKTABLE; typedef struct { int scr_w, scr_h; int x, y, w, h; } M_GLOBALS; #endif

Listing 9:

/*****************************/ /* Das Modul MOVERECT */ /*****************************/ #ifndef __M_DRAW #define __M_DRAW /* Ein Rechteck malen */ void MDrawRect( void ); #endif