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:
- Doing a PRINT from Basic
- Calling a C routine that does some string display functions
(printf)
- 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:
- Make certain the version numbers of the two languages are
compatible.
- 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.
- Make sure that the graphic libraries are not included in the C
libraries. If the graphic libraries are included, duplicate
definition errors will occur.
- Use the /NOE switch when linking to avoid duplicate definition
errors. If duplicate definition errors still occur, also use the
/NOD switch when linking.
- Watch for incompatible C functions such as system() and getenv().
- When passing strings to C, check for two things:
- If a C function will use the string, it should be null-terminated
(CHR$(0)).
- 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.
- 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.
- 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).
- 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 :