7. Tip's in Verbindung mit Turbo-C bzw. Borland C++

English


7.1. Autor
7.2. Warum externe Routinen für PB3 in C schreiben/verwenden
7.3. Speicher-Modell angleichen
7.4. Einschränkungen durch den PowerBASIC 3.x Compiler/-Linker
7.5. Parameterübergabe
7.6. PowerBASIC-Beispiel
7.7. Zugehöriger C-Modul
7.8. Der zur C-Routine gehörige Assembler-Code
7.9. Verwendung von Routinen fremder C-Bibliotheken
7.10. Massnahmen bei Verwendung von PB V2.1

7.1. Autor

Die Tip's für PowerBASIC in Verbindung mit C-Compilern wie Turbo-C bzw. Borland C++ wurden freundlicherweise von:

Andras Hoeffken (ah@confusion.rmc.de)
Andras Hoeffken (2:2480/13.34 @ fidonet)
Andras Hoeffken (130:1316/103 @ basnet-Germany)

für diese FAQ zur Verfügung gestellt.

Sehr nützliche Hinweise sind auch in der Datei CTOPB.FAQ enthalten, diese gehört zum Lieferumfang von PB Vs. 3.2.

7.2. Warum externe Routinen für PB3 in C schreiben/verwenden


7.3. Speicher-Modell angleichen

Für das Linken von *.EXE-Files können (z.B. bei MASM oder C) verschiedene Speichermodelle vorgegeben werden, z.B. Tiny, Small, Medium, Compact, Large, Huge, ...

Für die von PowerBASIC erzeugten *.EXE gilt nur:
Der C-Compiler muß daher in seinem Menü:

7.4. Einschränkungen durch den PowerBASIC 3.x Compiler/-Linker

a) Der PB3-Linker kann nur .OBJ-Module mit EINEM Datensegment einbinden, bei einem 2. Datensegment oder einer DGROUP streikt der PB3-Linker und erzeugt Errors. C-Compiler verwenden in der Grundeinstellung zunächst immer mehrere Datensegmentnamen, die in der Datengruppe DGROUP zusammengefaßt sind (das hat bestimmte Vorteile). Die IDE des C-Compilers muß daher in ihrem Menü "Options / Compiler / Names" wie folgt eingestellt werden:
           Code Segment: _TEXT          Bss Segment:      _DATA
           Code Group:                  Bss Group:
           Code Class:   TEXT           Bss Class:        DATA
           Data Segment: _DATA          Far Data Segment:
           Data Group:                  Far Data Group:
           Data Class:   DATA           Far Data Class:
           (die noch vorhandenen Sternchen MÜSSEN gelöscht werden)
Jetzt erzeugt der C-Compiler nur noch EINEN Daten-Segmentnamen und keine DGROUP mehr!

b) Der PB3-Linker (< Vs. 3.2) akzeptiert keine "_" bei Segmentnamen (ist bei C ein Standard), daher: In der $LINK-Zeile "$ALIAS" verwenden (s.u.)!

c) Der PB3-Compiler (< Vs. 3.2) akzeptiert keine "_" in Namen von Funktionen und SUB's (ist bei C ein Standard), daher: In den DECLARE- Zeilen "$ALIAS" verwenden (s.u.)!

d) Der PB3-Compiler übergibt bei Funktionen und Subs die Parameter in der Reihenfolge "von rechts nach links" (Pascal Konvention) und er- wartet, daá die externe Routine den Stack selbst aufräumt, C- Compiler arbeiten umgekehrt. Daher: In den DECLARE-Zeilen "CDECL" verwenden (s.u.)!

7.5. Parameterübergabe

PB3 übergibt Parameter an externe Routinen auf 2 Arten:
Entsprechend müssen die Deklarationen in den C-Routinen angepaßt werden.

7.6. PowerBASIC-Beispiel

Im nachfolgenden .BAS-Programm zeigen 2 Routinen die Mechanismen:
    'PB3_TBC.BAS - Turbo-C-Routinen in PB3.x einlinken

    $ALIAS DATA AS "_DATA"  'Zuordnung eines(!) Segmentnamens
    $LINK "pb3_tbc.obj"     'C-Spezialeinstellung ohne DGROUP!

    DEFINT A-Z
    DECLARE FUNCTION addab CDECL ALIAS "_addab" (a, BYVAL b, c)
    DECLARE SUB chst CDECL ALIAS "_chst" (word, word, integer)

    CLS: a = 7: b = 1: c = 0
    PRINT "c_vorher                =";c         'c=0
    x = addab (a, b, c)                         'A: c=a+b
    PRINT "c_nachher = a(7) + b(1) =";c         'c=8
    PRINT "x = c_nachher + 1       =";x         'x=9

    a$ = "hallo"
    CALL chst (strseg(a$), strptr(a$), len(a$)) 'B: Change String
    PRINT "Geänderter String      = ";a$        'druckt "*allo"
    END

7.7. Zugehöriges C-Modul

    /* Modul PB3_TBC.C  */

    #include <dos.h>  /* C-Bibliotheksfunk. (f. Strings) einbinden */

    static int d = 1;          /* d und e kommen ins Datensegment */
    static int e;              /* d und e sind KEINE Basic-Variablen */

    int addab(int far *a, int b, int far *c)
    {
        *c = *a + b;
         e = *c + d;
         return e;
    }

    void chst(unsigned far *stseg, unsigned far *stofs, int far *stlen)
    {
        char far *stdata;      /* Pointer auf den 1. String Char */

        if (*stlen)            /* falls Stringlänge > 0 */
        {
        stdata = (char far *) MK_FP(*stseg, *stofs); /* Pointer holen */
        if (stdata)            /* falls gültiger String */
            *stdata = '*';     /* 1. Char austauschen */
        }                      /* (Stringlänge NICHT überschreiben !! */
    }

7.8. Der zur C-Routine gehörige Assembler-Code:

Für alle, die C nicht so genau kennen: Der ASM-Code gibt einen Eindruck, wie kompakt und schnell der vom C-Compiler erzeugte Code ist.

Zur Gewinnung des nachstehenden Codes wurde zuerst der obige Modul PB3_TBC.C als PB3_TBC.OBJ compiliert, dann wurde mit einem Dis- assembler für .OBJ-Files (OBJ2ASM.EXE) das nachstehende File PB3_TBC.ASM erzeugt (da weiss man was man hat).

(Man kann auch die IDE des C-Compilers beauftragen, den C-Modul in einen ASM-Modul zu übersetzen und abzuspeichern.)
    ;File PB3_TBC.ASM

    _TEXT  SEGMENT BYTE PUBLIC 'CODE'
    _TEXT  ENDS

    _DATA  SEGMENT WORD PUBLIC 'DATA'
                                ;Achtung: kein BSS-Segment, keine DGROUP !!
    _DATA  ENDS

    PUBLIC _addab
    PUBLIC _chst

    _TEXT   SEGMENT
    assume cs: _TEXT
    assume ds: _DATA

    _addab:
    push   bp
    mov    bp,sp
    ; c = a + b:
    les    bx,dword ptr [bp+006h] ; a
    mov    ax,es:[bx]
    add    ax,[bp+00Ah]           ;   + b
    les    bx,dword ptr [bp+00Ch]
    mov    es:[bx],ax             ; c
    ;e = c + d:
    mov    ax,es:[bx]             ;c (Zeile kann beim Optimieren entfallen)
    add    ax,$S1                 ;   + d
    mov    dx,seg _DATA
    mov    es,dx
    mov    es:$S2,ax              ; e
    ;return e:
    mov    ax,seg _DATA           ; (Zeile kann beim Optimieren entfallen)
    mov    es,ax                  ; (Zeile kann beim Optimieren entfallen)
    mov    ax,es:$S2              ; ax = e
    pop    bp
    retf

    _chst:
    push   bp
    mov    bp,sp
    sub    sp,+004h
    les    bx,dword ptr [bp+00Eh] ; len(a$)
    cmp    word ptr es:[bx],+000h ; null?
    jz     $L3                    ; ja
    les    bx,dword ptr [bp+006h] ; strseg(a$) - Segment
    mov    ax,es:[bx]
    les    bx,dword ptr [bp+00Ah] ; strptr(a$) - Offset
    mov    dx,es:[bx]
    mov    [bp-002h],ax           ; eigener (echter) Pointer
    mov    [bp-004h],dx
    mov    ax,[bp-004h]
    or     ax,[bp-002h]           ; Pointer = 0?
    jz     $L3                    ; ja
    les    bx,dword ptr [bp-004h] ; Zeiger auf 1. String-Char
    mov    byte ptr es:[bx],2Ah   ; Char mit '*' überschreiben
    $L3:
    mov    sp,bp
    pop    bp
    retf

    _TEXT        ENDS

    _DATA        SEGMENT
    $S1     dw     00001h  ; initialisierte Daten
    $S2     dw     00000h  ; nicht initialisierte Daten (hier NICHT im
                           ; BSS-Segment!)
   _DATA        ENDS

    END

7.9. Verwendung von Routinen fremder C-Bibliotheken:

Obwohl PB V3.x schon viel besser an die C-Konventionen angepaßt ist als PB V2.1, ist es meist immer noch nicht möglich, .OBJ Files, die dem normalen C-Standard entsprechen, sofort einzubinden. Grund: C generiert mehrere Datensegmente zusammen mit DGROUP. Als Abhilfe bietet sich wohl nur der Umweg über den Quellcode an, der dann nach den Gesichtspunkten von Abschn. 7.3 + 7.4a neu compiliert/assembliert werden muß. Es gibt 2 Möglichkeiten:

7.10. Massnahmen bei Verwendung von PB V2.1:

Der Unterschied zwischen PB V3.x und V2.1 besteht hier in folgendem:
PB V2.1 kennt keine "$ALIAS"-Anweisungen. Da PB keine "_" (underscores) verarbeiten kann, müssen in der IDE des C-Compilers weitere Änderungen eingestellt werden:
PB V2.1 kennt die CDECL-Anweisung nicht. Man muss daher im C-Quellcode die Anweisung hineineditieren, dass die C-Funktionen nach der PASCAL- Convention (dies entspricht der PB-Convention) zu compilieren sind. In den Beispielen aus Abschn. 7.6 und 7.7 sind dann die nachstehend geänderten Zeilen zu verwenden:
        DECLARE FUNCTION addab (a, BYVAL b, c)
        DECLARE SUB chst (word, word, integer)

        int pascal addab(int far *a, int b, int far *c)
        void pascal chst(unsigned far *stseg, unsigned far *stofs,
                                                       int far *stlen)


(c) 1995/2007 by Thomas Gohel and Andras Höffken, All rights and bug's reserved