20.3 The MASM/High-Level-Language Interface

Since high-level-language routines require certain program initialization code, the main program for a mixed-language program must be written in the high-level language, or you must add EXTERN A__ACRTUSED to your program to force the start-up code from the high-level-language run times to be loaded. Once the high-level-language code calls an assembly routine, the assembly routine can then call high-level-language routines as needed.

Summary: Use INVOKE to call high-level-language procedures.

For procedures with prototypes, INVOKE makes calls from MASM to high-level-language programs, much like procedure or function calls in the high-level language. INVOKE calls procedures and generates the code to push arguments in the order specified by the procedure's calling convention and to remove arguments from the stack at the end of the procedure.

INVOKE can also do some type checking and data conversion for the argument types so that the procedure receives compatible data. Section 7.3.6, “Declaring Procedure Prototypes,” explains how to write procedure prototypes and gives several examples of procedure declarations and the corresponding prototypes.

Summary: Use H2INC to translate C prototypes to MASM.

For programs that mix assembly language and C, the H2INC utility makes it easy to write prototypes and data declarations for the C procedures you want to call from MASM. H2INC translates the C prototypes and declarations into the corresponding MASM prototypes and declarations, which INVOKE can use to call the procedure. Chapter 16 explains how to use H2INC. See Section 20.3.1 for examples of using H2INC to write prototypes.

Mixed-language programming also allows the main program or a routine to use external data—data defined in the other module. External data is the data that is stored in a set place in memory (unlike dynamic and local data, which is allocated on the stack and heap) and is visible to other modules.

External data is shared by all routines. One of the modules must define the static data, which causes the compiler to allocate storage for the data. The other modules that access the data must declare the data as external.

This section describes argument-passing options and the standards for preserving registers and pushing addresses that are common to all high-level languages. It also explains the two methods that compilers use to store arrays—row-major and column-major order.

Argument Passing

Each language has its own convention for how an argument is actually passed. If the argument-passing conventions of your routines do not agree, then a called routine receives bad data. Microsoft languages support three different methods for passing an argument:

Near reference. Passes a variable's near (offset) address. This address is expressed as an offset from the default data segment.

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

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

This method is similar to passing by near reference, except that an address made up of a segment and an offset is passed, and it is slower. But it is necessary when you pass data that is outside of the default data segment. (This is not an issue in Basic or Pascal unless you have specifically requested far memory.)

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

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

Summary: You can also change the default argument-passing method.

When you pass arguments between MASM and another language, you need to make sure that the called routine and the calling routine use the same method. In most cases, you should check the argument-passing defaults used by each language and make any necessary adjustments. Most languages have features that allow you to change argument-passing methods.

Register Preservation

A procedure called from any high-level language should preserve the direction flag and the values of BP, SI, DI, SS, and DS. Routines called from MASM must not alter SI, DI, SS, DS, or BP.

Pushing Addresses

Microsoft high-level languages push segment addresses before pushing offsets. This facilitates use of the LES and LDS instructions. Furthermore, when pushing arguments longer than two bytes, high-order words are always pushed before low-order words, and arguments longer than two bytes are stored on the stack from most significant to least significant.

Array Storage

Most high-level-language compilers store arrays in row-major order. This means that all elements of a row are stored consecutively. The first five elements of an array with four rows and three columns are stored in row-major order as

A[1, 1], A[1, 2], A[1, 3], A[2, 1], A[2, 2]

In column-major order, the column elements are stored consecutively. For example, the same array defined above would be stored in column-major order as

A[1, 1], A[2, 1], A[3, 1], A[4, 1], A[1, 2], A[2, 2]