Microsoft KB Archive/50062

From BetaArchive Wiki

Microsoft Knowledge Base

How to Pass Parameters Between Basic and C (Part 1 of 2)

Last reviewed: January 12, 1995
Article ID: Q50062

The information in this article applies to:

  • Microsoft Basic Professional Development System (PDS) for MS-DOS, versions 7.0 and 7.1
  • Microsoft Basic Compiler versions 6.00 and 6.00b for MS-DOS and MS OS/2
  • Microsoft QuickBasic for MS-DOS, versions 4.0, 4.0b, and 4.5
  • Standard and Professional Editions of Microsoft Visual Basic for MS-DOS, version 1.0
  • Microsoft C Optimizing Compiler versions 5.00, 5.10, and 6.00 for MS-DOS and MS OS/2
  • Microsoft QuickC versions 1.00, 1.01, 2.00, 2.01, and 2.50 for MS-DOS

SUMMARY

This article contains Part 1 of 2 of the complete text of "How to Pass Parameters between Basic and C." To obtain Part 2 of 2, please see the following article in the Microsoft Knowledge Base:

ARTICLE-ID: Q71274

TITLE     : How to Pass Parameters Between Basic and C (Part 2 of 2)

MORE INFORMATION

              HOW TO PASS PARAMETERS BETWEEN Basic AND C

This long article explains how Microsoft Basic programs can pass parameters to and from Microsoft C programs. It assumes that you have a basic understanding of the Basic and C languages.

Microsoft Basic supports calls to routines written in Microsoft C, FORTRAN, and Pascal. This application note describes the necessary syntax for calling Microsoft C routines and contains a series of examples demonstrating the interlanguage calling capabilities between Basic and C.

The following table specifies which versions of Microsoft Basic can be linked with specific versions of Microsoft C or QuickC:

                      Basic
      QuickBasic      Compiler  <->       C         QuickC
      ----------      --------  ---       -         ------

      4.00              --      <->       5.00      1.00
        --            6.00      <->       5.10      1.01
      4.00b      or   6.00b     <->       5.10      1.01
      4.50       or   7.00      <->       5.10      2.00 or 2.01
        --            7.10      <->       6.00      2.50

In QuickBasic versions 1.00, 1.01, 1.02, 2.00, 2.01, and 3.00, you can call only QuickBasic subprograms or Microsoft Macro Assembler routines (and cannot call C).

For more information about interlanguage calling, refer to the Microsoft Mixed-Language Programming Guide for the MS-DOS Operating System. This guide is available with C 5.00 and 5.10 and MASM 5.00 and 5.10.

This document contains the following sections:

   Making Mixed-Language Calls
   Naming Convention Requirements
   Calling Convention Requirements
   Parameter-Passing Requirements
   Restrictions on Calls from Basic
   The Basic Interface to C
   C Calls to Basic
   Compiling and Linking
   Data Types
   Debugging Mixed-Language Programs
   Compiling and Linking the Sample Programs
   Appendix: Common Pitfalls

MAKING MIXED-LANGUAGE CALLS

Mixed-language programming always involves a call; specifically, it involves a function or subprogram call. For example, a Basic main module may need to execute a specific task that you would like to program separately. In addition to calling a Basic subprogram or function, you can call a C function.

Mixed-language calls necessarily involve multiple modules. Instead of compiling all of your source modules with the same compiler, you use different compilers. In the example mentioned above, you would compile the main-module source file with the Basic compiler, compile another source file (written in C) with the C compiler, and then link the two object files.

There are two types of routines that can be called. The principle difference is that some kinds of routines return values, and others do not. (Note: In this article, "routine" refers to any function or subprogram procedure that can be called from another module.)

The following table compares the types of routine calls in C and Basic:

   Language      Returned Value    No Returned Value
   --------      --------------    -----------------

   Basic         FUNCTION          subprogram (SUB ... END SUB)
   C             function          void function

  Note: Basic DEF FN functions and GOSUB subroutines cannot be
  called from another language.

NAMING CONVENTION REQUIREMENTS

The term "naming convention" refers to the way that a compiler alters the name of the routine before placing it into an object file.

It is important that you adopt a compatible naming convention when you issue a mixed-language call. If the name of the called routine is stored differently in each object file, the linker will not be able to find a match. Instead, it will report an unresolved external reference.

When Microsoft compilers place machine code into object files, they also include the names of all routines and variables that need to be accessed publicly. That way, the linker can compare the name of a routine called in one module to the name of a routine defined in another module and can recognize a match.

Basic and C use different naming conventions. Basic translates each letter to uppercase and drops the declaration character (%, &, !, #, $). Basic recognizes the first 40 characters of a routine name.

C uses a different convention; the C compiler does not translate any letters to uppercase but inserts a leading underscore (_) in front of the name of each routine. C recognizes the first 31 characters of a name.

When linking, it is important not to use the /NOIGNORE linker option. Differences in naming conventions are taken care of for you automatically by mixed-language keywords as long as you do not use the /NOIGNORE linker option. Using this option causes the linker to distinguish among routines with different capitalization; for example, routines named "Prn" and "prn" would cause problems when linking Basic and C programs.

The CL driver and Microsoft QuickC automatically use the /NOIGNORE option when linking. To solve the problems created by this behavior, either link separately with the LINK utility or use all uppercase letters in your C modules (when not using CDECL, Basic translates all routine names to uppercase).

 Note: Microsoft Basic Professional Development System (PDS)  versions 7.00

and 7.10 can safely be linked with the /NOI switch. However, because names in Microsoft Basic are not case sensitive, this option can have minimal importance. You should not use the /NOI option when linking a protected mode custom run-time module or a protected mode program without the /O option.

CALLING CONVENTION REQUIREMENTS

The term "calling convention" refers to the way that a language implements a call. The choice of calling convention affects the actual machine instructions that a compiler generates to execute (and return from) a function or subprogram call. The use of a calling convention affects programming in two ways:

  1. The calling routine uses a calling convention to determine what order in which to pass arguments (parameters) to another routine. The calling convention can usually be specified in a mixed-language interface.
  2. The called routine uses a calling convention to determine the order in which to receive the parameters that were passed to it. In most languages, this convention can be specified in the routine's heading. Basic, however, always uses its own convention to receive parameters.

Basic and C use different calling conventions. Basic's calling convention pushes parameters onto the stack in the order in which they appear in the source code. For example, the Basic statement CALL Calc(A, B) pushes argument A onto the stack before it pushes B. This convention also specifies that the stack is restored by the called routine, just before returning control to the caller. (The stack is restored by removing the parameters.)

The C calling convention pushes parameters onto the stack in the reverse order in which they appear in the source code. For example, the C function calc(a, b); pushes b onto the stack before it pushes a. In contrast with Basic, the C calling convention specifies that a calling routine always restores the stack immediately after the called routine returns control.

When declaring a function in C, the pascal keyword can be used to indicate the calling convention used by Basic (Basic and Pascal both use the same calling convention). For example:

   extern pascal int function1(int, int);

When declaring a function in Basic, the CDECL keyword can be used to declare the function as using the C calling convention. For example:

   DECLARE FUNCTION Function1% CDECL (BYVAL N AS INTEGER)

PARAMETER-PASSING REQUIREMENTS

Microsoft compilers support three methods for passing a parameter, as explained in the following table:

    Method          Description
    ------          -----------

    Near reference  Passes a variable's near (offset) address.

                    This method gives the called routine direct
                    access to the variable itself. Any change the
                    routine makes to the parameter will be
                    reflected in the calling routine.

                    Note: Basic assumes that all variables are
                    passed by near (offset) address off of
                    DGROUP. This means that all C data passed to
                    Basic must be in the near data segment.

    Far reference   Passes a variable's far (segmented) address.

                    This method is similar to passing by near
                    reference, except that the segment as well as
                    the offset is passed. This allows the
                    variable to reside anywhere in memory.

    By value        Passes only the variable's value, not its
                    address.

                    With this method, the called routine knows
                    the value of the parameter but has no access
                    to the original variable. Changes to the
                    value parameter have no effect on the value
                    of the parameter in the calling routine once
                    the called routine terminates.

The fact that there are different parameter-passing methods has two implications for mixed-language programming:

  1. You need to make sure that the called routine and the calling routine use the same method for passing each parameter (argument). In most cases, you must check the parameter-passing defaults used by each language and possibly make adjustments. Each language has keywords or language features that allow you to change the parameter-passing method.
  2. You may want to use a particular parameter-passing method rather than using the default for the language.

The following table summarizes the parameter-passing defaults for C and Basic:

   Language     Near Reference    Far Reference   Value
   --------     --------------    -------------   -----

   Basic        All
   C            Near arrays       Far arrays      Everything else

Each of these default methods can be overridden, as shown in the following sections.

Basic ARGUMENTS

The default for Basic is to pass all arguments by near reference. This can be overridden by using the SEG directive or by using CALLS instead of CALL. Both of these cause Basic to pass both the segment and offset. These can be used only to call non-Basic routines because Basic receives all parameters by near reference.

  Note: Although Basic can pass parameters to other languages by
  far reference using either the SEG directive or CALLS, Basic
  routines can be called only from other languages when
  parameters are passed by near reference. You cannot DECLARE or
  CALL a Basic routine with parameters that have SEG or BYVAL
  attributes. SEG and BYVAL are used only for parameters of non-
  Basic routines. However, Basic PDS version 7.10 allows a Basic
  routine to be passed parameters by value.

Passing Basic Arguments by Value

In Basic, the BYVAL keyword is used to pass arguments by value. An argument is passed by value when the called routine is first declared with a DECLARE statement, and the BYVAL keyword is applied to the argument. For example:

   DECLARE SUB CRoutine CDECL (BYVAL a AS INTEGER)

Passing Basic Arguments by Near Reference

The Basic default is to pass by near reference. The use of SEG, BYVAL, or CALLS changes this default.

Passing Basic Arguments by Far Reference

Basic passes each argument in a call by far reference when CALLS is used to invoke a routine. Using SEG to modify a parameter in a preceding DECLARE statement also causes a Basic CALL to pass parameters by far reference.

NOTE: CALLS cannot be used to call a routine that is named in a DECLARE statement. Because of this, CDECL (an option of the DECLARE statement) cannot be specified when using CALLS. This means that CALLS always passes parameters using the Pascal calling convention.

C ARGUMENTS

The default for C is to pass all arrays by reference (near or far, depending on the memory model) and all other data types by value. C uses far data pointers for compact, large, and huge models, and near data pointers for small and medium models.

Passing C Arguments by Value

The C default is to pass everything except arrays by value.

Passing C Arguments by Reference (Near or Far)

In C, passing a pointer to an object is equivalent to passing the object itself by reference. After control is passed to the called function, each reference to the parameter itself is prefixed by * (an asterisk).

Note: To pass a pointer to an object, prefix the parameter in the CALL statement with &. To receive a pointer to an object, prefix the parameter's declaration with *. In the latter case, this may mean adding a second * to a parameter that already has an *. For example, to receive a pointer by value, declare it as

     inct   *ptr;

But to receive the same pointer by reference, declare it as follows:

     int    **ptr;

The default for arrays is to pass by reference.

Effect of C Memory Models on Size of Reference

Near reference is the default for passing pointers in small and medium model C. Far reference is the default in the compact, large, and huge models.

  Note: All C programs that are called from Basic must be
  compiled with the medium or large memory models.

Near pointers can be specified with the near keyword, which overrides the default pointer size. However, if you are going to override the default pointer size of a parameter, you must explicitly declare the parameter type in function declarations as well as function definitions. Far pointers can be specified with the far keyword.

RESTRICTIONS ON CALLS FROM Basic

Basic has a much more complex environment and initialization procedure than C. Interlanguage calling between Basic and C is possible only because Basic intercepts a number of C library function calls and handles them in its own way. Because of this, Basic must be the initial environment that the program starts in, and from there, C routines can be called (which can, in turn, call Basic routines). This means that a program cannot start up with C main-module code and then call Basic routines.

Basic creates a host environment in which the C routines can function. However, Basic is limited in its ability to handle some C function calls. These limitations are as follows:

MEMORY ALLOCATION

If your C module is a medium model and you do dynamic memory allocation with malloc(), or if you execute explicit calls to _nmalloc() with any memory model, you must include the following lines in your Basic source code before you call C:

   DIM mallocbuf%(2048)
   COMMON SHARED /NMALLOC/ mallocbuf%()

The array can have any name; only the size of the array is significant. However, the name of the COMMON block must be NMALLOC. In the QuickBasic in- memory environment, you must put this declaration in a module that you load into a resident Quick library.

The example above has the effect of reserving 4K in the COMMON block NMALLOC (integers take 2 bytes each, and there are 2048 integers allocated). When Basic intercepts C malloc calls, Basic allocates space out of this COMMON block.

WARNING: When you use the Basic statement CLEAR, all space allocated with near malloc calls will be lost. If you use CLEAR at all, use it only before any calls to malloc.

When you make far-memory requests in mixed-language programs, you may find it useful to first call the Basic function SETMEM. This function can be used to reduce the amount of memory Basic is using, thus freeing up memory for far allocations.

INCOMPATIBLE FUNCTIONS

The following C functions are incompatible with Basic, avoid them:

  • All forms of spawn() and exec()
  • system()
  • getenv()
  • putenv()

In addition, you should not link with the CVARSTCK.OBJ, SVARSTCK.OBJ, MVARSTCK.OBJ, or LVARSTCK.OBJ modules (which correspond to compact, small, medium, and large memory models). C provides these modules to allocate memory from the stack.

NOTE: The C graphics library, GRAPHICS.LIB, and the QuickC presentation graphics library PGCHART.LIB, are not compatible with QuickBasic or the Basic compiler. Many of the C graphics routines conflict with the Basic graphics routines. If graphics need to be done, they should be done in Basic.

Linking with Microsoft C graphics routines gives many "Duplicate Definition" errors, even if you LINK with the /NOE option.

                     THE Basic INTERFACE TO C
                     ========================

The Basic DECLARE statement provides a flexible and convenient interface to C. When you call a function, the DECLARE statement syntax is as follows:

   DECLARE FUNCTION name [CDECL][ALIAS "aliasname"][(parameter-list)]

The name field is the name of the function or subprogram that you want to call, as it appears in the Basic source file. The following are the recommended steps for using the DECLARE statement when calling C:

  1. For each distinct C routine you plan to call, put a DECLARE statement in your Basic source file before the routine is called.
  2. Use CDECL in the DECLARE statement (unless the C routine is declared with the pascal or fortran keywords or the /Gc compile switch).
  3. If you are calling a C routine with a name longer than 31 characters, use the ALIAS feature. The use of ALIAS is explained in the following section.
  4. Use the parameter list to determine how each parameter is to be passed. The use of the parameter list is explained in the section, "Using the Parameter List."
  5. Once the routine is properly declared, call it just as you would a Basic subprogram or function.

USING ALIAS

The use of ALIAS may be necessary because C places the first 31 characters of a name into an object file, whereas Basic places up to 40 characters of a name into an object file.

NOTE: You do not need the ALIAS feature to remove type declaration characters (%, &, !, #, $). Basic automatically removes these characters when it generates object code. Thus, Fact% in Basic matches FACT in C.

The ALIAS keyword directs Basic to place aliasname into the object file, instead of name. The Basic source file still contains calls to name. However, these calls are interpreted as if they were actually calls to aliasname. This is used when a Basic name is longer than 31 characters and must be called from C. For example:

   DECLARE FUNCTION QuadraticPolynomialFunctionLeastSquares%_
                    ALIAS "QUADRATI" (a, b, c)

QUADRATI, the alias name, contains the first eight characters of the name QuadraticPolynomialFunctionLeastSquares%. This causes Basic to place QUADRATI into the object file, thereby mimicking C's behavior. (Note: If the CDECL keyword was used in the DECLARE statement, Basic would take care of this automatically.)

USING THE PARAMETER LIST

The parameter list syntax is as follows:

   [BYVAL | SEG] variable [AS type]...,

You can use BYVAL or SEG, but not both. Explanations of each field are as follows:

  1. Use the BYVAL keyword to declare a value parameter. When such a function is called, the corresponding argument will be passed by value (the default method for C modules).

         Note: Basic provides two ways of "passing by value." The
         usual method of passing by value is to use an extra set of
         parentheses, as in:
             CALL HOLM((A))
    
         This extra-parentheses method actually creates a temporary
         value whose address is passed. The BYVAL keyword method
         provides a true method of passing by value because the value
         itself, not an address, is passed. Only by using BYVAL will
         a Basic program be compatible with a C routine that expects
         a value parameter.
    
  2. Use the SEG keyword to declare a far-reference parameter. When the function is called, the far (segmented) address of the corresponding argument will be passed.
  3. You can choose any legal name for the variable, but only the type associated with the name has any significance to Basic. As with other variables, the type can be indicated with a type declaration character (%, &, !, #, $) or the implicit declaration.
  4. You can use the "AS type" clause to override the type declaration of variable. type can be INTEGER, LONG, SINGLE, DOUBLE, STRING, a user-defined type, or ANY (which directs Basic to permit any type of data to be passed as the argument). For example:

          DECLARE FUNCTION Calc2! CDECL (BYVAL a%, BYVAL b%,_

    BYVAL c!)

In the example above, Calc2 is declared as a C routine that takes three arguments: the first two are integers passed by value, and the last is a SINGLE-precision real number passed by value.

ALTERNATIVE Basic INTERFACES

Instead of modifying the behavior of Basic with CDECL, you can modify the behavior of C by applying the pascal or fortran keyword to the function definition heading. (These two keywords are functionally equivalent.) Or, you can compile the C module with the /Gc option, which specifies that all C functions, calls, and public symbols use the conventions of Basic.

For example, the following C function uses the Basic conventions to receive an integer parameter:

   int pascal fun1(int n)

You can specify parameter-passing methods without using a DECLARE statement or by using a DECLARE statement and omitting the parameter list.

  1. You can make the call with the CALLS statement. The CALLS statement causes each parameter to be passed by far reference.
  2. You can use the BYVAL and SEG keywords in the actual parameter list when you make the call. For example:

          CALL Fun2(BYVAL Term1, BYVAL Term2, SEG Sum)

In the example above, BYVAL and SEG have the same meaning that they have in a Basic DECLARE statement. When you use BYVAL and SEG this way, however, you must be careful because neither the type nor the number of parameters will be checked as they would be in a DECLARE statement.

C CALLS TO Basic

No Basic routine can be executed unless the main program is in Basic because a Basic routine requires an initialization environment that is unique to Basic. C will not perform this special initialization.

However, it is possible for a program to start up in Basic, call a C function that does most of the work of the program, and then call Basic subprograms and functions as needed.

The following rules are recommended when you call Basic from C:

  1. Start up in a Basic main module. You must use the DECLARE statement to provide an interface to the C module.
  2. In the C module, declare the Basic routine as extern and include type information for parameters. Use either the fortran or pascal keyword to modify the routine itself or compile the C routine with the /Gc switch.
  3. Make sure that all data is passed as a near pointer. Basic can pass data in a variety of ways but is unable to receive data in any form other than near reference.

         Note: Basic PDS 7.10 allows a Basic routine to be passed
         parameters by value, using the BYVAL parameter attribute.

    With near pointers, the program assumes that the data is in the default data segment. If you want to pass data that is not in the default data segment, then first copy the data to a variable that is in the default data segment.

  4. Compile the C module in medium or large model.

COMPILING AND LINKING

After you have written your source files and resolved the issues raised in the above sections, you are ready to compile individual modules and link them together. Before linking, each program module must be compiled with the appropriate compiler. The C modules must be compiled in medium or large model. In many cases, linking modules compiled with C and Basic can be done easily. Any of the following measures will ensure that all of the required libraries are linked in the correct order:

  1. Put all language libraries in the same directory as the source files.
  2. List directories containing all needed libraries in the LIB environment variable.
  3. Let the linker prompt you for libraries.

In each of the above cases, the linker finds the libraries in the order that it requires them. If you enter the libraries on the command line, the Basic libraries must precede all others.

When linking, the /NOE switch should be used. This prevents "Duplicate Definition" errors.

/NOE is for NO Extended library search. Normally, if a module in a library uses routines in another module, then both of the modules are automatically pulled in without searching the library for the second module. /NOE makes the linker search for the second module.

When the linker automatically pulls in these secondary modules, it can pull in duplicate modules from two libraries. /NOE causes the linker to search for the secondary modules. This means the linker always pulls in the module from the first library the module resides in. Since two duplicate modules won't be brought in, this solves most duplicate definition errors.

If the linker is still generating duplicate definition errors, then use the /NOD switch as well as the /NOE switch. /NOD is for NO Default library search. An object file contains references to libraries that it will need. The linker automatically pulls in these libraries unless /NOD is used. If the /NOD switch is used, the Basic and C libraries must be explicitly defined on the link command line. For example, to link a medium model Microsoft C program with a stand-alone Microsoft Basic Compiler version 6.00 program using the /NOD switch, the following link line could be used:

   LINK /NOE /NOD basicprg cprog,,,bcom60er.lib mlibce.lib;

                              DATA TYPES
                              ==========

NUMERICAL FORMATS

Numerical data formats are the simplest kinds of data to pass between C and Basic. The following chart shows the equivalent data types in each language:

   Basic           C
   -----           -

   x%, INTEGER     short, int
   ...             unsigned short, --> Not available
                   unsigned        --> in Basic.
   x&, LONG        long
   x!, SINGLE      float
   x#, DOUBLE      double
   STRING * 1      char (inside a structure)
   x%, INTEGER     char (outside a structure)

WARNING: C sometimes performs automatic data conversions that Basic does not. You can prevent C from performing such conversions by declaring a variable as the only member of a structure and then passing this structure. For example, you can pass variable x of type float by declaring the structure as follows:

        struct {
             float x;
        } x_struct;

If you pass a variable of type char or float by value and do not take this precaution, then the C conversion may cause the program to fail.

STRING FORMATS

Variable-Length Near Strings

Variable-length near strings in Basic have 4-byte string descriptors:

        +-------------------------------------+
        |      Length      | Address (offset) |
        +-------------------------------------+
              (2 bytes)          (2 bytes)

The first field of the string descriptor contains a 2-byte integer indicating the length of the actual string text. The second field contains the address of the text. This address is an offset into the default data area and is assigned by Basic's string-space management routines. These management routines must be available to reassign this address whenever the length of the string changes, yet these management routines are available only to Basic. Therefore, a C routine should not alter the length of a Basic variable-length string.

  Note: Fixed-length strings do not have string descriptors.

C String Format

C stores strings as simple arrays of bytes and uses a null character (numerical 0, ASCII NUL) as the delimiter. For example, consider the string declared as follows:

   char str[] = "String of text"

The string is stored in 15 bytes of memory as

        +------------------------------+
        |S|t|r|i|n|g| |o|f| |t|e|x|t|\0|
        +------------------------------+

Since str is an array like any other, it is passed by reference, just as other C arrays are.

Passing Near String Descriptors from Basic

When a Basic string (such as A$) appears in an argument list, Basic passes a string descriptor rather than the string data itself. The Basic string descriptor is not compatible with the string format of C.

WARNING: When you pass a string from Basic to C, the called routine should under no circumstances alter the length or address of the string.

The routine that receives the string must be aware that if any Basic routine is called, Basic's string-space management routines may change the location of the string data without warning. In this case, the calling routine must note that the values in the string descriptor may change.

The SADD and LEN functions extract parts of the string descriptor. SADD extracts the address of the actual string data, and LEN extracts the length. The results of these functions can then be passed to C.

Basic should pass the result of the SADD function by value. Bear in mind that the string's address, not the string itself, is passed by value. This amounts to passing the string itself by reference. The Basic module passes the string's address, and the other module receives the string's address. The address returned by SADD is declared as type INTEGER, but is actually equivalent to a C near pointer.

Before attempting to pass a Basic string to C, you may want to first append a null byte to the end, with an instruction such as the following:

   A$ = A$ + CHR$(0)

The string now conforms to the C string format.

There are two methods for passing a string from Basic to C. The first method is to pass the string address and string length as separate arguments, using the SADD and LEN functions. If you are linking to a C library routine, this is the only workable method. C must receive a near pointer since only the near (offset) address is being passed by Basic.

The second method is to pass the string descriptor itself, with a call statement such as the following:

   CALL CRoutine(A$)

In this case, the C routine must declare a structure for the parameter, which has the appropriate fields (length and address) for a Basic string descriptor. The C routine should then expect to receive a pointer to a structure of this type.

Far Variable-Length Strings

Microsoft Basic PDS 7.00 and 7.10 allow for the use of far strings. Information on using far strings with other languages is covered in Chapter 13, "Mixed-Language Programming with Far Strings," of the "Microsoft Basic 7.0: Programmer's Guide" for versions 7.00 and 7.10.

Fixed-Length Strings

Fixed-length strings in Basic are stored simply as arrays of contiguous bytes of characters, with no terminating character. There is no string descriptor for a fixed-length string.

To pass a fixed-length string to a routine, the string must be put into a user-defined type. For example:

   TYPE FixType
      A AS STRING * 10
   END TYPE

Passing a String Descriptor from C

To pass a C string to Basic, first allocate a string in C. Then, create a structure identical to a Basic string descriptor. Pass this structure by near reference. Make sure that the string originates in C, not in Basic. Otherwise, Basic may attempt to move the string around in memory.

ARRAYS

There are three special problems that you must be aware of when passing arrays between Basic and C:

  1. Arrays are implemented differently in Basic, so you must take special precautions when passing an array from Basic to C.
  2. Arrays are declared differently in C and Basic.
  3. Passed arrays must be created in Basic.

Passing Arrays from Basic

Basic uses an array descriptor, which is similar in some respects to a string descriptor. The array descriptor is necessary because Basic may shift the location of array data in memory. Therefore, you can safely pass arrays from Basic only if you follow three rules:

  1. Pass the array's address by applying the VARPTR function to the first element of the array and passing the result by value (with BYVAL). To pass the far address of the array, apply both the VARPTR and VARSEG functions and pass each result by value (with BYVAL). C gets the address of the first element and considers it the address of the entire array.
  2. The routine that receives the array must not, under any circumstances, make a call back to Basic. If it does, then the location of the array may change, and the address that was passed to the routine becomes meaningless.
  3. Basic can pass any member of an array by value. With this method, the above precautions do not apply.

Array Ordering

Basic and C differ in the way that arrays are ordered (or indexed). This issue affects only arrays with more then one dimension. There are two types of ordering: row-major and column-major. Basic uses column-major ordering, in which the leftmost dimension changes fastest. C uses row-major ordering, in which the rightmost dimension changes fastest.

When you compile a Basic program with the BC command line, you can select the /R compile option, which specifies that row-major order is to be used, rather than column-major order.

Passed Arrays Must Be Created in Basic

Basic keeps track of all arrays by using a special structure called an array descriptor. The array descriptor is unique to Basic and is not available in any other language. Because of this, to pass an array from C to Basic, the array must first be created in Basic and then passed to the C routine. The C routine can then alter the values in the array, but it cannot change the length of the array.

STRUCTURES, RECORDS, AND USER-DEFINED TYPES

The C struct type and the Basic user-defined type are equivalent. However, these types may be affected by the storage method. By default, C uses word alignment (unpacked storage) for all data except byte-sized objects and arrays of byte-sized objects. This storage method specifies that occasional bytes can be added as padding, so that word and double-word objects start on an even boundary. (In addition, all nested structures and records start on a word boundary.)

When passing structures, the C routine should be compiled with packing turned on to make it compatible with Basic. This is done by specifying the /Zp switch on the CL compile line.

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.

NOTE: 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 DOS I/O ROUTINES DOES NOT AFFECT QUICKBasic CURSOR POSITION

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

For example, after doing the following, the next PRINT statement goes directly after the last QuickBasic PRINT statement, ignoring the new line position from calling the C routine:

  1. Doing a PRINT from QuickBasic
  2. Calling a C routine that does some string display functions (printf)
  3. Returning to QuickBasic

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

DEBUGGING MIXED-LANGUAGE PROGRAMS

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

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 Microsoft Basic Compiler versions 6.00 and 6.00b, Microsoft C Optimizing Compiler versions 5.00 and 5.10, Microsoft Macro Assembler versions 5.00 and 5.10, Microsoft Pascal Compiler version 4.00, and Microsoft FORTRAN Compiler versions 4.00 and 5.00.


Additional reference words: QuickBas BasicCom

KBCategory: kbprg
KBSubcategory:


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: January 12, 1995
©1997 Microsoft Corporation. All rights reserved. Legal Notices.