7. Hints in Connection with Turbo-C or Borland C++

Deutsch


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


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: The Borland C compiler in his menu:

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:
The declarations in the C routines must be selected correspondingly!

7.6. PowerBasic example

In the following .BAS program 2 routines show the mechanisms:
    '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:

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: 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)



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