17.3 A Sample Program

The following program prints Hello, world. It runs under OS/2 protected mode.

; HELLO.ASM

;

.MODEL small, pascal, OS_OS2

.286

INCLUDELIB os2.lib

INCLUDE os2.inc

.STACK

.DATA

message BYTE "Hello, world.", 13, 10 ; Message to print

bytecount DWORD ? ; Holds number of

; bytes written

.CODE

.STARTUP

push 1 ; Select standard output

push ds ; Pass address of message

push OFFSET message

push LENGTHOF message ; Pass length of message

push ds ; Pass address of count

push OFFSET bytecount ; returned by function

call DosWrite ; Call system write

; function

.EXIT 0 ; Exit with 0 return code

END

Summary: .STARTUP and .EXIT automatically generate code.

The .STARTUP and .EXIT directives are very useful because they automatically produce correct code for the operating-system type specified with the .MODEL directive (see Section 2.2, “Using Simplified Segment Directives”). As described in Section 17.6, OS/2 initializes all segment registers; therefore, .STARTUP does nothing but indicate the starting point. To correctly exit an OS/2 program, you must call the DosExit function. The DosExit prototype is always available to MASM programs.

In the example above, .EXIT automatically generates the following code under OS/2:

.EXIT 0

0011 6A 01 * push +000000001h ; Action 1 ends all threads

0013 6A 00 * push +000000000h ; Pass 0 return code

0015 9A ---- 0000 E * call DosExit ; Call system function

END

Between .STARTUP and .EXIT, the entire program consists of a single call to the DosWrite function. The program pushes the parameters on the stack and then makes the call. No POP or ADD instructions are needed to restore the stack after DosWrite returns; DosWrite observes the Pascal calling convention and restores the stack itself before returning.

The .MODEL statement helps ensure that the assembler produces correct code for calling DosWrite:

.MODEL small, pascal, OS_OS2

When you run HELLO.EXE, OS/2 looks at the import definitions in the executable-file header and makes sure that all needed DLLs are in memory. It then loads any needed DLLs not already in memory.

The assembler must be informed that DosWrite and DosExit are far and observe the Pascal calling convention. This information is in the prototype.

In the call to DosWrite, note that although OFFSET message is an immediate operand, the program pushes it directly onto the stack. This operation is legal on 80186–80486 processors but not on the 8086 or 8088:

push OFFSET message

Summary: The processors you want to target determine the instructions you should use.

Since OS/2 programs can execute only on the 80286 or later processors, it is reasonable to use extended operations not supported by the 8086. However, if you want to write a program that can be converted to run under both OS/2 and DOS (as shown in Section 17.5), then you should write code that can run on the 8086. For example,

mov ax, OFFSET msg

push ax

The following revision of the sample program illustrates the usefulness of the INVOKE directive. This version does everything the previous example did with far fewer statements:

; HELLO.ASM

.MODEL small, pascal, OS_OS2

INCLUDE os2.inc

INCLUDELIB os2.lib

.STACK

.DATA

message BYTE "Hello, world.", 13, 10 ; Message to print

bytecount DWORD ? ; Holds number of

; bytes written

.CODE

.STARTUP

INVOKE DosWrite,

1,

ADDR message,

LENGTHOF message,

ADDR bytecount

.EXIT 0 ; Exit with return code 0

END

The INVOKE directive generates a call to the given procedure after first pushing all other arguments on the stack. Like a call statement in a high-level language, the INVOKE directive handles types in a sophisticated way.