LONG: Calling C Routines from Basic -- Part 2 of 2

ID: Q104512


The information in this article applies to:
  • Microsoft Visual Basic Standard and Professional Editions for MS-DOS, version 1.0
  • Microsoft Basic Professional Development System for MS-DOS, version 7.1
  • Microsoft QuickBASIC for MS-DOS

... continued from part 1 ...

COMMON BLOCKS

You can pass individual members of a Basic common block in an argument list, just as you can any data. However, you can also give a C routine access to the entire COMMON block at once.

C can reference the items of a COMMON block by first declaring a structure with fields that correspond to the COMMON block variables. Having defined a structure with the appropriate fields, the C routine must then get the address of the COMMON block.

To pass the address of the COMMON block, pass the address of the first variable in the block. The C routine should expect to receive a structure by reference.

Passing arrays through the COMMON block is done in a similar fashion. However, only static arrays can be passed to C through COMMON.

Microsoft does not support passing dynamic arrays through COMMON to C (since this depends upon a Microsoft proprietary dynamic array descriptor format that changes from version to version). Dynamic arrays can be passed to C only as parameters in a CALL statement.

CALLING MS-DOS I/O ROUTINES DOES NOT AFFECT Basic CURSOR POSITION

C Routines linked with a Basic program that does screen output (by using printf, puts, and so on) do not update the cursor position after returning to the calling Basic program. (NOTE: This also applies to using the CALL INTERRUPT statement in Basic).

For example, after doing the following, the next PRINT statement goes directly after the last Basic PRINT statement, ignoring the new line position from calling the C routine:
  1. Doing a PRINT from Basic


  2. Calling a C routine that does some string display functions (printf)


  3. Returning to Basic


This is expected behavior. C routines should not change the Basic cursor position.

DEBUGGING MIXED-LANGUAGE PROGRAMS

CodeView is useful when trying to debug mixed-language programs. With it, you can trace through the source code of both C and Basic.

To compile programs for use with CodeView, use the /Zi switch on the compile line for both the C and Basic compilers. Then when linking, use the /CO switch.

CodeView is a multilanguage source code debugger supplied with the Professional Edition of Microsoft Visual Basic version 1.0 for MS-DOS.

COMPILING AND LINKING THE SAMPLE PROGRAMS

Following the appendix is a series of examples that demonstrate the interlanguage calling capabilities between Basic and C.

When compiling the sample Basic programs, you can compile with or without the /O (stand-alone library) option. The following uses the stand-alone library to produce a stand-alone Basic program:
BC /O Basicmodulename;
The following uses the run-time library, so that the compiled program needs the run-time library on disk when it is executed:
BC Basicmodulename
When compiling the sample C programs, one of the following compile lines should be used:
CL /c /AM Cmodulename
NOTE: For convenience, the medium memory model is used for the C programs. If the large memory model were used instead (/AL instead of /AM), all pointers would have to be explicitly defined as near.
To link the programs, use the following link line:
LINK /NOE Basicmodulename Cmodulename;

APPENDIX: COMMON PITFALLS

The following common pitfalls are all explained in more detail in the main text. Use this appendix as a checklist when you encounter problems while doing mixed-language programming:
  1. Make certain the version numbers of the two languages are compatible.


  2. Make certain that a C routine is declared with CDECL in Basic or that in C the PASCAL directive is used when defining the procedure. This ensures the same calling convention is being used.


  3. Make sure that the graphic libraries are not included in the C libraries. If the graphic libraries are included, duplicate definition errors will occur.


  4. Use the /NOE switch when linking to avoid duplicate definition errors. If duplicate definition errors still occur, also use the /NOD switch when linking.


  5. Watch for incompatible C functions such as system() and getenv().


  6. When passing strings to C, check for two things:

    1. If a C function will use the string, it should be null-terminated (CHR$(0)).


    2. SSEGADD should be used instead of VARPTR to pass the actual address of the string. VARPTR returns the offset to the string descriptor, not to the string itself.




  7. The C routine should be compiled in the medium or large memory model. If the large memory model is used, then all pointers to Basic variables must be explicitly defined as near.


  8. When using VARSEG, VARPTR, SADD or SSEGADD to pass addresses to assembly language, it is important to check the function definition. Since Basic normally passes all parameters by reference, any parameter that is an address should be declared using BYVAL. If BYVAL is not used, Basic will create a temporary variable to hold the address, then pass a pointer to this variable (in effect, pass a pointer to a pointer).


  9. If you enter the libraries on the link line, then the Basic libraries must precede all others.


PASSING NUMERIC VARIABLES FROM Basic TO C BY NEAR REFERENCE

Basic Program


DECLARE SUB NumericNear CDECL (a%,b&,c!,d#)

a% = 32767
b& = 32769
c! = 123.312
d# = 129381.333#

CLS
CALL NumericNear(a%, b&, c!, d#)
LOCATE 5, 1
END 

C Routine


#include <stdio.h>

void NumericNear(int *a, long *b, float *c, double *d) {
   printf("INTEGER %d\n", *a);
   printf("LONG    %ld\n", *b);
   printf("FLOAT   %f\n", *c);
   printf("DOUBLE  %lf\n", *d);
} 

Output

INTEGER 32767
LONG 32769
FLOAT 123.311996
DOUBLE 129381.333000

PASSING NUMERIC VARIABLES BETWEEN Basic AND C BY FAR REFERENCE

Basic Program


DECLARE SUB NumericFar CDECL (_
        BYVAL p1o AS INTEGER, BYVAL p1s AS INTEGER,_
        BYVAL p2o AS INTEGER, BYVAL p2s AS INTEGER,_
        BYVAL p3o AS INTEGER, BYVAL p3s AS INTEGER,_
        BYVAL p4o AS INTEGER, BYVAL p4s AS INTEGER)

a% = 32767
b& = 32769
c! = 123.312
d# = 129381.333#
CLS
CALL NumericFar(VARPTR(a%), VARSEG(a%),_
                VARPTR(b&), VARSEG(b&),_
                VARPTR(c!), VARSEG(c!),_
                VARPTR(d#), VARSEG(d#))

LOCATE 5, 1
END 

C Routine


#include <stdio.h>
void NumericFar(int far *a, long far *b, float far *c, double far *d)
{

   printf("INTEGER  %d\n", *a);
   printf("LONG     %ld\n", *b);
   printf("FLOAT    %f\n", *c);
   printf("DOUBLE   %lf\n", *d);
} 

Output

INTEGER 32767
LONG 32769
FLOAT 123.311996
DOUBLE 129381.333000

PASSING NUMERIC VARIABLES FROM Basic TO C BY VALUE

Basic Program


DECLARE SUB NumericValue CDECL (_
            BYVAL p1 AS INTEGER,_
            BYVAL p2 AS LONG,_
            BYVAL p3 AS SINGLE,_
            BYVAL p4 AS DOUBLE)

a% = 32767
b& = 32769
c! = 123.312
d# = 129381.333#
CLS
CALL NumericValue(a%, b&, c!, d#)
LOCATE 5, 1
END 

C Routine


#include <stdio.h>
void NumericValue(int a, long b, float c, double d) {
   printf("INTEGER  %d\n", a);
   printf("LONG     %ld\n", b);
   printf("FLOAT    %f\n", c);
   printf("DOUBLE   %lf\n", d);
} 

Output

INTEGER 32767
LONG 32769
FLOAT 123.311996
DOUBLE 129381.333000

PASSING A CHARACTER FROM Basic TO C

Basic


DECLARE SUB test CDECL (BYVAL a%, BYVAL b%)

CALL test(ASC("A"), ASC("B"))
END 

C Routine


#include <stdio.h>
void test(char a, char b)
{

   printf("%c %c\n",a,b);
} 

Output

A B

PASSING A Basic VARIABLE-LENGTH STRING TO C BY FAR REFERENCE

Basic Program


DECLARE SUB StringFar CDECL (BYVAL addr AS LONG, ln AS INTEGER)
CLS
a$ = "This is a test" + CHR$(0)
CALL StringFar(SSEGADD(a$), LEN(a$))

LOCATE 20, 1
END 

C Routine


#include <stdio.h>
void StringFar(char far *a, int *len) {
   int i;
   printf("The string is : %Fs\n\n", a);
   printf(" Index       Value       Character\n");

   for (i=0;i < *len; i++) {

      printf("  %2d          %3d            %c\n", i, a[i], a[i]);
   }
} 

Output

The string is : This is a test
Index Value Character
0 84 T
1 104 h
2 105 i
3 115 s
4 32
5 105 i
6 115 s
7 32
8 97 a
9 32
10 116 t
11 101 e
12 115 s
13 116 t
14 0

PASSING A Basic FIXED-LENGTH STRING TO C BY NEAR REFERENCE

Basic Program


DECLARE SUB StringNear CDECL (_
            BYVAL p1o AS INTEGER,_
            p3 AS INTEGER)

DIM a AS STRING * 15
CLS
a = "This is a test" + CHR$(0)
CALL StringNear(VARPTR(a), LEN(a))
LOCATE 20, 1
END 

C Routine


#include <stdio.h>
void StringNear(char *a, int *len) {
   int i;
   printf("The string is : %s \n\n",a);
   printf(" Index       Value       Character\n");
   for (i=0;i < *len; i++) {
      printf("  %2d          %3d            %c\n", i, a[i], a[i]);
   }
} 

Output

The string is : This is a test
Index Value Character
0 84 T
1 104 h
2 105 i
3 115 s
4 32
5 105 i
6 115 s
7 32
8 97 a
9 32
10 116 t
11 101 e
12 115 s
13 116 t
14 0

PASSING A Basic FIXED-LENGTH STRING TO C BY FAR REFERENCE

Basic Program


DECLARE SUB StringFar CDECL (_
            BYVAL p1o AS INTEGER,_
            BYVAL p1s AS INTEGER,_
            p3 AS INTEGER)

DIM a AS STRING * 15
CLS
a = "This is a test" + CHR$(0)
CALL StringFar(VARPTR(a), VARSEG(a), LEN(a))
END 

C Routine


#include <stdio.h>
void StringFar(char far *a, int *len) {
   int i;
   printf("The string is : %Fs\n\n", a);
   printf(" Index       Value       Character\n");
   for (i=0;i < *len; i++) {
      printf("  %2d          %3d            %c\n", i, a[i], a[i]);
   }
} 

Output

The string is : This is a test
Index Value Character
0 84 T
1 104 h
2 105 i
3 115 s
4 32
5 105 i
6 115 s
7 32
8 97 a
9 32
10 116 t
11 101 e
12 115 s
13 116 t
14 0

PASSING A STRING FROM C TO Basic

Basic Program


DECLARE SUB CSUB CDECL()

TYPE fixstringtype

   B AS STRING * 26

END TYPE
CALL CSUB
END

SUB BASSUB(B AS fixstringtype)
   PRINT B.B
   PRINT LEN(B.B)
END SUB 

C Routine


#include <string.h>

extern void pascal bassub(char *basfixstring);
char thestring[26];
void csub() {
   strcpy(thestring, "This is the string");
   bassub(thestring);

} 

Output

This is the string:
26

PASSING A Basic USER-DEFINED TYPE TO C BY NEAR REFERENCE

Basic Program


TYPE record
   a AS INTEGER
   b AS STRING * 20
   c AS SINGLE

END TYPE

DECLARE SUB TypeReference CDECL (p1 AS record)
CLS
DIM element AS record
element.a = 128
element.b = DATE$ + CHR$(0)
element.c = 39.6
CALL TypeReference(element)
LOCATE 4, 1
END 

C Routine


#include <stdio.h>
struct record {
   int a;
   char b[20];
   float c;
};

void TypeReference(struct record *element) {
   printf("Record.A = %d\n", element->a);
   printf("Record.B = %s\n", element->b);
   printf("Record.C = %f\n", element->c);
} 

Output

Record.A = 128
Record.B = 02-02-1988
Record.C = 39.599998

PASSING A Basic USER-DEFINED TYPE TO C BY FAR REFERENCE

Basic Program


TYPE record
   a AS INTEGER
   b AS STRING * 20
   c AS SINGLE

END TYPE

DECLARE SUB TypeReference CDECL (BYVAL p1o AS INTEGER, _
   BYVAL p1s AS INTEGER)
CLS
DIM element AS record
element.a = 128
element.b = DATE$ + CHR$(0)
element.c = 39.6
CALL TypeReference(VARPTR(element), VARSEG(element))
LOCATE 4, 1
END 

C Routine


#include <stdio.h>
struct record {
   int a;
   char b[20];
   float c;
};

void TypeReference(struct record far *element) {
   printf("Record.A = %d\n", element->a);
   printf("Record.B = %s\n", element->b);
   printf("Record.C = %f\n", element->c);
} 

Output

Record.A = 128
Record.B = 02-02-1988
Record.C = 39.599998

PASSING A Basic INTEGER ARRAY TO C BY FAR REFERENCE

Basic Program


DECLARE SUB IntArray CDECL (BYVAL p1 AS INTEGER, BYVAL p2 AS INTEGER)
DEFINT A-Z
DIM i AS INTEGER
DIM array(10) AS INTEGER
CLS
FOR i = 1 TO 10
   array(i) = i

NEXT i
'Array must be a FAR pointer, so offset and segment must be passed:
CALL IntArray(VARPTR(array(0)), VARSEG(array(0)))
LOCATE 13, 1
PRINT "Back in Basic"
FOR i = 1 TO 10
   PRINT i, array(i)

NEXT i
END 

C Routine


#include <stdio.h>
void IntArray (int far *array) {
   int i;
   printf("Index         Value\n");
   for (i = 0; i <= 10; i++) {
      printf(" %d          %d\n", i, array[i]);
      array[i] = array[i] + 100;
   }
} 

Output

Index Value
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
Back in Basic
1 101
2 102
3 103
4 104
5 105
6 106
7 107
8 108
9 109
10 110

PASSING A Basic ARRAY OF LONG INTEGERS TO C BY FAR REFERENCE

Basic Program


DECLARE SUB LongArray CDECL (_
            BYVAL p1 AS INTEGER,_
            BYVAL p2 AS INTEGER)
DEFINT A-Z
DIM i AS LONG
DIM array(10) AS LONG
CLS
FOR i = 1 TO 10
   array(i) = i + 100

NEXT i
'Array must be a FAR pointer, so offset and segment must be passed:
CALL LongArray(VARPTR(array(0)), VARSEG(array(0)))
LOCATE 13, 1
PRINT "Back in Basic"
FOR i = 1 TO 10
   PRINT i, array(i)

NEXT i
END 

C Routine


#include <stdio.h>
void LongArray(long far *array) {
   int i;
   printf("Index         Value\n");
   for (i=0; i < 11; i++) {
      printf("  %d          %ld\n", i, array[i]);
      array[i] = array[i] + 100;
   }
} 

Output

Index Value
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
Back in Basic
1 101
2 102
3 103
4 104
5 105
6 106
7 107
8 108
9 109
10 110
Index Value 0 0 1 101 2 102 3 103 4 104 5 105 6 106 7 107 8 108 9 109 10 110 Back in Basic 1 201 2 202 3 203 4 204 5 205 6 206 7 207 8 208 9 209 10 210

PASSING A Basic SINGLE-PRECISION ARRAY TO C BY FAR REFERENCE

Basic Program


DECLARE SUB FloatArray CDECL (BYVAL p1 AS INTEGER, BYVAL p2 AS INTEGER)
DEFINT A-Z
DIM i AS SINGLE
DIM array(10) AS SINGLE
CLS
FOR i = 1 TO 10
   array(i) = i + 100

NEXT i
'Array must be a FAR pointer, so offset and segment must be passed:
CALL FloatArray(VARPTR(array(0)), VARSEG(array(0)))
LOCATE 13, 1
PRINT "Back in Basic"
FOR i = 1 TO 10
   PRINT i, array(i)

NEXT i
END 

C Routine


#include <stdio.h>
void FloatArray(float far *array) {
   int i;
   printf("Index         Value\n");
   for (i=0; i < 11; i++) {
      printf("  %d          %f\n", i, array[i]);
      array[i] = array[i]+100;
   }
} 

Output

Index Value 0 0.000000 1 101.000000 2 102.000000 3 103.000000 4 104.000000 5 105.000000 6 106.000000 7 107.000000 8 108.000000 9 109.000000 10 110.000000 Back in Basic 1 201 2 202 3 203 4 204 5 205 6 206 7 207 8 208 9 209 10 210

PASSING A Basic DOUBLE-PRECISION ARRAY TO C BY FAR REFERENCE

Basic Program


DECLARE SUB DoubleArray CDECL (BYVAL p1 AS INTEGER, BYVAL p2 AS INTEGER)
DEFINT A-Z
DIM i AS DOUBLE
DIM array(10) AS DOUBLE
CLS
FOR i = 1 TO 10
   array(i) = i + 100

NEXT i
'Array must be a FAR pointer, so offset and segment must be passed:
CALL DoubleArray(VARPTR(array(0)), VARSEG(array(0)))
LOCATE 13, 1
PRINT "Back in Basic"
FOR i = 1 TO 10
   PRINT i, array(i)

NEXT i
END 

C Routine


#include <stdio.h>
void DoubleArray(double far *array) {
   int i;
   printf("Index         Value\n");
   for (i=0; i < 11; i++) {
      printf("  %d          %lf\n", i, array[i]);
      array[i] = array[i] + 100;
   }
} 

Output

Index Value 0 0.000000 1 101.000000 2 102.000000 3 103.000000 4 104.000000 5 105.000000 6 106.000000 7 107.000000 8 108.000000 9 109.000000 10 110.000000 Back in Basic 1 201 2 202 3 203 4 204 5 205 6 206 7 207 8 208 9 209 10 210

PASSING A Basic ARRAY OF FIXED-LENGTH STRINGS TO C

Basic Program


DECLARE SUB StringFar CDECL (length%, num%, BYVAL p3o AS INTEGER,_
   BYVAL p3s AS INTEGER)

DIM array(10) AS STRING * 10
CLS
length% = 10
num% = 3
FOR i = 0 TO 10
   array(i) = STRING$(9, 65 + i) + CHR$(0)

NEXT i
CALL StringFar(length%, num%, VARPTR(array(0)), VARSEG(array(0)))
END 

C Routine


#include <stdio.h>
void StringFar(int *len, int *num, char far *array) {
   int i;
   printf("The string length is : %d \n\n",*len);
   printf("The number of elements is : %d \n\n",*num);
   printf(" Index        String\n");
   for (i=0; i < *num; i++) {
      printf("  %2d         %Fs\n", i, array);
      array=array+*len;
   }
} 

Output

The string length is : 10

The number of elements is : 3

Index String 0 AAAAAAAAA 1 BBBBBBBBB 2 CCCCCCCCC

PASSING A Basic ARRAY OF USER-DEFINED TYPE TO C

Basic Program


TYPE record
   a AS INTEGER
   b AS STRING * 20
   c AS SINGLE

END TYPE

DECLARE SUB TypeArray CDECL (BYVAL p1o AS INTEGER, BYVAL p1s AS INTEGER)
DIM element(10) AS record
CLS
FOR I = 0 TO 10
   element(I).a = 128 + I
   element(I).b = STR$(I) + ". " + DATE$ + CHR$(0)
   element(I).c = 39.6 * I

NEXT I
CALL TypeArray(VARPTR(element(0)), VARSEG(element(0)))
END 

C Routine


#include <stdio.h>

struct record {
   int a;
   char b[20];
   float c;

};

void TypeArray(struct record far *element) {
   int i;
   for (i=0; i<3; i++) {
      printf("Record[%d].A = %d\n",  i, element->a);
      printf("Record[%d].B = %Fs\n", i, element->b);
      printf("Record[%d].C = %f\n",  i, element->c);
      printf("\n");
      element++;
   }
 } 

Output

Record[0].A = 128
Record[0].B = 0. 02-02-1988
Record[0].C = 0.000000

Record[1].A = 129
Record[1].B = 1. 02-02-1988
Record[1].C = 39.599998

Record[2].A = 130
Record[2].B = 2. 02-02-1988
Record[2].C = 79.199997

PASSING A Basic TWO-DIMENSIONAL INTEGER ARRAY TO C BY FAR REFERENCE

Basic Program


DECLARE SUB TwoIntArray CDECL (BYVAL p1o AS INTEGER, BYVAL p1s AS INTEGER) 


DIM x(4, 4) AS INTEGER CLS FOR i = 0 TO 4

   FOR j = 0 TO 4 
x(i, j) = i * 10 + j

   NEXT 
NEXT CALL TwoIntArray(VARPTR(x(0, 0)), VARSEG(x(0, 0))) LOCATE 5, 1 END

C Routine




#include <stdio.h> 
struct two_int_array {

   int a[5][5]; 
};


void TwoIntArray(struct two_int_array far *x) {
   int i,j;
   for (i = 0; i < 5; i++) {
      for (j = 0; j < 5; j++) {
         printf("  %3d   ", x->a[i][j]);
      }
      printf("\n");
   } 
}

Output



0 10 20 30 40 1 11 21 31 41 2 12 22 32 42 3 13 23 33 43 4 14 24 34 44


PASSING A COMMON BLOCK FROM Basic TO C BY FAR REFERENCE



Basic Program




DECLARE SUB RCommon CDECL (BYVAL p1o AS INTEGER, BYVAL p1s AS INTEGER) 


COMMON SHARED element1 AS INTEGER, element2 AS STRING * 20,_

   element3 AS SINGLE 


element1 = 23 element2 = "DATE : " + DATE$ + CHR$(0) element3 = 309.03 CALL RCommon(VARPTR(element1), VARSEG(element1)) END

C Routine




#include <stdio.h> 


struct common_block { // structure ooks like Basic common block

   int a;
   char b[20];
   float c; 
};


void RCommon(struct common_block far *pointer) {
   printf("Element1 = %d\n",  pointer->a);
   printf("Element2 = %Fs\n", pointer->b);
   printf("Element3 = %f\n",  pointer->c); 
}

Output



Element1 = 23 Element2 = DATE : 02-02-1988 Element3 = 309.029999

PASSING A FIXED-LENGTH STRING FROM C TO Basic BY FAR REFERENCE



Basic Program




DECLARE SUB StringFar CDECL (BYVAL p1o AS INTEGER, BYVAL p1s AS INTEGER,_
   p3 AS INTEGER) 


DIM array AS STRING * 15 CLS array = "This is a test" + CHR$(0) CALL StringFar(VARPTR(array), VARSEG(array), LEN(array)) LOCATE 20,20 PRINT array END

C Routine




#include <stdio.h>
void StringFar(char far *a, int *len) {
   int i;
   printf("The string is : %Fs\n\n",a);
   printf(" Index       Value       Character\n");
   for (i = 0;i < *len; i++) {
      printf("  %2d       %3d      %c\n", i, a[i], a[i]);
   }
   /* This loop writes over the end of the string */ 
   for (i = 10; i < *len; i++) {
      a[i] = 64;    // ASCII value for '@'
   } 
}

Output



The string is : This is a test

Index Value Character 0 84 T 1 104 h 2 105 i 3 115 s 4 32 5 105 i 6 115 s 7 32 8 a 9 32 10 116 t 11 101 e 12 115 s 13 116 t

This is a @@@@


C FUNCTIONS RETURNING NUMERICS TO Basic



Basic Program




DECLARE FUNCTION cintfunc% CDECL ()
DECLARE FUNCTION clongfunc& CDECL ()
DECLARE FUNCTION csinglefunc! CDECL ()
DECLARE FUNCTION cdoublefunc# CDECL () 


PRINT "Integer: "; cintfunc PRINT "Long : "; clongfunc PRINT "Single : "; csinglefunc PRINT "Double : "; cdoublefunc

C Routines




int cintfunc(void) {
   int theint = 32767;
   return(theint); 
}

long clongfunc(void) {

   long thelong = 32769;
   return(thelong); 
}

float csinglefunc(void) {

   float thefloat = 123.312F;
   return(thefloat); 
}

double cdoublefunc(void) {

   double thedouble = 129381.123;
   return(thedouble); 
}

Output



Integer: 32767 Long : 32769 Single : 123.312 Double : 129381.123

Additional query words: VBmsdos 1.00

Keywords :
Version : MS-DOS:1.0,7.1
Platform : MS-DOS
Issue type :


Last Reviewed: March 24, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.