7. Hints in Connection with Turbo-C or Borland C++
Short overview:
7.1. The author
7.2. Why write/use external routines for PB3 in C
7.3. The correct memory model
7.4. Limitations by the PowerBASIC 3.x compiler/-linker
7.5. Passing Parameters
7.6. PowerBasic example
7.7. Corresponding C module
7.8. The assembler code, corresponding to the C module
7.9. Usage of routines from external C libraries
7.10. Preparations if PB V2.1 is used
7.1. The author
The hints for "PowerBASIC in cooperation with C compilers like Turbo-C or
Borland C++ have been contributed to This FAQ by:
Andras Hoeffken (ah@confusion.rmc.de)
Andras Hoeffken (2:2480/13.34 @ fidonet)
Andras Hoeffken (130:1316/103 @ basnet-Germany)
Very useful hints can also be found in the file CTOPB.FAQ contained in the
original PB Vs. 3.2 distribution.
7.2. Why write/use external routines for PB3 in C
- routines produce fast running code
- for extremely fast code: first write a C source, then let the C Compiler
translate this source to ASM code (is done much faster as if one would
write ASM code directly, see example), finally optimize the ASM code
- Routines from external C libraries can be used with PB
7.3. The correct memory model
For the generation of *.EXE files (e.g. by MASM or C + LINK) different
memory models can be selected, e.g. Tiny, Small, Medium, Compact,
Large, Huge, ...
For the *.EXE files, generated by PowerBASIC, the following is valid only:
-
PowerBASIC 3.x - memory model = LARGE
(PB uses 32-bit FAR pointers for both code- and datasegments)
The Borland C compiler in his menu:
-
Options / Compiler / Code Generation / Model
has therefore to be set to the LARGE model.
7.4. Limitations by the PowerBASIC 3.x compiler/-linker
a) The PB3 linker can only link .OBJ modules, which have ONE data segment,
with a 2nd data segment or a DGROUP the PB3 linker refuses work and
produces Errors. - C compilers in their default set-up always use
several data segment names, which are combined i a DGROUP (this has
certain advantages). For the cooperation with PB, the IDE of the
C-Compilers in its menu
"Options / Compiler / Names"
must therefore be set up like:
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:
(the stars, originally present, MUST be erased!)
Now, the C compiler produces only ONE data segment name and no more a
DGROUP !
b) The PB3 linker (< Vs. 3.2) does not accept an "_" with segment names
(standard with C), therefore: use "$ALIAS" in the $LINK line
(see example)!
c) The PB3 compiler (< Vs. 3.2) does not accept "_" in names of FUNCTIONS
and SUBs (standard with C), therefore: use "$ALIAS" in DECLARE lines
(see example)!
d) The PB3 compiler transfers parameters to functions and subs in the
order "from right to left" (Pascal convention) and assumes, that the
external routine purges the stack before returning to PB. C compilers
work in the opposite way. Therefore: use "CDECL" in the DECLARE lines
(see example)!
7.5. Passing Parameters
PB3 passes parameters to an external routine by 2 different ways:
- using 'far pointers' ('by reference' or 'by copy')
- by putting the values directly on the stack (BYVAL)
The declarations in the C routines must be selected correspondingly!
7.6. PowerBasic example
In the following .BAS program 2 routines show the mechanisms:
- in the add-function "addab" (integer) "c=a+b" is calculated, then
"x=c+1" is returned as the result of the function.
- in the string-function "chst" the first character of a string is
replaced by a "*".<(li>
'PB3_TBC.BAS - linking Turbo C functions into PB3.x
$ALIAS DATA AS "_DATA" 'assignment of one(!) segment name
$LINK "pb3_tbc.obj" 'special C set-up without 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_before =";c 'c=0
x = addab (a, b, c) 'A: c=a+b
PRINT "c_after = a(7) + b(1) =";c 'c=8
PRINT "x = c_after + 1 =";x 'x=9
a$ = "hallo"
CALL chst (strseg(a$), strptr(a$), len(a$)) 'B: change string
PRINT "changed string = ";a$ 'prints "*allo"
END
7.7. Corresponding C modul
/* Modul PB3_TBC.C */
#include <dos.h> /* C library function (for strings) */
static int d = 1; /* d and e are put into the same data segment */
static int e; /* d and e are NO PB-variables */
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 to the first string char */
if (*stlen) /* if string length > 0 */
{
stdata = (char far *) MK_FP(*stseg, *stofs); /* fetch the pointer */
if (stdata) /* if we have a valid string */
*stdata = '*'; /* replace first character */
} /* (do NOT overwrite the string length !) */
}
7.8. The assembler code, corresponding to the C module
If someone is uncertain about the code, produced by the C compiler: get
the corresponding ASM code and look, how compact and fast the C generated
code is.
To get the ASM code, shown below, first the above module PB3_TBC.C was
compiled to PB3_TBC.OBJ (with the set-up of par. 7.4 !!), then with a
disassembler for .OBJ-Files (OBJ2ASM.EXE) the following file PB3_TBC.ASM
was gained. (You can instead order the IDE of the C compiler to produce
equivalent ASM code, too. But with an external .OBJ disassembler, you are
quite better sure, what the PB3_TBC.OBJ module really contains)
;File PB3_TBC.ASM (disassembled from PB3_TBC.OBJ)
_TEXT SEGMENT BYTE PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT WORD PUBLIC 'DATA'
;attention: no BSS segment, no 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 (line can be put out for optimization)
add ax,$S1 ; + d
mov dx,seg _DATA
mov es,dx
mov es:$S2,ax ; e
;return e:
mov ax,seg _DATA ; (line can be put out for optimization)
mov es,ax ; (line can be put out for optimization)
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 ; nul?
jz $L3 ; yes
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 ; own pointer
mov [bp-004h],dx
mov ax,[bp-004h]
or ax,[bp-002h] ; pointer = 0?
jz $L3 ; yes
les bx,dword ptr [bp-004h] ; points to first string char
mov byte ptr es:[bx],2Ah ; overwrite char with '*'
$L3:
mov sp,bp
pop bp
retf
_TEXT ENDS
_DATA SEGMENT
$S1 dw 00001h ; initialized data
$S2 dw 00000h ; uninitialized data (here NOT in the
; BSS segment!)
_DATA ENDS
END
7.9. Usage of routines from external C libraries
Although PB v3.x is much better adapted to C conventions than PB v2.1,
it's mostly still NOT possible to immediately link .OBJ modules,
corresponding to the normal C standard. Reason: C generates more than
one data segment together with a DGROUP. To overcome this, one has to use
C or ASM source code and to re-compile/re-assemble these sources with
corresponding to the aspects of par. 7.3 and 7.4a. There are 2
possibilities:
- With Borland C++ for the most runtime libraries the complete source code
is delivered with the standard distribution including instructions, how
to change the sources, and including MAKE files to re-generate own
versions of the library modules.
- If for an .OBJ module no source code is available, it's mostly possible
with small modules to get the corresponding ASM source code by using an
.OBJ-disassembler. Then, after having adapted the definitions of the
data segments correctly to the PB3 conventions, it can be re-assembled
again.
7.10. Preparations if PB V2.1 is used
The difference between PB v3.x and PB v2.1 is like follows:
PB v2.1 does not know a "$ALIAS" instruction. AS PB v2.1 can not work
with "_" (underscores), in the IDE of the C compiler additional switches
have to be changed:
- with segment names (see par 7.4a) "_" are not allowed
- in the menu "Options / Compiler / Advanced Code Generation" one has to
deactivate the switch "Generate underbars".
PB v2.1 does not know the CDECL instruction. One therefore has to put
instructions into the C source code, that the C functions have to be
compiled corresponding to the PASCAL-conventions (this corresponds to the
PB-convention). For this, e.g in the example of par 7.6 and 7.7, the
following (changed) lines have to be used:
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)