The Assembly Programming Master Book
| ||
| ||
|
History deserves to be learned! Although this chapter presents information of only historical interest, and although those who remember Windows 3.1 are few in number, I won't delete it even from future editions of my book. This chapter contains material that is valuable from a historical point of view. As a rule, when providing basic information about Windows programming in Assembly language, most authors start by describing 16-bit programming. In my opinion, it is obsolete and can no longer serve as an introduction to Windows programming. [i] Since it is extremely unlikely that you'll need to develop a 16-bit application, I'll limit myself to providing a simple example. If you are interested in this topic, I recommend that you read other books [1, 7] in addition to this chapter.
The Idea of 16-Bit Windows Programming
Let me start by considering the differences between 16-bit and 32-bit Assembly programming. You'll discover that programming has become considerably easier over time.
-
In contrast to the Windows 9x model, the memory model in Windows 3.1x was segmented. Accordingly, in the program, the data, the stack, and the code relate to different segments. [ii] As with MS-DOS, the DS register pointed to the data segment and CS pointed to the code segment.
-
In the 16-bit model, addressing is accomplished according to the segment/offset method. Accordingly, the address is defined by two 16-bit components . For example, to load the address of variable M into the stack, you'll need at least two commands: PUSH DS/PUSH OFFSET M. At the same time, the segment address must be loaded into the cell with the address higher than that of the offset (the stack grows in the direction of lower addresses).
-
For developing 16-bit applications, MASM 6.1 is the most convenient tool. Because this chapter provides only a historical overview, I won't concentrate attention on the development of 16-bit applications using Turbo Assembler. For linking the applications, you will use the LIBW.LIB library supplied with MASM 6.1. A feature of API calls, in this case, is the inverse order of loading parameters into the stack, as compared to all examples considered previously. In this chapter, and only here, I will use the following principle: from left to rightfrom top to bottom.
-
After starting the program, it is necessary to initialize startup. For this purpose, it is necessary to call the following API functions: INITTASK , WAITEVENT , and INITAPP .
-
INITTASK initializes registers, the command line, and memory. This function doesn't require any parameters and is the first one to be called. Return values of registers are as follows : AX = 1 (0 = error), CX = the stack size , DI = the unique number for the current task, DX = NCMDSHOW (described later), ES = the segment address (selector) PSP, ES:BX = the command line address, and SI = the unique number of the previous copy of the same application started earlier. In Windows 3.1, when starting an application, only a part of the registers is loaded into the memory each time and a part of the segments is the common resource. This was the way to economize on the memory. The developers of Windows 95 abandoned this approach. Every task running in the system is isolated and independent. In Windows 95, SI is always zero. Furthermore, this procedure fills the reserved header of the data segment.
-
WAITEVENT checks whether there are events intended for the specified application. If there is such an event in the queue, it is removed from the queue. The call looks as follows:
PUSH AX ; AX --- Application number ; If it is set to 0, this is the current application CALL WAITEVENT
-
INITAPP initializes the event queue for the current application. The call looks as follows:
PUSH DI ; Unique number of the task CALL INITAPP
In case of an error, this function returns 0; otherwise , it returns a nonzero value.
-
-
Some parameters of the API functions of a 16-bit application have a size of 2 bytes. In particular, the WPARAM and HWND parameters of the window procedure are 2 bytes long. When you need to operate with 4-byte parameters, they must be processed in two passes .
-
Consider the last difference: the data segment in the program start must contain the reserved 16-byte block.
-
An interesting fact is that in a 16-bit application, you can use normal MS-DOS interrupts by means of INT 21H. You only have to bear in mind which functions make sense with Windows.
An Example of 16-bit Windows Application
To illustrate the idea explained in the preceding section, I'll provide a simple program. Program translation is carried out using MASM 6.1:
ML /c prog.asm LINK prog, prog, libw
The question about the DEF file can be ignored.
Listing 4.1: An example of a 16-bit application
|
.286 .DOSSEG ; Segment order according to the agreement with Microsoft DGROUP GROUP DATA, STA ASSUME CS:CODE, DS:DGROUP ; Prototypes of external procedures EXTRN INITTASK:FAR EXTRN INITAPP:FAR EXTRN WAITEVENT:FAR EXTRN DOS3CALL:FAR EXTRN REGISTERCLASS:FAR EXTRN LOADCURSOR:FAR EXTRN GETSTOCKOBJECT:FAR EXTRN GETMESSAGE:FAR EXTRN TRANSLATEMESSAGE:FAR EXTRN DISPATCHMESSAGE:FAR EXTRN CREATEWINDOW:FAR EXTRN CREATEWINDOWEX:FAR EXTRN UPDATEWINDOW:FAR EXTRN SHOWWINDOW:FAR EXTRN POSTQUITMESSAGE:FAR EXTRN DEFWINDOWPROC:FAR ; Templates WNDCL STRUCT STYLE DW 0 ; Window class style LPFNWNDPROC DD 0 ; Pointer to the handler procedure CBCLSEXTRA DW 0 CBWNDEXTRA DW 0 HINSTANCE DW 0 HICON DW 0 HCURSOR DW 0 HBRBACKGROUND DW 0 LPSZMENUNAME DD 0 ; Pointer to the string LPSZCLASSNAME DD 0 ; Pointer to the string WNDCL ENDS ; MESSA STRUCT HWND DW ? MESSAGE DW ? WPARAM DW ? LPARAM DD ? TIME DW ? X DW ? Y DW ? MESSA ENDS ; Stack segment STA SEGMENT STACK 'STACK' DW 2000 DUP(?) STA ENDS ; Data segment DATA SEGMENT WORD 'DATA' ; The 16 bits at the starting point provide the reserve ; required for a 16-bit application to correctly ; operate in the Windows environment DWORD 0 WORD 5 WORD 5 DUP (0) HPREV DW ? HINST DW ? LPSZCMD DD ? CMDSHOW DW ? ; Structure for creating a class WNDCLASS WNDCL <> ; Message structure MSG MESSA <> ; Window class name CLASS_NAME DB 'HELLO', 0 ; Window header APP_NAME DB '16-bit program', 0 ; Cursor type CURSOR EQU 00007F00H ; Window style STYLE EQU 000CF0000H ; Window parameters XSTART DW 100 YSTART DW 100 DXCLIENT DW 300 DYCLIENT DW 200 DATA ENDS ; Code segment CODE SEGMENT WORD 'CODE' _BEGIN: ; I. Initial code CALL INITTASK ; Initialize the task OR AX, AX ; CX --- Stack boundaries JZ _ERR MOV HPREV, SI ; Number of the previous application MOV HINST, DI ; Number for the new task MOV WORD PTR LPSZCMD, BX ; ES:BX - Address MOV WORD PTR LPSZCMD+2, ES ; of the command line MOV CMDSHOW DX ; Screen parameter PUSH 0 ; Current task CALL WAITEVENT ; Clear the event queue PUSH HINST CALL INITAPP ; Initialize applications OR AX, AX JZ _ERR CALL MAIN ; Start the main procedure _TO_OS: MOV AH, 4CH INT 21H ; Exit the program _ERR: ; Here, it is possible to place the error message JMP SHORT _TO_OS ; Main procedure ;***************************************************** MAIN PROC ; II. Registering the window class ; Window style NULL --- Standard window MOV WNDCLASS.STYLE, 0 ; Handler procedure LEA BX, WNDPROC MOV WORD PTR WNDCLASS.LPFNWNDPROC, BX MOV BX, CS MOV WORD PTR WNDCLASS.LPFNWNDPROC+2, BX ;----------------------------------------- ; Reserved bytes terminating the structure being reserved MOV WNDCLASS.CBCLSEXTRA, 0 ; Reserved bytes terminating the structure for each window MOV WNDCLASS.CBWNDEXTRA, 0 ; The window icon is missing MOV WNDCLASS.HICON, 0 ; The number of the task being started MOV AX, HINST MOV WNDCLASS.HINSTANCE, AX ; Get the standard cursor number PUSH 0 PUSH DS PUSH CURSOR CALL LOADCURSOR MOV WNDCLASS.HCURSOR, AX ; Get the number of the standard object PUSH 0 ; WHITE_BRUSH CALL GETSTOCKOBJECT ; Background color MOV WNDCLASS.HBRBACKGROUND, AX ; Menu name from the resource file (NULL if missing) MOV WORD PTR WNDCLASS.LPSZMENUNAME, 0 MOV WORD PTR WNDCLASS.LPSZMENUNAME+2, 0 ; Pointer to the string containing the class name LEA BX, CLASS_NAME MOV WORD PTR WNDCLASS.LPSZCLASSNAME, BX MOV WORD PTR WNDCLASS.LPSZCLASSNAME+2, DS ; Call the registration procedure PUSH DS; Pointer to LEA DI, WNDCLASS PUSH DI; WNDCLASS structures CALL REGISTERCLASS CMP AX, 0 JNZ _OK1 ; Registration error RET ; Error in the course of registration _OK1 ; III. Creating the window ; Address of the window class name string PUSH DS LEA BX, CLAS_NAME PUSH BX ; Address of the window header string PUSH DS LEA BX, APP_NAME PUSH BX ; Window style MOV BX, HIGHWORD STYLE PUSH BX MOV BX, LOWWORD STYLE PUSH BX ; X coordinate of the window's top left corner PUSH XSTART ; Y coordinate of the window's top left corner PUSH YSTART ; Window width PUSH DXCLIENT ; Window height PUSH DYCLIENT ; Number of the parent window PUSH 0 ; Number (identifier) of the window menu PUSH 0 ; NULL ; Task number PUSH HINST ; Address of the block of window parameters (none) PUSH 0 PUSH 0 CALL CREATEWINDOW CMP AX, 0 JNZ NO_NULL ; Window creation error RET ; Error when creating a window ; Set the window visibility state (window or icon) ; according to the CMDSHOW parameter and its display NO_NULL: MOV SI, AX PUSH SI PUSH CMDSHOW CALL SHOWWINDOW ; Send the command to redraw the window area ; (WM_PAINT command) ; The message is sent to the window directly PUSH SI CALL UPDATEWINDOW ; IV. Waiting loop LOOP1: ; Retrieve the message from the queue PUSH DS LEA BX, MSG ; Pointer to the message structure PUSH BX PUSH 0 PUSH 0 PUSH 0 CALL GETMESSAGE ; Check whether the "exit" message has been received CMP AX, 0 JZ NO_LOOP1 ; Convert all received messages to the ANSI standard PUSH DS LEA BX, MSG PUSH BX CALL TRANSLATEMESSAGE ; Instruct Windows to pass this message ; to the appropriate window PUSH DS LEA BX, MSG PUSH BX CALL DISPATCHMESSAGE ; Close the message-processing loop JMP SHORT LOOP1 NO_LOOP1: RET MAIN ENDP ; Procedure for the specified window class ; Windows passes the following parameters to this procedure: ; HWND - Window descriptor, WORD type ; MES --- Message number, WORD type ; WPARAM - Additional information, WORD type ; LPARAM --- Additional information, DWORD type WNDPROC PROC PUSH BP MOV BP, SP MOV AX, [BP+0CH] ; MES - Message number CMP AX, 2 ; Is this the WM_DESTROY message? JNZ NEXT ; Pass the message about application termination ; This message will be received by the message-handling ; loop, and the application will thus be terminated PUSH 0 CALL POSTQUITMESSAGE JMP _QUIT NEXT: ; Pass the message further to Windows ; This means that everything that wasn't processed ; by the procedure is provided to Windows for processing PUSH [BP+0EH] ; HWND PUSH [BP+0CH] ; MES - Message number PUSH [BP+0AH] ; WPARAM PUSH [BP+8] ; HIGHWORD LPARAM PUSH [BP+6] ; LOWWORD LPARAM CALL DEFWINDOWPROC ;****************************************************** _QUIT: POP BP ; The call to the window procedure is always FAR; therefore, you use RETF RETF 10 ; Clear the stack from the parameters WNDPROC ENDP CODE ENDS END _BEGIN
|
That's all. The compiled and linked program will start both in older versions of the operating system and in all newer versions of operating systems from the Windows family. I'd only like to draw your attention to the .DOSSEG directive. This directive specifies a certain order of segments in the EXE file. This order is as follows: The starting segment is the segment of the CODE class, it is followed by segments of classes other than CODE that do not belong to the group of segments ( GROUP ), then there are segments grouped using the GROUP directive. At the same time, the segments not from the BSS and STACK classes go first, then come the segments of the BSS class (if any), and the last segment is the segment of the STACK class.
For Assembly language, Windows 95 was a significant advance. Obviously, Assembly language programming became considerably easier.
Now, say good-bye to 16-bit programming. You'll never encounter it again in this book.
[i] As you'll see for yourself, 16-bit programming is slightly more difficult than 32-bit programming.
[ii] I hope that you remember that in 32-bit model division of data and code into segments is a matter of convention.
| ||
| ||
|