7. Tip's in Verbindung mit Turbo-C bzw. Borland C++
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
- C-Routinen ergeben schnell laufenden Code
- für extrem schnellen Code: C-Source schreiben und in ASM-Source
übersetzen lassen (geht VIEL schneller als ASM-Source direkt
zu schreiben), dann den ASM-Quellcode optimieren (Beispiel: s.u.)
- Routinen fremder C-Bibliothen können mit PB verwendet werden
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:
-
PowerBASIC 3.x - Speichermodell = LARGE
(PB benutzt für Code- und Datensegmente 32-bit FAR Pointer)
Der C-Compiler muß daher in seinem Menü:
-
Options / Compiler / Code Generation / Model
auf das Modell LARGE eingestellt werden.
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:
- mit 'far pointern' ('by reference' bzw. 'by copy')
- direkt auf dem Stack (BYVAL)
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:
- in der Addier-Funktion "addab" (integer) wird "c=a+b" berechnet, der
Funktionswert wird mit "x=c+1" zurückgegeben.
- in der String-Routine "chst" wird das erste Zeichen eines Strings
durch "*" ersetzt.
'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:
- Bei Borland C++ Vs. 3.1 wird für die meisten Runtime-Bibliotheken der
ges. Quellcode mitgeliefert einschliesslich einer Anleitung und Make-
Files, wie man Bibliotheksmodule abwandeln und dann neu generieren
kann.
- Liegt für ein .OBJ Modul kein Quellcode vor, gelingt es bei kleineren
Modulen mit einem .OBJ-Disassembler oft, den zugehörigen .ASM-
Quellcode zu generieren. Dieser kann dann nach Anpassung der Datensegment-
Definitionen neu assembliert werden.
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:
- Bei den Segmentnamen (vergl. Abschn. 7.4a) sind die "_" zu entfernen
- Im Menü "Options / Compiler / Advanced Code Generation" ist der
Punkt "Generate underbars" zu deaktivieren.
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)