Das ATOS-Magazin 3/98
ATOS Programmierpraxis
GEMScript Teil II

# GS_COMMAND: Aye, Sir.

Wenn GS_REQUEST und GS_REPLY zwischen den Applikationen ausgetauscht wurden, steht die GEMScript-Verbindung im Prinzip und es kann losgescripted werden. Dreh- und Angelpunkt der Kommunikation ist hier die Message GS_COMMAND (0x1352), deren Verständnis seitens der Applikationen mittels Eintragen von GSM_COMMAND in msgs in der GS_INFO-Struktur ja bereits bestätigt wurde.

Der Aufbau der GS_COMMAND-Message ist folgender:

    msg[0] GS_COMMAND (0x1352 (4946))
    msg[1] die ID des Versenders
    msg[2] 0
    msg[3]
        +  Pointer auf Kommandozeile
    msg[4]
    msg[5] 0
    msg[6] 0
    msg[7] Gemscript-ID

Diese Message enthält in ev_mgpbuff[3] und [4] grundsätzlich einen Zeiger auf eine Kommandozeile, deren Aufbau wir ja in Teil I schon kurz gezeigt hatten. Was tun wir nun, wenn wir eine solche Message erhalten?

Fangen wir doch erst einmal damit an, uns das Kommando und die dazugehörigen Parameter aus der Kommandozeile zu extrahieren. Dazu schreiben wir uns, wie sollte es auch anders sein, eine Funktion, die ungefähr so aussehen könnte (ich beschränke mich jetzt mal auf zwei Kommandos):

    #define GS_OPEN   1
    #define GS_CLOSE  2

    PARLIST *extract(char *cmdline, int *command)
    {
        char *param;
        PARLIST *first, *curr;

        /* Kommando aus der cmdline holen
         */
        if(stricmp(cmdline, "open")==0)
            *command=GS_OPEN;
        if(stricmp(cmdline, "close")==0)
            *command=GS_CLOSE;

        /* Ersten Parameter holen bzw. nachgucken, ob überhaupt einer drin ist
         */
        param = cmdline + strlen(cmdline) + 1;
        if(*param==0) return(0);

        /* Und jetzt alle Parameter bis zum doppelten Nullbyte extrahieren
         */
        curr = first;
        do
        {
            curr = malloc(sizeof(PARLIST));
            curr->param = malloc(strlen(param)+1);
            strcpy(curr->param, param);

            curr = curr->next;
            param = param + strlen(param) +1;
        } while(*param!=0);

        curr->next=NULL;

        return(first);
    }

Für die Parameter verwende ich hier eine einfach verkettete Liste, die eine Struktur

    typedef struct parlist
    {
        char *param;
        struct parlist *next;
    } PARLIST;

benutzt. Das erscheint mir als der einfachste Weg, auch mit wirklich beliebig vielen Parametern fertig zu werden.

Daß die einzelnen Bestandteile der Kommandozeile durch Nullzeichen getrennt sind, kommt uns hier natürlich sehr gelegen, da das Auflösen der Kommandozeile so einfach durch strcpy, strlen und stricmp (die Kommandos müssen ohne Unterscheidung von Groß- und Kleinschreibung differenziert werden) vorgenommen werden kann.

extract() wird also vom Messagehandler der GS_COMMAND empfangenden Applikation mit der Kommandozeile und einem Zeiger auf eine Integer-Variable aufgerufen und gibt einen Zeiger auf das erste Element einer verketteten Liste PARLIST zurück. In PARLIST->next steht jeweils ein Zeiger auf den nächsten Parameter, der am Ende der Liste NULL ist. Wenn der Rückgabewert von extract() direkt NULL ist, enthält das Kommando keine Parameter. Das aus der Kommandozeile extrahierte Kommando steht nach dem Aufruf in der übergebenen Variable command, und zwar in Form der oben definierten Konstanten.

Wenn die Kommandozeile derart aufgedröselt wurde und nicht mehr weiter benötigt wird, können wir GS_ACK (0x1353) an die Versenderapplikationen verschicken, die daraufhin den Speicher für die Kommandozeile wieder freigeben:

    msg[0] GS_ACK (0x1353 (4947))
    msg[1] ap_id
    msg[2] 0
    msg[3]
        +  exakt die Werte der empfangenen GS_COMMAND-Nachricht
    msg[4]
    msg[5]
        +  Ergebnis bzw. Fehlermeldung als ASCIIZZ-Text (s.u.) oder NULL
    msg[6]
    msg[7] 0: (GSACK_OK)      OK, Kommando wurde oder wird ausgeführt
           1: (GSACK_UNKNOWN) Kommando unbekannt
           2: (GSACK_ERROR)   Fehler (Kommando nicht ausgeführt)

In ev_mgpbuff[3] und [4] schicken wir also einfach einen Zeiger auf die empfangene Kommandozeile, bei Index 7 den entsprechenden Rückgabewert an die andere Applikation zurück.

Für die weitere Verarbeitung der Kommandos gibt es nun mehrere Möglichkeiten. Empfehlenswert ist auf jeden Fall die Implementation eines Dispatchers, der nach dem Aufrufen von extract() aufgerufen wird und entsprechende Aktionen vornimmt. Nehmen wir z.B. einmal ein Programm an, das mittels einer Funktion FileOpen(char *path) eine Datei öffnet. Dann könnte der Dispatcher folgendermaßen aussehen:

    int gs_dispatch(int command, PARLIST *first)
    {
        PARLIST *curr_par = first;

        switch(command)
        {
            case GS_OPEN:   do{
                                FileOpen(curr_par);
                                curr_par = curr_par->next;
                            } while(curr_par!=NULL)
                            break;
        }

        return(0);
    }

So würde also anhand des Kommandos command und des ersten Elements der Parameterliste first die Funktion FileOpen nacheinander mit den übergebenen Dateipfaden aufgerufen.

Abschließend brauchen wir eigentlich nur noch die PARLIST wieder freizugeben:

    void destroy_parlist(PARLIST *first)
    {
        PARLIST *curr = first, *next;

        do{
            free(curr->command);
            next = curr->next;
            free(curr);
            curr = next;
        } while(curr!=NULL);
    }

Und fertig ist die Interpretation des GEMScript-Open-Kommandos mit beliebiger Parameterzahl. Die hier gezeigten Funktionen sollten leicht erweiterbar sein und können als Gerüst für die Implementation in eigene Programme dienen.

Und jetzt wollen wir den Aufbau der weiteren Standard-GEMScript-Kommandos und eventuell auftretende Probleme erläutern.

Close | Das Gegenstück zu open. Parameter sind optional einer oder mehrere Dateinamen, wenn kein Parameter übergeben wurde ist die dem obersten Fenster entsprechende Datei zu schließen.
Copy | Parameter ist optional ein Dateiname. Beim Eintreffen dieses Kommandos soll die Selektion des dem Dateinamen entsprechenden (oder des obersten) Fensters auf das Clipboard kopiert werden.
Cut | Siehe copy. Nur eben nicht Kopieren, sondern Ausschneiden. ;-)
Delete | Wie copy oder cut, nur daß die Selektion nur gelöscht und nicht aufs Clipboard geschrieben werden soll.
GetFront | Bei GS_ACK soll der Dateiname der obersten Datei in ev_mgpbuff[5] und [6] zurückgegeben werden.
New | Das empfangende Programm soll eine neue Datei anlegen. Hierzu existieren keine Parameter, was es Grafikprogrammen erschweren wird, ohne weiteres etwas mit diesem Kommando anzufangen. Die beste Parameterfolge für Rastergrafikprogramme scheint mir hierbei "Breite in Pixeln\0Höhe in Pixeln\0Farbtiefe in Bit" zu sein. Das ist auch die in Smurf implementierte Parameterfolge. Hier sollte eine Einführung genauer spezifizierter Standards stattfinden.
Paste | Die Applikation soll den Inhalt des Klemmbretts in die Datei einfügen, die als Parameter übergeben wurde. Wenn kein Parameter enthalten ist, ist der Clipboardinhalt ins oberste Dateifenster einzufügen.
Print | Parameter sind hier eine oder mehrere Dateinamen. Die angegebenen Dateien sollen ausgedruckt werden. Auch hier existieren z. Zt. keine genaueren Spezifikationen über Ausgabeskalierung, Farbe oder s/w, zu verwendenden Druckertreiber oder ähnliches.
Quit | Die Applikation soll sich beenden.
Save | Die als Parameter übergebene(n) Datei(en) (sofern geöffnet) speichern.
SaveAs | Die Applikation soll eine Datei unter einem neuen Namen speichern. Einer oder zwei Parameter: der erste ist der neue Dateiname, der zweite bezeichnet das zu speichernde Fenster. Wenn der zweite Parameter fehlt, ist die zuoberst liegende Datei zu speichern. Was an diesem Kommando vermißt werden könnte, ist die optionale Angabe eines Dateiformats. Es gibt hier nur die Möglichkeit, dieses anhand des neuen Dateinamens (bzw. dessen Extension) zu erkennen.
SelectAll | Als Parameter kann eine Datei übergeben werden. Die Applikation soll den gesamten Inhalt des dazugehörigen Fensters selektieren. Wenn der Parameter fehlt, ist der Inhalt der zuoberst liegenden Datei auszuwählen.
ToFront | Die Applikation soll ein Fenster in den Vordergrund bringen. Als Parameter wird auf jeden Fall der Name der zu toppenden Datei übergeben.
Undo | Die Applikation soll die letzte Aktion in der als Parameter übergebenen Datei rückgängig machen. Wenn kein Parameter übergeben wird, ist Undo auf die zuoberst liegende Datei anzuwenden.
AppGetLongName | Enthält keine Parameter. Die Applikation soll mit GS_ACK ihren Namen in langer Form zurückgeben. Die vom Betriebssystem ermittelbaren Prozeßnamen von Programmen können maximal acht Zeichen lang sein. Da dies je nach Programm nicht unbedingt ausreichend oder aussagekräftig ist, kann hier ein längerer Name (hierbei soll auf Versionsinformationen verzichtet werden!) zurückgegeben werden.

In der nächsten Ausgabe schließlich werden wir uns ein wenig näher mit dem Feature der Aufnahmefähigkeit beschäftigen. Dahinter verbirgt sich die Möglichkeit, Scripte nicht durch manuelle Programmierung zu schreiben, sondern Aktionen innerhalb eines Programmes wie auf Band "aufzunehmen" und als GEMScript zu speichern, um sie später wieder abzurufen. Gerade für User, die keine Programmiererfahrung haben, erleichtert ein solches Feature den Umgang mit GEMScript erheblich. Wie die Aufnahmefähigkeit zu realisieren ist, lesen Sie in der nächsten ATOS.

Olaf Piesche