USING ASSEMBLY LANGUAGE WITH C/C++
INTRODUCTION
Today, it is rare to develop a complete system using only assembly language. We often use C/C++ with some assembly language to develop a system. The assembly language portion usually solves tasks (difficult or inefficient to accomplish in C/C++) that often include control soft- ware for peripheral interfaces and driver programs that use interrupts. Another application of assembly language in C/C++ programs is the MMX and SEC instructions that are part of the Pentium class microprocessor and not supported in C/C++. Although C++ does have macros for these commands, they are more complicated to use than using assembly language. This chapter develops the idea of mixing C/C++ and assembly language. Many applications in later chapters also illustrate the use of both assembly language and C/C++ to accomplish tasks for the microprocessor.
This text uses Microsoft Visual C/C++ Express, but programs can often be adapted to any version of C/C++, as long as it is standard ANSI (American National Standards Institute) format C/C++. If you want, you can use C/C++ to enter and execute all the programming applications in this text. The 16-bit applications are written by using Microsoft Visual C/C++ version 1.52 or newer (available [CL.EXE] for no cost as a legacy application in the Microsoft Windows Driver Development Kit [DDK]); the 32-bit applications are written using Microsoft Visual C/C++ version 6 or newer and preferably Microsoft Visual C/C++ version .NET 2003 or Visual C++ Express. The examples in the text are written assuming that you have the latest version of Visual C++ Express, which is a free downloadable version of Visual C++. Please visit http://msdn.com to obtain the Visual C++ Express program.
CHAPTER OBJECTIVES
Upon completion of this chapter, you will be able to:
1. Use assembly language in _asm blocks within C/C+.
2. Learn the rules that apply to mixed language software development.
3. Use common C/C++ data and structures with assembly language.
4. Use both the 16-bit (DOS) interface and the 32-bit (Microsoft Windows) interface with assembly language code.
5. Use assembly language objects with C/C++ programs.
USING ASSEMBLY LANGUAGE WITH C++ FOR 16-BIT DOS APPLICATIONS
This section shows how to incorporate assembly language commands within a C/C++ program. This is important because the performance of a program often depends on the incorporation of assembly language sequences to speed its execution. As mentioned in the introduction to the chapter, assembly language is also used for I/O operations in embedded systems. This text assumes that you are using a version of the Microsoft C/C++ program, but any C/C++ program should function as shown, if it supports inline assembly commands. The only change might be setting up the C/C++ package to function with assembly language. This section of the text assumes that you are building l6-bit applications for DOS. Make sure that your software can build l6-bit applications before attempting any of the programs in this section. If you build a 32-bit application and attempt to use the DOS INT 21H function, the program will crash because DOS calls are not directly allowed. In fact, they are inefficient to use in a 32-bit application.
To build a 16-bit DOS application, you will need the legacy 16-bit compiler usually found in the C:WINDDK2600.1106binwin_mebin16 directory of the Windows DDK. (The Windows driver development kit can be obtained for a small shipping charge from Microsoft Corporation.) The compiler is CL.EXE and the 16-bit linker program is LINK.EXE, both located in the directory or folder listed. Because the path in the computer that you are using probably points to the 32-bit linker program, it would be wise to work from this directory so the proper linker is used when linking the object files generated by the compiler. Compilation and linking must be performed at the command line because there is no visual interface or editor provided with the compiler and linker. Programs are generated using either Notepad or DOS Edit.
Basic Rules and Simple Programs
Before assembly language code can be placed in a C/C++ program, some rules must be learned. Example 7–1 shows how to place assembly code inside an assembly language block within a short C/C++ program. Note that all the assembly code in this example is placed in the _asm block. Labels are used as illustrated by the label big: in this example. It is also extremely important to use lowercase characters for any inline assembly code. If you use uppercase, you will find that some of the assembly language commands and registers are reserved or defined words in C/C++ language.
Example 7–1 uses no C/C++ commands except for the main procedure. Enter the program using either WordPad or Edit. This program (Example 7–1) reads one character from the console keyboard, and then filters it through assembly language so that only the numbers 0 through 9 are sent back to the video display. Although this programming example does not accomplish much, it does show how to set up and use some simple programming constructs in the C/C++ environment and also how to use the inline assembler.
The register AX was not saved in Example 7–1, but it was used by the program. It is very important to note that the AX, BX, CX, DX, and ES registers are never used by Microsoft C/C++. (The function of AX on a return from a procedure is explained later in this chapter.) These registers, which might be considered scratchpad registers, are available to use with assembly language. If you wish to use any of the other registers, make sure that you save them with a PUSH before they are used and restore them with a POP afterwards. If you fail to save the registers used by a program, the program may not function correctly and can crash the computer. If the 80386 or above microprocessor is used as a base for the program, the EAX, EBX, ECX, EDX, and ES registers do not need to be saved. If any other registers are used, they must be saved or the program will crash.
To compile the program, start the Command Prompt program located in the Start Menu under Accessories. Change the path to C:WINDDK2600.1106binwin_mebin16 if that is where you have your Windows DDK. You will also need to go to the C:WINDDK2600.1106 libwin_me directory and copy slibce.lib to the C:WINDDK2600.1106binwin_mebin16 directory. Make sure you saved the program in the same path and use the extension .c with the file name. If using Notepad, make sure you select All Files under File Type when saving. To compile the program, type CL /G3 filename.c>. This will generate the .exe file (/G3 is the 80386) for the program. (See Table 7–1 for a list of the /G compiler switches.) Any errors that appear are ignored by pressing the Enter key. These errors generate warnings that will not cause a problem when the program is executed. When the program is executed, you will only see a number echoed back to the DOS screen.
Example 7–2 shows how to use variables from C with a short assembly language program. In this example, the char variable type (a byte in C) is used to save space for a few 8-bit bytes of data. The program itself performs the operation X + Y = Z, where X and Y are two one-digit numbers, and Z is the result. As you might imagine, you could use the inline assembly in C to learn assembly language and write many of the programs in this textbook. The semicolon adds comments to the listing in the _asm block, just as with the normal assembler.
What Cannot Be Used from MASM Inside an _asm Block
Although MASM contains some nice features, such as conditional commands (.IF, .WHILE,.REPEAT, etc.), the inline assembler does not include the conditional commands from MASM, nor does it include the MACRO feature found in the assembler. Data allocation with the inline assembler is handled by C instead of by using DB, DW, DD, etc. Just about all other features are supported by the inline assembler. These omissions from the inline assembler can cause some slight problems, as will be discussed in later sections of this chapter.
Using Character Strings
Example 7–3 illustrates a simple program that uses a character string defined with C and displays it so that each word is listed on a separate line. Notice the blend of both C statements and assem- bly language statements. The WHILE statement repeats the assembly language commands until the null (00H) is discovered at the end of the character string. If the null is not discovered, the assembly language instructions display a character from the string unless a space is located. For each space, the program displays a carriage return/line feed combination. This causes each word in the string to be displayed on a separate line.
Suppose that you want to display more than one string in a program, but you still want to use assembly language to develop the software to display a string. Example 7–4 illustrates a program that creates a procedure displaying a character string. This procedure is called each time that a string is displayed in the program. Note that this program displays one string on each line, unlike Example 7–3.
Using Data Structures
Data structures are an important part of most programs. This section shows how to interface a data structure created in C with an assembly language section that manipulates the data in the structure. Example 7–5 illustrates a short program that uses a data structure to store names, ages, and
salaries. The program then displays each of the entries by using a few assembly language procedures. Although the string procedure displays a character string, shown in Example 7–4, no car- riage return/line feed combination is displayed—instead, a space is displayed. The Crlf procedure displays a carriage return/line feed combination. The Numb procedure displays the integer.
An Example of a Mixed-Language Program
To see how this technique can be applied to any program, Example 7–6 shows how the program can do some operations in assembly language and some in C language. Here, the only assembly language portions of the program are the Dispn procedure that displays an integer and the Readnum procedure, which reads an integer. The program in Example 7–6 makes no attempt to detect or correct errors. Also, the program functions correctly only if the result is positive and less than 64K. Notice that this example uses assembly language to perform the I/O; the C portion performs all other operations to form the shell of the program.