Asm - Windows Assembly Language and Systems Programming
Asm - Windows Assembly Language and Systems Programming
2nd edition
bY
Barry Kauler
R&D Books
Lawrence, Kansas 66046
0 Copyright 1997, Barry Kauler
All rights reserved. No part of this publication may be reproduced, stored in a retrieval
system, or transmitted in any form or by any means, electronic, mechanical,
photocopying, recording, or otherwise, without prior written permission of the Publisher.
In this book, many of the designations used by manufacturers and sellers to distinguish
their products may be claimed as trademarks. Due acknowledgement is hereby made of
all legal protection. Windows TM is a trademark of Microsoft Corporation.
Disclaimer. Whilst due care has been taken in the preparation of this book, no
responsibility is accepted for any inaccuracy, loss or damage to data, or consequential
loss or damage. The content of the Companion Disk is not guaranteed to be exactly as
described.
ISBN: 087930474X
DISTRIBUTION:
USA Canada
Publishers Group West Publishers Group West Canada
P.O. Box 8843 543 Richmond Street West
Emeryville, CA 94662 Suite 223
Tel: (800) 788-3123 Toronto, Ontario M5V lY6
Fax: (510) 658-1834 Canada
Tel: (416) 504-3900
Fax: (4 16) 504-3902
UK and Europe Asia
McGraw-Hill Publishing Co. Longman Singapore
Shopper&angers Road 25 First Lok Yang Road
Maidenhead Singapore 2262
Berkshire SL6 2QL Tel: 65 268 2666
United Kingdom Fax: 65 268 7023
Tel: 0800 810800 or 01628 502500
Fax: 01628 770224
e-mail: [email protected]
Latin America Editorial & Marketing Oftice
ID International R&D Books
126 Old Ridgefield Road 1601 West 23rd Street, Suite 200
Wilton, CT 06897 USA Lawrence, KS 66046
Tel: (203) 834-2272 Tel: (913) 841-1631
Fax: (203) 762-9725 Fax: (913) 841-2624
e-mail: [email protected]
Web: http://www.rdbooks.com
Contents
Ch. Page
Preface xi
1 CPU Architecture 1
Preamble ..................................................1
Power-up the PC ........................................... 2
The System Files ....................................... 3
Number Systems ........................................... 6
Registers and Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
MemoryMapofthePC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
The CPU & Support Chips ........................... 12
Conventional and Extended Memory ..................... 14
Segments ............................................ 14
Real Mode ........................................... 17
DOS Real Mode Programming ....................... 18
DOS Protected Mode Programming .................. 18
Coding Restraints .................................... 20
Inside the 286/386l486/etc. ............................... 2 1
CPU Registers ....................................... 22
Instructions .......................................... 23
Real and Protected Modes ............................ 25
Memory Management .................................... 25
Segmentation Only ................................... 25
Shadow Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Descriptors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
386 Paging ........................................... 28
Virtual-86 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Contention Issues ........................................ 3 1
Privileges.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
I/O Privilege ......................................... 3 1
Task Switching ...................................... 32
Interrupts ................................................ 3 3
Real Mode Interrupts ................................. 33
Protected Mode Interrupts ............................ 34
Postamble ............................................... 36
..I
111
iv
Ch. Page
2 Basic Assembly Language 37
Preamble ................................................ 3 7
Stack Instructions ........................................ 38
Transfer of Control ....................................... 39
Conditional Jump .................................... 43
Addressing Modes ....................................... 44
Segment Registers ................................... 46
String Instructions ....................................... 47
Arithmetic Instructions ................................... 50
Logical Instructions ...................................... 54
Code and Data Labels .................................... 56
Code Labels ......................................... 56
Data Labels .......................................... 58
Accessing Data ...................................... 5 8
Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
LES, LDS, and LEA Instructions ..................... 60
Local Data ........................................... 62
Type Override ........................................... 63
Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Label Equates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Postamble ............................................... 67
3 Opening Windows 69
Preamble ................................................ 69
DOS versus Windows Programming ..................... 70
Internal Differences .................................. 7 1
Building a Windows Application ......................... 72
Library Functions .................................... 72
The Mechanics of Assembling and Linking ........... 73
The Link Step ........................................ 74
Two Steps for Resources ............................. 74
Windows Programming Mechanics ....................... 75
Objects .............................................. 75
Handles .............................................. 76
Instances ............................................. 76
Messages ............................................ 77
C Syntax ............................................. 78
Message Loop ....................................... 78
Callback Functions ................................... 79
Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
V
Ch. Page
4 The Bare Bones 85
Preamble ................................................ 85
Getting Started ........................................... 86
Tools Required . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Source Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Resource and Definition Files . . . . . . . . . . . . . . . . . . . . . . . . 89
Message Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Make File ............................................ 91
Development Cycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Application Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Preliminary Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Startup Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
WINMAIN{) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Callback Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5 High-Level Assembly 109
Preamble ............................................... 109
Include Files ............................................ 109
Microsoft versus Borland ........................... 110
Skeleton Analysis ....................................... 111
.MODEL Directive ...................................... 119
Private and Global Data ................................. 120
MASM versus TASM Scope ........................ 121
TASM’s @@ ....................................... 121
Life of Automatic Data .............................. 122
Assembling and Linking ................................ 123
MASM6 versus TASM .................................. 125
WINDOWS Qualifier ............................... 126
Prototypes .......................................... 127
Callback Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Other Incompatibilities .............................. 130
MASM Assembling and Linking .................... 13 l
MASM6 Program Listing ............................... 132
6 Program Design 137
Preamble ............................................... 137
Object Addressing ...................................... 138
Calling a Function .................................. 138
Early Binding ....................................... 14 1
Late Binding ........................................ 142
C++ Binding ........................................ 142
vi
Ch. Page
Assembly Language Binding ........................ 145
Use of THIS ............................................ 145
Interfacing with C++ .................................... 147
Compiling to ASM O/P ............................. 147
In-Line Assembly ................................... 148
In-Line DOS and Don’ts .............................. 149
The ASM Stub ...................................... 150
Compile and Assemble Steps ........................ 15 1
The Amazing 9-Line Program ........................... 153
A Skeleton Program ..................................... 154
Overrides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Kickstart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Message Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
The WINDOW Object .................................. 158
WINMAIN ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Callback ................................................ 165
MA=( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Getting it Together ...................................... 175
Postamble .............................................. 178
7 PC Hardware 179
Preamble ............................................... 179
CPU bus ................................................ 179
Control Bus ......................................... 180
Address Decoder .................................... 182
I/OPorts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
I/O Instructions ..................................... 184
Keyboard Interface ...................................... 184
AT-Class Keyboard Port Enhancements ............. 186
PC Expansion Buses .................................... 187
Industry Standard Architecture (ISA) ................ 188
Peripheral Connect Interface (PCI) ...................... 19 1
Postamble .............................................. 194
8 BIOS, DOS, & Windows Low-Level Services 195
Preamble ............................................... 195
BIOS and DOS Services ................................ 197
Standard DOS Interrupts ............................ 200
DOS Protected Mode Interface (DPMI) ................. 203
INT-2Fh Extensions ..................................... 205
vii
Ch. Page
Windows Functions ..................................... 207
Thunking ............................................... 219
Generic Thunking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
More Win95 “Improvements” ........................... 222
Device I/O Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
Dynamically Loadable Drivers ...................... 223
Threads ............................................. 223
Memory Mapped Files .............................. 224
Postamble .............................................. 224
9 Direct Hardware Access 225
Preamble ............................................... 225
Initialisation ............................................ 226
Addressing Segments ................................... 227
Direct Video . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Restore Video ....................................... 23 1
Change Video Mode ................................ 232
A Direct-Video Text-Mode Routine ................. 232
Call REPAINTSCREEN . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Ordinal Coordinates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
To and From Text Mode ............................ 236
Video Output Issues ................................. 237
MessageInput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Experimenting ...................................... 239
A Direct-Video Window Program ................... 239
I/O Ports ................................................ 244
10 Real-Time Events 249
Preamble ............................................... 249
TSRs ...................................................250
Hooking a Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Service Routine (ISR) ............................... 253
Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
Hardware Interrupts ..................................... 256
XT Hardware Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
AT Hardware Interrupts ............................. 257
Windows’ Standard Mode Hardware Interrupts ...... 258
Interrupt Handler Code .............................. 260
Enhanced Mode Hardware Interrupts ................ 263
Direct Memory Access .................................. 264
11 Real Mode Access 267
CPU Architecture 27
pz?ii
1 GDT-register t\
etc.
* = 0 if pointing to
the GDT,
* = 1 if pointing to LDT, task 2
an LDT.
r PI requested
= . . . task 3, etc.
privilege level.
Association The next step in this saga is that the CPU can use the selector in
between the CS register to index into the current LDT and get the actual
descriptor address, or more correctly the descriptor, of the code segment.
ands/radow The IP register (or EIP) will have the offset into that segment from
register which the CPU wiII fetch the instruction.
Having read the descriptor from the LDT, the CPU then has the
base address of the code segment. To avoid having to look in the
LDT every time it wants to fetch the next instruction, the CPU
makes use of shadow registers again. Every segment register has
an associated shadow register.
28 Windows Assembly Language & Systems Programming
The CPU will only have to look in the shadow register to find out
the starting address of the segment (plus some other information)
and can then go ahead and put together the full 32-bit address for
fetching the instruction.
The CPU will add the base address to the offset IP and get a 32-bit
address that can be put onto the address bus.
Descriptors
I have introduced the descriptor as being an entry in the GDT or
LDT. There are various types of descriptors, but the most
common is the normal addressing type that we have been
discussing so far.
Each descriptor is 8 bytes in size, and Figure 1.13 shows what a
normal descriptor looks like.
Figure 1.13: Descriptor format.
64 55 47 39 15 0
base+ # access base limit
I
Size of
Normally set “Base” is the address segment.
to zero on the of the segment.
286.
“Base+” extends the base segment addressing beyond
24 bits. “#“extends the limit beyond 64K.
AC&?SS field The access byte in Figure 1.13 has various flags and codes. It has
a two-bit DPL field (Descriptor Privilege Level) that determines
the privilege level of the segment. It has P (Present) and A
(Accessed) bits that are used for moving the segments in and out
of memory. There are R (Read) and W (Write) bits that set
constraints on reading and writing the segment. There is also the
C (Conforming) bit and ED. The latter is set if the segment is a
stack.
I go into the description of the descriptor in far greater detail in
Chapter 12.
386 Paging
There are two paging modes in the 386. One is built on top of the
descriptor tables, and the other, called virtual-86, does away with
the descriptor tables altogether.
CPU Architecture 29
I’ll look first at the one built on top of the desriptor tables. From
our program point of view it Iooks just like the segmentation
mechanism with the GDT and LDTs. The only difference is that
the CPU secretly stores the segments in actual memory not in one
contiguous chunk, but all over the place as 4Kpage.s.
What’s Why go to this trouble? The operating system has trouble bringing
wrong with segments in and out of memory because they are all different sizes
segments? - if a new segment is to be brought in, space must be found for it,
but space released by a segment that has vacated its spot may not
be the right size. This is a real problem for the operating system,
and it ends up with lots of little unused gaps everywhere.
Inefficiency.
By transparently parcelling the segment up into lots of little pages
all the same size and storing them wherever there is a space, the
mismatch of segment sizes is no longer a problem. We know that
a space vacated by a departing page will be exactly the right size
to take a new page. No problem.
Page tables Well, there is one. To achieve this, more translation tables are
and control required, called page tables. The CR registers are used to address
registers these, and the page tables are kept in memory just like the
descriptor tables.
The CPU has various extra registers for maintaining the paging
mechanisms, most importantly, CR3, which contains the base
address of the Page Table Directory.
Just for the record . . .
Linear The address computed from the descriptor table, now renamed the
address linear address (as it is no longer the final physical address), is
divided into fields, with bits 22 to 31 being an index into a
page-table directory that gives the address of a particular page
table. Bits 12 to 21 are the index into this second table, which
contains the final address. Bits 0 to 11 are unchanged and become
part of the final address.
You will come across the words linear address later in the book.
Note that sometimes the words virtual address are used in various
books to mean the same thing, though there is a distinction. The
linear address is that 32-bit address that would be the physical
address if page tables didn’t get in the way.
Virtual-86
This is another paging mechanism that does away with descriptor
tables. It was intended to provide the 386 with better Protected
mode emulation of the 86 CPU than the 286 can manage, which it
does very well.
30 Windows Assembly Language & Systems Programming
386 PC
Appar-
ent 1M
address
space.
1 Virtual XT PC
I Virtual XT PC
Jbe upper8 Instead of putting the 20-bit linear address onto the address bus, as
bits of the for Real mode, virtual-86 mode uses the upper 8 bits of this
linear address as a lookup in the current page table - note that the table
entry contains the base address of the page, which is combined
address are
with the lower 12 bits of the linear address to form the actual
remapped 32-bit address. It is this final 32-bit address that the CPU puts out
for a memory access. Refer also to page 274, Figure 11.2.
CPU Architecture 31
Contention Issues
There are various things to think about under this heading, but I
have at this stage just addressed the issues of privileges, I/O, and
task switching.
The topics are brought up at various points through the book, so
look in the Index for other page references.
Privileges
The dpl field in the descriptor defines the privilege level of that
segment. Also you will see back on page 27, Figure 1.12, that the
selector has a requested privilege level (rpl).
Four Because it is a 2-bit code, there are four possible levels, zero being
privilege the most privileged. The kernel of the operating system will
levels operate up here (zero), while your lowly program will reside at a
lower privilege level.’
Your program’s level is basically reflected in what the rpl is set to,
and this must be numerically equal to or less than the segment’s
dpl to allow access to that segment - otherwise the CPU exits to
an error routine and the dreaded UAE (Unrecoverable Application
Error) dialog box appears, and that’s the end of your program!
I/O Privilege
IOPL f i e l d Privilege levels do have some impact on I/O. If you look at the
FLAGS register (see page 244), you’ll find 2 bits that hold the
Input/Output Privilege Level (IOPL). Your application must have
a privilege level numerically equal to or less than this to be able to
perform I/O. With Windows, the IOPL field is set to zero, most
privileged.
IN, OUT, CL!, However, it is possible for the operating system to give permission
and ST/ for certain I/O to occur, even though the application doesn’t have
the right privilege. I/O access involves use of the IN and OUT
instructions and control of the interrupt flag by CL1 and ST1
’ Windows 3.0 runs WinApps at level 1, DOSApps at level 3, and DLLs at level 1. Windows 3. I
and later run all three at level 3.
32 Windows Assembly Language & Systems Programming
Task Switching
Considering the complications of multitasking, I sometimes
wonder if it is all worth it. Perhaps a more effective solution
would have been multiple CPU-boards, each single-tasking.
Anyway, we are stuck with the current situation.
Changing Changing from one task (program) to another is a matter of
Lors changing to a new LDT,’ which involves the CPU looking into the
GDT and getting the new LDT’s address.
However, the “state” of the task about to be suspended must be
saved, and the “state” of the incoming task must be restored. This
state consists of the CPU and coprocessor registers plus various
memory pointers and values, and an incredible time overhead is
involved to save and restore this lot.
lask State The CPU has to maintain a special segment for each task, called
Segment the Task State Segment (TSS), into which all of this goes. Then, of
course, the CPU must keep track of where these TSSs are, so it
maintains descriptors for the TSSs in the GDT. Thus the GDT
contains more than just descriptors for the LDTs.
’ Windows 3.x and 95 have only one LDT for all applications, whether in Standard or Enhanced
modes, which is a compromise in its design that can potentially cause trouble. This limitation
tallies with DPMI version 0.9, which in Windows maintains one LDT per virtual machine, not
per task. Windows is seen as a single client to DPMI. Windows 95 32-bit applications have
individual LDTs.
CPU Architecture 33
Interrupts
Real mode Like everything else, Protected mode interrupts are a whole new
interrupts ball game. First, let’s review the mechanism in Real mode.
The standard method of doing I/O and file and memory
management, plus a heap of other operations, was by the BIOS
and DOS interrupt services. These are accessed from an
application program by means of the INT instruction, with this
syntax:
3. The CPU then loads the FAR address into its CS:IP registers
and commences execution of the service routine.
4. Interrupt routines always terminate with an IRET instruction,
which has the effect of popping the three values saved on the
stack back off, into CS, IP, and FLAGS. Thus the CPU
carries on as before, as though nothing had happened.
Postamble
This chapter mapped out the overall architecture of the x86
processor, and you may have found some of it heavy going.
Subsequent chapters are a step back, and topics are revisited in
depth. Chapter 2 is an in-depth treatment of the basics of
assembly language.
2
Basic Assembly
Language
Preamble
Content This chapter contains an introduction to assembly language for the
of this x86 family of processors. The focus is on 16-bit programming.
chapter Later chapters will expand this to 32-bit programming.
Real mode 16-bit programming can be considered an essential step
up the ladder of understanding, climbing through 16-bit Protected
mode, toward 32-bit Protected mode programming.
Chapter 4 puts this knowledge to use in a first 16-bit Windows
application.
Discussion relates to the Microsoft and Borland assemblers,
though of course there are other compatibles.
37
38 Windows Assembly Language & Systems Programming
Stack Instructions
fnitiafisation The computer maintains a stack somewhere in memory. DOS will
of the stack set the Stack Segment register SS when your program is loaded,
and the Stack Pointer SP will be initialised to FFFEh, or some
value that means the stack is empty. The stack is used by the
computer and by your program. For example, whenever an
interrupt occurs the CPU pushes the IP, CS, and FLAGS onto the
stack, so that when the interrupt routine is finished (terminated by
an IRET instruction) the CPU will pop these values back into the
respective registers and continue from where it left off.
Purpose of Thus the stack is used to hold register values to enable the CPU to
the stack return from an interrupt and also from a procedure CALL.
However you can make use of the stack in your program, by
means of the PUSH instruction, which pushes a 16-bit value onto
the stack, and POP, which pops the top value off the stack into a
register or memory location. Also PUSHF and POPF can be used
to push the FLAGS onto the stack and pop them off.
Whoa! This is a lot to think about! I’ve just stated above that
there is a memory area called a stack, that it is used by the CPU to
store register values for interrupt and CALL-instruction execution,
and it is used by the PUSH and POP instructions. You may find it
extremely helpful at this point to visualise what is happening.
Look at Figure 2.1 and examine the effect of the PUSH and POP
instructions.
*.. t e m p o r a r y In Figure 2.1 you see two instructions, PUSH and POP, that you
storage can use in your program. You can push values onto the stack, and
take them off again - why? - one reason is that it serves as a
convenient temporary storage.
,,a C A L L / R E T I also mentioned that the stack is used by the CALL instruction -
this is one of the “transfer of control” instructions and is described
in the next section.
*.* interrupt I mentioned that interrupts also use the stack - again, explanation
mechanism is deferred.
Do not worry about these deferred explanations - one thing at a
time. Examination of Figure 2.1 will give you an idea about what
the stack is, which is satisfactory for now.
Basic Assembly Language 39
Transfer of Control
The idea of a computer program is that it is a sequence of
instructions: in this book we are looking at machine instructions
that the CPU directly understands. Assembly language is just a
symbolic (more meaningful) way of writing the machine
instructions.
40 Windows Assembly Language & Systems Programming
The CPU executes the instructions sequentially - that is, one after
the other in order of increasing addresses - but can also jump out
of sequence.
LOOP, JMP, The topic of this section is those instructions that cause execution
CALL.. /NT.. to go to some other place in the program. The main ones are:
JX LOaP, Jh4P, CALL, I&T, and Jx. in this section we will examine
CALL, JMP, and Jx. LOOP and INT are examined a little bit
later:
Figure 2.2: Stack handling for CALL and RET.
CPU
Figure 2.2 illustrates how the CALL and its companion RET use
the stack. The basic idea is that the value in the Instruction
Pointer, IP, is always the next instruction to be executed, so when
“CALL ROUTINEX” is executing, IP will have IPm in it. Since
the value in IP has to be changed to the subroutine, IPx, the return
value has to be saved somewhere: hence the stack is used to save
IPm. The RET instruction must always be placed at the end of a
procedure, as it pops the top off the stack, back into IP.
If you have programmed in C or Pascal, you know that you don’t
put a RET, or anything special, at the end of a procedure or
function. CALL and RET do go into the code, though, because the
Basic Assembly Language 41
Code labels One thing that you will notice from Figure 2.2, is that I used a
code label, ROUTINEX, to name the start of the procedure. This
is basically what you expect to be able to do in any high-level
language, and you can also do this in assembly language. A code
label marks, or identities, that point in the code, hence a CALL
was able to be made to that place.
Code /abeIs With a professional assembler, such as the Borland TASM, or
with MASM, Microsoft MASM, these labels are a normal part of writing a
TASM, program, but DEBUG is a different story.
DEBUG DEBUG CANNOT HAVE LABELS!
With DEBUG any instruction that transfers control to another
address must contain the actual offset.
What is What is DEBUG? It is a program that comes with DOS, and from
DEBUG? the DOS prompt you will only have to type the name of the
program to execute it. DEBUG.EXE is a way of becoming
familiar with the instruction set - it allows you to try out the
instructions and put together simple programs.
These examples show that DEBUG must have an actual address,
not labels:
MOV CX,9
PLACEl: I*this is at 113 (say)
MOV AX,0 I-arbitrary instr
LOOP 113 ;absolute offset (no label)
42 Windows Assembly Language & Systems Programming
jmp PLACE1
PLiCEl: ;code l a b e l .
mov ax,0 ;arbitrary i n s t r u c t i o n .
Increasing
addresses
downward
In Figure 2.3 you can see the basic scenario. The first one (or
sometimes two) memory location(s) contain the instruction-code,
or operation-code, often referred to as the op-code, that identifies
this as a JMP instruction (or whatever), while the following zero
or more bytes are the operand.
NEARJMP In the case of the NEAR jump instruction, the operand contains a
16-bit offset, which is the place to jump to. But, and this is most
important, the addressing structure of all the Intel x86 CPUs uses
Basic Assembly Language 43
Iancrcesiey Operation-code
Operand
downward
. \1 Fl
Range of a This reduces the instruction down to the one-byte (g-bit) op-code
SHORTjump followed by a one-byte 2%complement displacement. This
displacement allows jumps to be only +127 to -128 about the
current IP position.
In some circumstances, the assembler will automatically make the
jump SHORT, but it can also be forced to, by means of the
SHORT directive.
Conditional Jump
The conditional-jump instructions test various flags before
deciding whether to jump or not. These instructions are always of
the SHORT type. This is very important - they can only jump
128 locations away from the current code location.lhe conditional
jump instructions are sometimes confusing for the student,
however the concept becomes quite clear with a little practise.
Most CPU instructions affect the flags after they have executed,
44 Windows Assembly Language h Systems Programming
and the conditional jump instructions can be used to test the flags
and jump accordingly.
Below is a summary of the conditional jump instructions:
When using these instructions, you do not enter the part in italics.
Signed and Note that when comparing two values, we need to distinguish
unsigned between whether the values are unsigned or 2’s complement.
compare Here are simple examples:
ADD AX,VALl
JZ ZERORESULT ;jumps if previous result=O(zero-flag
; set)
&1;, AX,56 *compare instr.
JA ABOVE56 ljumps if AX>56
ivariations . . .
JNC place1 *jump if Carry flag=0
JE place1 isame as JZ ("Equal")
JAE place1 ;unsigned jump, if above or equal.
JBE place1 I*unsigned jump, if below or equal.
Addressing Modes
Obviously, the instructions of your program will be accessing
registers and memory, and the mechanisms by which this is done
are called the addressing modes.
The best way to show this is by example:
VALl DW 0
'P&7 AX,BX I*register addressing mode.
MOV AX,567 *immediate addressing mode.
MOV AX,[567] ldirect addressing mode
Basic Assembly Language 45
That just about covers it, except that indirect addressing does have
some options, as shown in the last two instructions above.
The first one adds the contents of BX to 5, and the result is the
address, while the second example adds the contents of BX, SI,
and 5 to form the address. This modified form of indirect
addressing is called indirect plus displacement if a constant is
specified, or indexed indirect if two registers are specifed.
Restrictions Note that we often just label these various indirect modes under
on indexed the title of indexed addressing.
addressing
46 Windows Assembly Language & Systems Programming
Segment Registers
Another thought: how do you access data in DS, the data segment?
This is the place to keep data, so obviously your program must be
able to get to it. Simple: most instructions automatically reference
the DS.
For example, the listing below shows how VALl is defined and
referenced:
.DATA
VALl DB 0 I-in data segment.
.&E
mov ax,VALl I*in code segment.
Later, you will see more details on how to use the assembler, so
don’t worry about that side of things. Suffice to say that you can
define a label in the data segment and reference it from the code
segment.
When the program is assembled, the address of VALI will be put
into the operand of the MOV instruction: note however that this is
an offset relative to the DS.
Most importantly, when your program is executed, it must have
DS set to the beginning of the data area, as the MOV instruction
will automatically use DS to compute the physical address.
Sometimes, especially with pop-up and interrupt routines, the
program may be entered with DS not set correctly, so you have to
take care of that at the beginning of the program.
.DATA
.&DE
jmp place1
VALl DB 0 ;data defined in code segment.
placel:
mov ax,cs:VALl
Basic Assembly Language 47
String Instructions
This group of instructions are designed for moving blocks of data
from one place in memory to another, and some of them are for
searching through and comparing blocks of data. The word
“string” does not necessarily imply text, but any block of data.
Mostly you will use the string instructions responsible for moving
data around, such as MOVS, LODS, and STOS. Basically, you
have the source block in one part of memory and the destination
somewhere else, and you have to set certain registers to point to
these source and destination areas before using the string
instruction.
Concept of The string instructions have an “implied” addressing mode, in that
the string they use certain predetined registers, as shown in Figure 2.5.
iflstructions Figure 2.5 is a picture of memory. DS:SI is where the data is, and
ES:DI is where it’s sent.
MOVSB, for example, would read a single byte from DS:SI, copy
it to ES:DI, and automatically increment both SI and DI, so that
the next time the instruction is executed the next byte will be
copied.
W/B pos ffix All the string instructions can be postfixed with a “B” or a “W”.
MOVSW would move two bytes of data (one word) and SI and DI
would automatically increment by two.
48 Windows Assembly Language & Systems Programming
Auto- String operations make use of SI and DI to point to the source and
increment destination strings respectively, and they are automatically
updated each time the string instruction is executed.
Directiofl There is a direction flag, DF, that is cleared by instruction CLD,
flag, DF and set by instruction STD. If DF is clear, the string instruction
will automatically increment SI and/or DI to point to the next byte
or word, and if DF is set they will be decremented. It is normal to
operate on a string starting from the lowest address in memory, so
use CLD before a string operation (this is the default for the 80x86
family anyway).
DF is one bit of the FLAGS register, shown on page 244.
CLD and STD are described in the Appendices.
REP prefix REP is a prefix, placed on the same line and before a string
instruction. It means “check if CX = 0, if not perform the string
instruction, decrement CX, then start again”. Example:
mov cx,str_length
rep movsb ;repeat with cx = count.
mov cx,str_length
Basic Assembly Language 49
mov cx,str_length
rep cmpsb
jnz difference - fnd
This example will compare the two strings until the end of the
string (set by value in CX) OR until a non-equal comparison is
reached (in which case CX will point to the position in the string at
which the difference was found, and the zero-flag will be clear).
SCASB, Use these instructions to compare AL or AX with the value
SCASW pointed to by ES:DI. Note: they are most often used with REPNE.
A typical use is:
Command- The code searches the DOS command-tail in the PSP (see Figure
tine tail 1.8) to see if there is a “switch” (“/” followed by a letter).
If the loop terminates without finding a slash, CX will equal zero,
so the special conditional jump instruction, JCXZ, which tests if
CX = 0, can be used to detect that no slash was in the string.
Because the string-instruction automatically increments DI each
time, at termination DI will point to the next character past the last
one tested. If the slash was found, this next character will be the
switch.
Note that Windows 3.x and 95 applications still have a PSP.
LODSL?, The value in the location pointed to by DS:SI is loaded into AL or
LODSW AX. SI is automatically incremented (+/-1 if LODSB, or +/-2 if
LODSW).
STOSB, The value in AL or AX is stored at the location pointed to by
STOSW ES:DI. DI is automatically incremented (+/-1 if STOSB, or +/-2 if
STOSW).
STOS and LODS are most useful for video access, as the format of
video-RAM in text-mode requires every odd byte to be an attribute
character:
; . . . setup ES:DI....
. . . setup DS:SI....
r&v c x , s t r i n g l e n g t h
mov ah,attribiite
. . .
next c h a r :
lodEb ;char-->AL
stosw ;AX-->destination.
loop next char
; . . . this code will send characters to the screen
Arithmetic Instructions
PREREQUISITES
These include addition, subtraction, multiplication, and division. I expect you to have
a working knowledge of the principles of binary arithmetic: unsigned binary numbers,
2's complement binary numbers, radix conversion among hex/binary/decimal.
For example, suppose I ask you to express -2 as a 32-bit binary number, and also as a
32-bit hexadecimal number. Can you do it? If the answer is yes, then you do have a
few clues, so read on. Otherwise look back at Chapter 1, and consolidate with further
study if required.
Basic Assembly Language 51
mov al,127
neg al
mov al,-127
A useful point to note about the assembler is that you don’t ever
have to calculate the binary or hex negative 2’s complement
number; just put a minus sign in front and the assembler will do
the conversion. The last line shows this.
/NC, D E C (INCrement, DECrement). These two do what their names
instructions suggest; add 1 to an operand or subtract 1 from it.
Since we have specified an 8-bit operand in the examples below, if
INC goes beyond 255 (FF hex), then it will simply roll around and
start from zero. Ditto, but the opposite, for DEC.
inc al
dec al
ADD, SUB Recall from the above notes that ADD/SUB arithmetic instructions
hstmctions don’t know whether your operands are 2’s complement or unsigned
numbers - that interpretation is up to you. The size of the
operands are important in these calculations, and the instruction
determines that from the operands themselves.
SUB works just like CMP, setting the same flags (and so can be
followed by a conditional jump), but the subtraction is not
hypothetical - the result of the subtraction is left in AX.
add al,127
sub al,127
The first example makes the assumption that the other operand is
in AL, so the result will appear in AX. The second example
makes the assumption that the other operand is in AX, and the
result will be in DX:AX.
Division has problems of its own. The dividend (the operand to be
divided) is in either AX or DX:AX, and the divisor is in any other
register or variable (8 or 16 bits).
d i v bl ;ax/bl --> a h a n d a l .
div bx ; d x : a x / b x --> dx and ax.
Logical Instructions
Logical instructions basically work on individual bits rather than
complete numbers. They relate back to boolean algebra, and as
with the arithmetic instructions, I assume a certain background
knowledge. You should have a basic understanding of the boolean
AND, OR, EXCLUSIVE-OR, and NOT functions.
AN.., TEST AND performs a logical AND on corresponding bits in two
operands, leaving the results in one operand.
mov a1,01001000b
and al, OOOOlOOOb ;answer a l = OOOOlOOOb
TEST is just like AND but only does the operation hypothetically
and doesn’t change the operands (this is very similar in concept to
the relationship between SUB and CMP).
OR OR performs a logical OR operation on two operands.
I
Basic Assembly Language 5.5
mov a1,01001OOOb
xor a1.00001000b :result al = OlOOOOOOb
mov a1,01001000b
not al ;result al = 1OllOlllb
SHL, SHR SHL (SHift Left) and SHR (SHift Right) do what they suggest, but
it is clearer if their operation is viewed diagrammatically (Figure
2.7):
Figure 2.7: Shift instructions.
Examples of shift and rotate
instructions . . .
SHR AL,1
SAR AL, 1
ROR AL, 1
RCR AL, 1
The example of SHR moves all bits in AL one place to the right,
and a 0 into the most significant bit (MSB). Note that the least
significant bit (LSB) goes into the carry flag, CF.
This instruction is sometimes used to test individual bits, since it
can be followed by JC (Jump on Carry set) or JNC (Jump on Carry
not set).
56 Windows Assembly Language h Systems Programming
A limit with the 8088/8086 is that the “count” operand can only be
a value of 1 if in immediate mode, as shown in Figure 2.7. If the
shift is to be more than 1 bit, a count value must first be moved
into CL:
mov cl,3
Sk-X al,cl ,*shift 3 bits right.
Note that the shift operations can also be on 16-bit (and 32-bit)
registers.
SHL does exactly the opposite of SHR, moving zeros into the LSB
and the MSB out to the carry flag.
SAR SAR (Shift Arithmetic Right) works like SHR, except it maintains
the sign. This is most useful for signed numbers. Refer to Figure
2.7.
ROL, ROR ROL (Rotate Left) and ROR (Rotate Right) work similarly to the
shift instructions, except what falls out is rotated around back in
the other end. Refer to Figure 2.7.
Thus the contents are never lost, but circulate around the register.
ROL is the mirror-image of ROR, sending the MSB to the carry
flag and back around to the LSB.
RCR, RCL RCR (Rotate through Carry Right) and RCL work as per ROR and
ROL, except the path of the bits goes through the carry flag. See
Figure 2.7.
Code Labels
In the case of a code label, the syntax is that it should start in
column 1 and be suffixed with a colon ” : “, as in this example:
. . . .
jmp place1 I*jumping to somewhere in the program.
. . . .
placel: ;a code label.
. . . . .
Basic Assembly Language 57
. . . .
call routine1 I*calling a p r o c e d u r e .
. . . .
routine1 PROC *the p r o c e d u r e .
. . . . . Ibody g o e s i n h e r e .
ret I*must h a v e e x p l i c i t r e t .
routine1 ENDP
PROC and Procedures allow you to organize code into structured modules,
ENDP that can be called from a main procedure. In some languages they
are called subroutines. A function is a special case of a procedure
that returns a value via a register. For example, C functions return
a value in the AX register or DX:AX register pair (though when
writing C programs you don’t know this underlying mechanism of
the registers).
The point I want to make here is that procedure names are treated
by the assembler just like code labels. In the above example,
“routine1 PRO? could have been replaced by “routine1 : ” (in
which case the “routine1 ENDP" would not be needed, since it is
a syntactical requirement to match the PROC directive).
58 Windows Assembly Language & Systems Programming
Data Labels
Data labels define constant or variable data, including numerical
values, strings, arrays, and pointers.
Major The above examples show the difference. At execution time the
distinction second MOV instruction will move the actual address of place1
between code into a, while the other MOV instruction will use a
and data non-immediate mode, moving not the address varl, but its content.
labels Thus, although "MOV ~~,varl" assembled with the address of
varl as the operand to the instruction, at execution-time the
instruction looks at the content of that address. Make sure you
have grasped this distinction before continuing.
Accessing Data
Sometimes, when writing a program, you want to know the
address of something, say a point in the program, or the starting
address of an ASCII string. I gave an example of how to define a
Basic Assembly Language 59
OFFSET and SEG only work for static data; that is, data that is
defined in the data or code segments. It is possible to have
dynamic or automatic data that is created during execution on the
stack or heap: getting the addresses of this data involves other
techniques, discussed on page 60 (and in Chapters 4 and 5).
Pointers
Data labels can also be pointers. This means that the data content
is itself an address. Earlier, I defined “ptrl DW 789”, but the
treatment of the content “789” is up to the program. Consider
these examples:
Immediate “call ptrl” at execution-time will not jump to the ptrl data in
versus non- the data segment - obviously that wouldn’t make sense. No,
immediate since the CALL instruction has assembled as a non-immediate
mode CALL addressing mode, even though the operand of the instruction is the
address ptrl, the instruction looks at the content of ptrl and uses
that. Thus execution will transfer to offset 789 in the code
(wherever that is!).
60 Windows Assembly Language & Systems Programming
.DATA
ptrl DW place1 ;defining a p o i n t e r .
.CODE
call ptrl
place1 :
. . ,
.CODE
mov DI, OFFSET place2
mov ES,SEG place2
l e s DI , place2 ;!!!!!! Example of what NOT to do!
. . . .
place2:
. . .
routine2 PROC
LOCAL ptr4 : DWORD ;local data created on s t a c k .
i&’ DI,ptr4
. . . .
ret
routine2 ENDP
Some further clarification: the local data label ptr4 only exists
within routine2. LEA will load the offset ptr4 into DI.
LES with “ L E S ~1,ptr4” will load the content of ptr4 into ES:DI
data-label (non-immediate mode, since ptr4 is a data label - which is the
operand only mode LES can handle).
Note that LDS works like LES, but loads DS instead of ES.
The LEA instruction differs from the other two in that it loads the
offset of the label regardless of whether it is a data or code label.
“LEA DI, placel”, for example, would just load the offset
(NEAR address) of place1 into DI, not the segment value.
Local Data
An example is given above, and there is more explanation in
Chapter 5.
So far I have been treating labels (code and data) as being equated
by the assembler to their addresses. But what of the case of local
or automatic data labels that only come into existence when
execution enters the procedure in which they are defined?
How the The assembler equates local labels to [BP-value], where value is
assembJer known at assembly-time, but the BP register will have a certain
equates value at execution-time. If you want to know more about the
automatic special role of the BP register, study Chapter 4. Basically, when
data JabeJs execution enters a procedure, BP has an offset pointing to a region
in the stack segment (see page 99). Addresses going down from
BP can be reserved by the assembler for local data. In the above
example, if ptr4 was the only local data, of DoubleWord size (32
bits, or four memory locations), then the assembler would equate
ptr4 to [BP-4].
Thus an instruction like “lea DI, ptrl” would actually assemble
with the instruction operation code specially encoded to refer to
BP for calculation of the address, immediate mode, and with the
value of 4 as the operand.
(Again, I remind you that the MOV instruction with BP-relative or
index-register-relative addressing cannot be immediate-mode
addressing - see the golden rule above).
Basic Assembly Language 63
Type Override
Looking back to that example of a local data label, ptr4 (see page
61), what if I wanted to see what it contains, from within my
program?
The assembler will be rather rude to you if you give it the first
instruction. The reason is that source and destination operands
must always have the same type.
Type has two aspects to it: size and address.
BYTE, Size can be of type BYTE (8 bits), WORD (16 bits), DWORD (32
WORD, bits), QWORD (64 bits), or TWORD (80 bits).
DWORD, Address can be SHORT (within 128 bytes either way of the
QWORD, current IP; 8-bit signed offset), NEAR (within the current
JWORD, segment; 16-bit offset), or FAR (in another segment; 32-bit
SHORT, segment:offset).
NEAR, FAR In light of this, take a closer look at that example M O V
instruction. BX is a 16-bit register, while the content of ptr4 is
DWORD (32-bit). In other words a type mismatch.
rvPe The assembler will pick this up as a possible error and will tell you
mismatch so.
Any data values you define must have a size that matches the
register. “mov AX, va16” would not work if va16 was defined as
‘%a16 DB 0”. Get the idea?
WORD PTR override. The first will grab the lower 16 bits of ptr4,
while the second will grab the higher 16 bits.
Order of Make a note of this. All values are stored in memory with the
storage of lowest byte at the lowest address and the highest byte at the
data in highest address. That is why I added “+2” to the second MOV
memory instruction.
It may be that in my program I want to see what is contained in
ptr4. Any data label defined as having a 32-bit value has a
problem with the 8088, 8086, and 80286, because there are no
32-bit registers. So if I wanted to get that value into a register, I
would actually have to use two registers. That is why I am forced
to use those two MOV instructions with "WORD PTR" overrides,
even for the 386 (for compatibility with the other CPUs). In
Chapter 4 you will see plenty of examples of this.
If we write code for the 386 and upwards exclusively, then a
simple “mov EAX, ptr4” would do the trick.
.DATA
ptroffset DW 789h I-far pointer stored in
ptrsegment DW 1234h I-two pieces.
.CODE
mov BX,ptroffset
mov ES,ptrsegment
This may not be practical for data values, but for FAR addresses in
the form of 16-bit segment:offset it works fine. It means that
source and destination types will match, so no override is required.
n..more on Another little note: just as with the x86 family we always store
order of values with the lowest byte at the lowest address. The same goes
storage of for FAR addresses; the offset always comes first, that is at the
data in lowest address.
memory In the above code I suffixed the values with “h” to indicate that
they are hexadecimal values, not decimal. The memory would
look like Figure 2.8 after assembly.
Always remember: the lowest byte at the lowest address.
Basic Assembly Language 65
Offset in data
segment of / 89h
“ptroffset”
07h
Offset in data
segment of f 34h
“p&segment”
Increasing
addresses 4
Structures
Whatever language you have experience with, you have probably
encountered the concept of data structures. These are in fact the
foundation of object oriented programming (OOP).
Windows programming makes extensive use of data structures, so
it is appropriate to introduce the topic here.
.DATA
WINDOW STRUC ;Definition of structure...
field1 DB "ABCDEFGHIJ"
field2 DW 0
field3 DD 0
WINDOW ENDS
. . . . . ;Assembling instances...
win1 WINDOW <"KLJMNO",35,0>
win2 WINDOW <"PQRSTUVWX" ,55,234>
. CO'D'E .
. . . . ;Accessing the instances...
mov ax,winl.field2
, . . .
mov si,OFFSET win1
mov ax, [si.field2]
. . . .
mov ax, [si+lOl
66 Windows Assembly Language & Systems Programming
Object This listing shows how a structure is declared and used. In OOP
oriented terminology the definition is the class. The instances are
programming objects “.I A structure is just a convenient way of getting at data.
In this case we have data labels fieldl, tield2, and Iield3. By
putting the STRUC and ENDS directives around them, we have a
convenient mechanism for creating mutiple copies of those same
data declarations.
Instances The declarations between STRUC and ENDS don’t actually get
assembled: it is a template, and wherever we create instances, they
are what actually assembles. In this case there are two instances:
win1 and win2. These are identical blocks of data, able to have
their own values, but with identical variable (field) names. In
OOP we would call each field a member.
The example code shows how we can get at these two instances.
The most common method would be the first example. If I had
want to access the “f ield2” field of win2, the instruction would
simply be “mov ax, wina. f ield2”.
You can have as many instances as you wish, and as you will see
in Chapter 5, structures can be automatic or local to a procedure.
Label Equates
It is extremely useful to understand how the assembler assembles
structures. Normally the assembler equates data labels to their
offset from the start of the data segment, but fields of a structure
are equated to offsets relative to the start of the structure. In the
example, tieldl equates to 0 and field2 equates to 10. When the
instances are created, the names win1 and win2 are treated as
normal data labels and thus are equated to offsets from the
segment start.
Dot:” In assembly language the “.” (period) means exactly the same as
operator “+“, so the first code example is really:
The assembler will add the offset of win1 to 10 and assemble the
result as the operand, with non-immediate addressing encoded into
the operation code. Thus, at execution-time, the content of field2
will get loaded into AX.
Field You will see from the listing that the structure declaration
initia/isation initialises the values. These initialisations will be put into the
instances, unless overridden.
/
’ In some languages the structure-definition is called the object, and an instance is called an
instance-object.
Basic Assembly Language 67
Postamble
There are a host of other considerations for assembly language
programming for Windows, but hey, why should I throw it all at
you at once? Enough is enough.
3
Opening Windows
Preamble
7iiple You’ll find this book a nice way for beginners to learn Windows
purpose of programming, as well as a look “under the hood” for those with
fbi’s book Windows programming experience but with an urge to know
more. You can also use it to learn assembly language.
By the very nature of tackling a topic from a fundamental point of
view, the “nut and bolts” if you like, the beginner can develop very
concrete concepts on which to build. When you have a grasp of
what is going on underneath, a lot of what happens “on top” makes
more sense. Therefore, a beginner can progress to being
“advanced” in the same book, with a solid foundation of
understanding.
Content of This chapter is an introduction to the basic principles of Windows,
this cbapfer, followed by a complete assembly language program in Chapter 4
and beyond - don’t worry if the “skeleton” program looks intimidatingly
long; this is done to show the nitty-gritty of how an assembly
language program works. Chapter 5 shows you how to write an
assembly language program that is almost as short as the same
thing written in a high-level language such as C.
70 Windows Assembly Language & Systems Programming
Applicatiic
queue
_- __.-. ~.
’ Actually, Windows 3.x follows the Pascal calling convention and Windows 95 and NT follow a
mix of Pascal and C convention; that is, parameters are pushed from right to left (C) and stack
cleanup is done by the called function (Pascal).
Opening Windows 73
Other functions are available in other .DLL and .DRV files, many
of them undocumented, and I’ll take you a little bit of the way into
this uncharted, but very exciting, territory.
.H/,INC .ASM
Include file/s Assembly language
source files
Windows & C
libraries
.DEF Module
Resource Definition file
compiler
+ Resource + .XXzs Resource script
compiler
.EXE!
Note that linkers for 32-bit Windows 95
perform the function of the final
resource compiler step
The Link Step
Notice how overloaded the LINK program is! The job of a linker
is to combine the various program modules to produce the final
.EXE, but in this case there are extra complications.
. DE/ Module The .DEF Module Definition tile defines various program
Definition parameters that the linker needs to generate the .EXE tile.
file What we call static library functions can be linked into the .EXE,
and become a permanent part of it, which is the way things work
in the DOS world.
D y n a m i c l i n k However, the Windows library functions get linked in without
/ibray ( D L L ) actually adding to the size of the .EXE. That is, they stay where
they are, and are only loaded into R4M memory when the
program executes. This keeps .EXEs small. This kind of library is
called a dynamic link library.
Two Steps for Resources
Resource The .RC resource file defines parameters connected with the
compiler windows, icons, menus, dialog boxes, and segments. The resource
compiler is run twice, first to compile the .RC file(s) and second to
combine the *EXE from the linker with the compiled resources to
produce a final .EXE.
Opening Windows 75
After the first compilation, it becomes a .RES file, which has the
information in binary form.
With recent LINK programs, there is support to perform the final
step by the linker. That is, the .RES file is fed to the linker.
Objects
Borland’s latest assembler is described as object oriented, and
there are various C++ compilers around. There is also Turbo
Pascal with Objects. So, what are they?
You’ll find a chapter on object oriented assembly language later
(see page 137), but for now consider just a basic idea. Whatever
you can lump together as a whole, as a distinct entity, think of as
being an object. Your application’s window is an entity on its
own, separate from other windows - it is an object. In fact, so
too are the distinct elements of that window, such as the various
controls, the menu-bar, and the client area (where you output text
and/or graphics to).
You can consider these latter objects as being children of, or
related to, the parent window and subject to its dictates, though
there are limits as Windows is not a true object oriented
environment.
Furthermore, you access any object by getting its handle. As
you’ll see in the skeleton, even writing text to the screen requires
you to get a handle for the client area.
Handles
A handle is just an ID, a unique number, that our program can use
to access the object. Actually, you probably already have some
exposure to the concept. Various PC programming books discuss
handles in relation to file access under DOS.
76 Windows Assembly Language & Systems Programming
Instances
Mu/f/jr/e A fascinating aspect of Windows is that there can be multiple
program copies of an application running, or at least residing in memory,
instances concurrently.
After all, why not, since this is a multitasking environment? You
can, for example, have two copies of your word processor
executing simultaneously, and you can jump between them. In
such a situation, each copy would be an instance of the program.
The current instance refers to the one you are dealing with at this
moment.
There are some interesting considerations from this ability to have
multiple copies or instances. Windows is not wasteful and only
loads one copy of the code into RAM. Windows will, upon entry
to each instance, give it a unique handle, but the reality is that
there is just the one copy of the code. For this to work, each
instance needs to have its own copy of the data segment or
segments.
The downside is that your program needs to have some extra
statements to handle multiple instances. In practise this is fairly
standardized, and you can use the supplied skeleton program as the
basis for much more complicated projects, without having to
worry about multiple instances.
With 32-bit applications running in Windows 95, multiple
instances are treated as totally separate programs, so special
instance-handling code is not required.
Messages
Event driven I introduced the basic concept of event driven back on page 7 1;
intertwined with this is messages. I also said that Windows sends
messages to an application, and the latter has to decipher them and
act accordingly. Let us consider this in more detail, since it affects
the very soul of our program.
Our program has to call Windows and wait for a message - while
waiting, it is in an idle state and other tasks can be executing.
Windows does an incredible amount of housekeeping, including
receiving all of the incoming messages and parcelling them to
individual queues. Any mouse activity on your application’s
window, for example, that Windows determines will affect your
program will result in the generation of an appropriate message.
Windows is always working, seeing everything that happens.
Structure Of Below is the application’s main function, entered from Windows
WinMain() when the program starts executing. It is called WinMain - and
I’ve used C syntax - straight from the textbooks:
c syntax
The code sample should be readable, even if you don’t know C.
Note that some of the Windows textbooks give the basic program
structure in “classical” C, not ANSI C, and I have stuck with that.
You will notice “&msg” specified as a parameter, and this may
need some clarification to those unfamiliar with C. It should
become clear later on when you see it in assembly language. This Callback
function requires that an address, to which the returned message function
can be placed, be provided as a parameter. The “&‘I means
“address of’, in this case the address of a data area labelled as
“msg” (not defined in listing).
Pascal You will also notice the PASCAL qualifier in the declaration of
caMng WinMain(). This is because Windows 3.x uses Pascal calling
convention conventions, not C conventions. So the override is needed. This
is explained in more detail later (see page 112, if the fancy takes WndProc()
you), and a note was made earlier, on page 72.
STDCALL You might like to glance ahead to Chapter 13 to see a complete
calling 32-bit application written in assembly language. There, you will
convention see the procedures default to the STDCALL convention (as
specified in the .MODEL directive: see page 111). This is a
mixture of C and Pascal, in which parameters are pushed onto the
stack from right to left, and stack cleanup is performed by the
called procedure.
Data label I suppose this is as good a place as any in which to introduce the
prefixes Windows labelling conventions. You have had a first exposure to
them in the above listing. What I’m talking about are the prefixes
to the parameters. These are put there to clarify the type of data
the parameter represents. It would be breaking the flow of the
explanation to describe this in detail, but the prefixes used above long FAR
are “h” to signify type of “handle” and “lp” to signify “long HWND
unsi.91
pointer”. A more complete list of prefixes and data types is given WORD
on page 82. LONG
Message Loop
;;:.
The WinMain() function contains what we refer to as the “message
.(
loop”. ;;: . .1
Callback Functions
WndProc() I said above that, having got the message via GetMessage(), your
program must then give it back by calling DispatchMessage().
Windows then sends the message to another part of your program,
known as a callback function. In fact, each window (including
windows called diuZog boxes) has its own unique callback
functions.
The name I gave above, WndProc(), is only a suggestion. Unlike
the main function, which must always be called WinMain
(though this has become more of a convention only), your
callbacks can be called whatever you want. There is a simple
mechanism for informing Windows of the names of the callbacks,
so it can call them.
This is a C skeleton of a callback:
function, which can then process it. But the twist is that most
messages are of no interest to your program, and your callback just
sends them back to Windows again, for final default processing.
Default DefWindowProc() is a kind of rubbish bin for messages that you
message don’t know what to do with. And believe me, there are a lot of
handling them.
After sending the message to its final resting place, or handling it
in some way within the callback, execution returns to the next
statement after DefWindowProc(), which is usually a return from
the callback function (designated by “}” above, or by a RET
instruction in assembly). However, this will take execution back
to Windows again . . . .
Figure 3.3: Event-driven structure.
APPLICATION WINDOWS
WinMain function Hungarian
V Start. Windows convention
calls WinMain.
an event occurs
related to your PREFC
application b
Windows calls the C
application’s
callback function. dw
//unless exit condition
This processes
* the messages f
(WndProc will
return back here h
and hence to the 1
t
//case structure message-loop).
//for processing 1P
n
/ Default processing
. of messages P
(then return to Pt
WinMain via rgb
WndProc and
W
Windows).
Data Types
Hungarian Tabulated in Table 3.1 are prefixes to data and pointer labels. It is
convention known as the Hungarian convention and is the voluntary prefixing
of data labels with a character or characters to indicate the type of
content.
Table 3.1: Data types.
PREFIX MEANING SIZE COMMENTS
1 Boolean value 1 WORD* IO = false, non-zero = true
I I
r
HRGN Physical region handle
HSTR String resource handle
(LOCALHANDLE ILocal memory handle
IHWND I Handle of a window I
One thing that you will notice throughout much of this book is my
disregard for upper- or lowercase. For example, I have usually
used uppercase for function names. This stems from the dynamic
link libraries themselves, in which the functions are recorded
(exported) in upper case. Mixed case, in the case of Windows
functions, is for readability only. Another factor is that the
assembler treats upper- and lowercase alike - well, that can
usually be controlled by a switch.
I did have a change of heart in the matter of case sensitivity, and
you will find the 32-bit application in Chapter 13 has correct case
on everything.
The link step also can be made case sensitive or not, by the use of
switches. Note that the command line switches for the linker are
themselves case-sensitive (not all linkers, and not earlier Microsoft
and Borland linkers), which is not something that you associate
with the DOS command line.
The Bare Bones
Preamble
Skeleton The earlier theory will only really make sense when actual code is
pfogram shown, so in this chapter I have done a complete application: a
skeleton program that just puts “Hi there!” on the screen. Nothing
too ambitious, but the skeleton can be built upon for much more
ambitious projects.
Assembly for It’s quite feasible to write entire applications for Windows just in
and against assembly language, though it is more usual to restrict assembly to
critical sections of the program. Although there’s no concrete
argument against writing the whole thing in assembler, it’s a
matter of preference and personal requirements. I will show that
the argument that assembly programming is more tedious and
time-consuming than C is not true.
From the professional’s point of view, assembly gives very precise
control over what is going on, is more appropriate for low-level
and getting-behind-the-scenes development, and is potentially
extremely compact and fast.
From the beginner’s point of view, looking at how to write the
entire program at the assembly level is most useful for learning
purposes and gives us useful insights into how Windows works.
The argument in assembly language’s favour is developed further
in the last chapter (see page 367).
85
86 Windows Assembly Language & Systems Programming
Orgaflisatiofl I have organised this section by example with a simple “Hi there!”
of this introductory program, as shown on page 94. We go through it
chapter here step by step and put together the complete application. This
program is on the Companion Disk, in directory SKELETNI.
Note that I have written the program at the most fundamental level
for instructional purposes. However, the next chapter introduces
the same skeleton program, but makes use of advanced assembler
features, so it is more practical. The program of this chapter has
the advantage that it represents the lowest common denominator
and should work with just about any assembler.
I recommend that you use this chapter as a theoretical learning
tool and focus hands-on experimentation in the next chapter.
32-bit Chapter 13 describes a 32-bit skeleton program; however, I
skeleton recommend that you follow the steps of the “ladder of learning”.
application The 16-bit applications of this and the following chapter will work
fine under Windows 95. By all means refer to Chapter 13 as you
study this chapter and the next, as you wish, to see the contrast -
you will find the 32-bit code is structurally the same, and very few
changes are required to convert a 16-bit application.
lnstal.latio4
Getting Started of the
devefopmn
fools
Tools Required
Microsot? So what do you need? Many people will have access to the
SDK Microsoft Software Development Kit (SDK) and Microsoft
assembler (MASM), so this is a good starting point. In my
previous book I showed how the SDK and MASM ~5.1 could be
used to write a complete assembly language program, but I now
consider ~5.1 to be behind the times. However, I constrained the
program in this chapter to work with ~5.1, in which case the
earliest tools that I can guarantee the program to successfully
assemble and link with are in Table 4.1.
Note that SLIBCEW and CWINS are C run-time libraries, and are
not required for the skeleton. However, in a situation where you
would need them to call C run-time functions, investigate using
startup code supplied by the vendors, for correct initialisation (the
next chapter shows how to link the Borland startup tile,
COWS.OBJ; Microsft’s MASM ~6.1 supplies APPENTRY.OBJ).
Whenever you see the letter “S” in a library filename, it usually
means “Small model”, while the letter “W” designates “Windows”.
The Bare Bones 87
Borland & The second column of Table 4.1 contains the earliest Borland
other tools versions that will work. Other LINK versions should be ok, as
long as they are Windows-compatible. MASM prior to 5.10
should also be ok.
/nsta//atiofl
The normal situation is to have the SDK installed with everything
of the in the appropriate directories. The manuals with the SDK, C
&ve/opmenf ~6.00, and MASM explain how the environment variables need to
too/s be set so that MASM and LINK can find the appropriate files. Or,
you could have one of the other development systems installed,
such as Borland C++, that do not need the SDK as a separate
entity. Note also the Microsoft C/C++ ~7.0 and later is bundled
with elements of the SDK.
Actually, the main reason that you require the SDK is for the
programs RC.EXE, the import file LIBW.LIB, and
Windows-compatible LINK. The SDK does have some other
tools, such as a debug version of Windows, but most of these tools
are available with recent compilers. There are also a lot of useful
manuals with the SDK. Microsoft has gone away from supplying
printed manuals, and wherever I refer to a Microsoft manual in
this book, it will be on-line; although, in most cases it should also
be available for purchase separately. I personally prefer printed
manuals.
If such housekeeping (i.e., the correct installation of all the
software tools) is too much trouble, get together all the above files,
or suitable equivalents, and put them all into the same directory.
Problem solved.
Look ahead through this book and you’ll see examples of Make
files for both Microsoft and Borland.
88 Windows Assembly Language & Systems Programming
Segacy
cbapteF I must emphasize again that this chapter is a “legacy chapter”.
I am using the oldest tools and the most primitive assembly
language skeleton. This is not what I recommend for actual
development, but the very basic skeleton is excellent for
learning. I have included all of the meandering through
version numbers below, partly to record what I remember,
before I completely forget! Should you wish to learn this
skeleton and you only have old development tools, or you
need to modify or maintain legacy code, you will find this
information useful.
h4icroso~ MASM prior to version 6.00 can’t handle the high-level language
and used in subsequent chapters, so I recommend upgrading if you
Borland don’t have it. The alternative is the long-winded program given in
version this chapter. In fact, at the time of writing, the latest version is
notes 6.11, and I recommend that you use it in preference to all earlier
versions, including version 6.10. Microsoft made some important
changes in the upgrade from 6.10 to 6.11!
Other older assemblers may be able to handle the code in this
chapter. .RCfi.e
TASM Borland TASM prior to ~2.5 should be ok for this chapter, but
vz. 5 ~2.5 has enhanced features and is the basis, along with TASM
~3.00, of the program in the next chapter. At the time of writing,
the latest is version 5.0 (see Chapter 13). .DEFfie
Microsoff Microsoft Quick assembler should be ok for this chapter. I think
Quick that Quick assembler version 2.01 can be considered equivalent to
assembler MASM version 5.2.
All of this upgrading is difficult to keep up with, but the above
notes should prove helpful.
Of course, as mentioned above, with some language products, such
as those from Borland, you don’t need to have the SDK installed,
though I certainly recommend the SDK documentation.
Note that even if you are only interested in writing in-line # SKELETC
assembly within your high-level code, consider this chapter to #define
#define
have important buiding-block educational information. Many skeleton
modern compilers allow in-line assembly, and this is developed BEGIb
further in Chapter 6. PO1
E
I have gone through the above outline of products and versions Ml
and based this chapter on early tools, as not everyone has access to
the latest tools. Also, it is actually quite educational to analyse a
Windows assembly language program written with an earlier
assembler minus the high-level features. Having understood
The Bare Bones 89
Source Files
The next step is to w-rite the application, for which, of course, you
use a text editor. However, it is no longer a case of producing a
single .ASM source file - let’s call it SKELETON.ASM. The
absolute minimum files required are:
l SKELETON.ASM (program source)
l SKELETON.RC (resource script)
l SKELETON.MAK (Make file)
l SKELETON.DEF (definition file)
# SKELETON.RC
#define IDM QUIT 200
#define IDM-MESSAGE 201
skeleton MEm
BEGIN
POPUP "File"
BEGIN
MENUITEM IrQuitl', IDM_QUIT
MENUITEM l'Message...'l,IDM_ MESSAGE
END
lmrl
90 Windows Assembly Language & Systems Programming
Menu-bar You will be able to figure out what this .RC file does by observing DOS stub
the execution of the program. A menu-bar with only one
selection, “File”, drops down two menu-items: “Quit” and
“Message...“. The next chapter has the same “Hi there” program,
but written using high-level assembly constructs.
IDM_QUIT and IDM_MESSAGE are arbitrary labels, assigned
(almost) arbitrary values. One of these values is passed within a
message as an identifier to Windows, if a menu-item is selected.
Message Format
WM- Selecting a menu-item generates a WM_COMMAND message,
COMMAND which is one of many possible messages that can be sent to the
message callback. It is a 16-bit value, and ilso has other parameters,
n o t a b l y “wparam” and “lparam”, that constitute extra data
attached to the message.
message, So, this is what constitutes a message:
wParam,
/Param l message (16-bit number) (32-bit WinApp: 32 bits)
l wParam (16-bit number) ( ” )
1Param (32-bit number) II
l ( )
wParam is 16 bits also, hence the “w” (word) prefix. Every
message has two parameters attached to it, wParam and lParam,
the latter being 32 bits (hence the “1” prefix, meaning “long”).
What these parameters contain depends upon the message. The
# SKBl
prefixes are just a convenient notation for labels, so that we know fn = sl
what they represent (see page 82). Note that for 32-bit all:$(:
applications, these parameters are all 32 bits (making the “w” and $ (fn) .I
“1” rather confusing, as these prefixes are still used). masrr
$ (fn) .’
Before we delve further in this direction, here is the .DEF file: rc -
$ (fn) .
SXELBTON. DBF. . . link
NAME SKELETON rc S
DESCRIPTION 'Hi t h e r e ! p r o g r a m ’
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS SKELETONPROC
DOS stub I have explained various aspects of the .DEF tile throughout this
book, so investigate via the index. Some of the lines are
self-explanatory. “WINSTUB.EXE” is a program supplied by the
software vendor, that is incorporated into the overall .EXE tile,
and is executed if you try to run the program from the DOS
command line. It just displays a short message and quits.
I have put the DOS stub to very interesting use in Chapter 14.
Make File
Before we go ahead with the application itself, let’s consider the
Make file. This determines the assemble, compile, and link steps.
With reference to Figure 3.2 on page 74, the first step is to
assemble SKELETON.ASM to produce SKELETON.OBJ (any
Include files are also assembled). MASM and TASM have
various directives to aid with creating Windows applications;
however, by writing the program at the most fundamental level I
have avoided these, which means that just about any assembler
should work. You can see in the listing below how RC.EXE is
used to compile SKELETON.RC and how to incorporate
SKELETONRES into SKELETON.EXE. LINK converts the
.OBJ to .EXE, and LIBW.LIB provides connection to the
Windows functions. LIBW.LIB is not itself a library. Note also
that LINK refers to the .DEF tile.
# SKELBTON.MAK...
fn = s k e l e t o n
all:$(fn) .exe
3.00 (and later) is supposed to be more compatible with NMAKE Other ways
(this is doubtful - see my comments in Chapter 14).
Why use a The Make file saves you the trouble of typing in all the assemble,
Make fife? compile, and link steps at the command line. Some integrated
environments generate the Make file automatically, so you don’t
even have to do that much, but there are some sound reasons for
learning about and using Make files, not the least of which is
flexibility. Some integrated environments generate what is called
a project file, which is saved with a special extension, and with
some products it is possible to convert a project tile into a Make
file. The fundamental difference in usage is that in the integrated
environment you do everything via pull-down menus, while you
run the Make file from the command line.
Programmer’s Microsoft’s Programmer’s Workbench (PWB) is an example of an Programmel
Workbench integrated environment that works with Make files in its native Workbench
lPWs) mode, though the Make files are highly stylised. PWE3 can,
however, read ordinary Make tiles, and you can open a “project”
by opening many of the Make files given in this book.
El-p fanation You can figure out what the above Make fiIe does: it assembles
of above SKELETON.ASM using MASM.EXE, t h e n i t c o m p i l e s
Make file SKELETON.RC using RC.EXE, then LINK.EXE links everything
together, and finally RC.EXE is executed again to combine
SKELETON.EXE and SKELETONRES (the compiled output
from the first RC execution) to produce the final When can I
SKELETON.EXE. 7jet
started”?
Development Cycle
Wifhin You can run the Make file from the DOS prompt, but you can aIso
Windows do it from within Windows. What you should do is open the File
Manager and go to the directory containing the application. Then
iconize the File Manager and open the Notepad. Use the Notepad
to view and edit SKELETON.ASM, and iconize when finished. It
is a simple matter to flip between the Notepad and the File
Manager.
When in the File Manager, and the directory containing the
application is open (and the directory must contain all software
tools if the SDK is not installed properly on the PC), select
“Run...” from the “File” menu.
In the box, type “NMAKE SKELETON.MAK”, just as you would
on the DOS command line. After running the Make file, all you
need to do to test your program is double-click on
SKELETON.EXE in the File Manager.
The Bare Bones 93
Other ways The above is not the only way to do it. There are various reasons
why you may want to do everything from DOS and load Windows
to test the program, or, have a “DOS-box” open and use <ctrl-esc>
to flip between it and Windows. Or, you may be working within
an integrated environment, which may have something called a
projectfile rather than a Make file. Many integrated environments
can generate a Make tile from a project file, and can also execute a
Make file from within the environment. I have never been entirely
satisfied with integrated environments and prefer to be outside
one, using the traditional Make file from the command line: but I
don’t want to prejudice you. If your product has an integrated
environment, give it a go. One problem you may have is getting it
to handle stand-alone assembly programs.
Programmer3 However, I have rather grudgingly come to like Microsoft’s
Workbench Programmer’s Workbench (PWB). If you install PWB, you can
open almost any of the Make files supplied on the Companion
Disk, and thus you will have opened a project. You will however,
have to click the “non-PWB Makefile” button. Then you can
select “Rebuild All” from the “Run” menu, and see the result in a
“Build” window.
PWB can be started from within Windows, and after running the
Make tile, you can use <aIt-tab> to flip over to Windows and try
the program.
When can 1 This is, of course, just theory if you are reading through the book
“set linearly - don’t worry though, as the hands-on exercises begin
started”? soon. If you feel the overwhelming desire to try the program, why
not? (flick ahead to the next chapter if you want to assemble the
simplified skeleton). Copy the appropriate tiles off the
Companion Disk. Then, assuming that you have all the
development tools installed, follow the above instructions to
assemble, link and test your program. Later on you can learn how
it works internally.
Alternatively, you may feel that you don’t want to get “bogged
down” in a skeleton that is very primitive and would prefer to
jump directly into a skeleton that uses the higher level assembler
features. In that case, study this chapter theoretically only, and do
your hands-on work in Chapter 5. Or, if you really insist on
short-circuiting my “ladder of learning”, you can get hands-on
experience with the 32-bit application in Chapter 13.
94 Windows Assembly Language & Systems Programming
Application Structure
It doesn’t do much more than put “Hi there” on the screen, but High-level
wow, so much red tape.1 A far cry from the few lines a DOS CALL
program would need.
Try to understand as much as possible and identify the major
structural elements.
Preliminary Code
EXTRN IN
EXTRN IN
EXTRN WA
;SKELETON.ASM Windows assembly language program EXTRN DO
.286 ;286 i n s t r u c t i o n s e t . UP
; . . . . . . . . EXTRN
EXTRN BE
EXTRN EN
EXTRN DE
WINDOWS. - The identifiers (equates) shown below would normally be in the EXTRN PO
/NC WINDOWS.INC Include file (refer page 109). With this skeleton EXTRN RE
I have minimized the number of files involved. EXTRN GE
EXTRN CR
EXTRN SH
ID1 APPLICATION EQU 32512 ;default icon type. EXTRN GE
IDC-ARROW EQU 32512 ;default cursor type. EXTRN LO
OEM-FIXED FONT EQU 10 I*font type. EXTRN TR
COLGR BACFGROUND EQU 1 ;background color EXTRN DI
WM CREATE EQU 1 ;Windows message EXTRN LO
WM-DESTROY EQU 2 EXTRN TE
WM-PAINT EQU 15 j ;
EXTRN ME
WM-COMMAND EQU 273 ; / EXTRN SE
WM-LBUTTONDOWN EQU 513 ;
WM-CHAR EQU 258 ; $
IDM QUIT EQU 100 ;menu-identifiers from
IDM-ABOUT EQU 101 ; .RC file.
MB_BK EQU 0 I*messagebox identifier.
.DATA
Program h%fhig The Windows startup code would normally be in a ;This mus
conllnuesuttf/fpage separate .OBJ module supplied by the compiler vendor; DWORD
rsrvptrs
r07 however, in this fundamental skeleton, I have put the WORD
startup code into this module. This code is taken from hPrev
APPENTRY.ASM, which is the source file for hInst
Generic program for APPENTRY.OBJ, supplied by Microsoft. These are a lpszcmd
any assembler couple of equates used by the startup code: cmdShow
; . . . . . . . .
. DATA
;This is the equates for the startup code... szwintitl
STACKSLOP EQU 256 ; amount of stack slop space required szskeletc
maxRsrvPtrs EQU 5 ; number of Windows reserved pointers hOemFont
sout
s zabout
sztitle
The Bare Bones 95
EXTRN 1NITAPP:FAR
EXTRN 1NITTASK:FAR
EXTRN WAITEVENT:FAR
EXTRN DOS3CALL:FAR
EXTRN UPDATEWINDOW:FAR
EXTRN BEGINPAINT:FAR
EXTRN ENDPAINT:FAR
EXTRN DEFWINDOWPROC:FAR
EXTRN POSTQUITMESSAGE:FAR
EXTRN REGISTERCLASS:FAR
EXTRN GETSTOCKOBJECT:FAR
EXTRN CREATEWINDOW:FAR
EXTRN SHOWWINDOW:FAR
EXTRN GETMESSAGE:FAR
EXTRN LOADCURSOR:FAR
EXTRN TRANSLATEMESSAGE:FAR
EXTRN DISPATCHMESSAGE:FAR
EXTRN LOADICON:FAR
EXTRN TEXTOUT:FAR
EXTRN MESSAGEBOX:FAR
EXTRN SELECTOBJECT:FAR
.DATA
I-This must be at beginning of data segment...
DWORD 0 ; Windows reserved data space.
rsrvptrs WORD maxRsrvPtrs ; 16 bytes at top of DATA seg.
WORD maxRsrvPtrs DUP (0) - Do not alter
hPrev WORD 0 - space to‘save WinMain parameters
hInst WORD 0
1pszCmd DWORD 0 ’ ;$
cmdShow WORD 0 ;/
;........................................................
.DATA
szwintitle DB 'SKELETON PROGRAM',0
szskeletonname DB 'SKELETON',0
hOemFont DW 0 ;handle to OEM font.
sout DB I Hi there! I
szabout DB 'Assembly Language Skeleton',0 ;messagebox
sztitle DB 'Barry Kauler',O ; /
96 Windows Assembly Language & Systems Programming
Startup Code
The startup code is fascinating, because it is something you
normally don’t see in a Windows program. It is the code that is
first entered when the application is loaded, and it performs
various initialisations before calling the entry point of your
program, WINMAINO.
DOS3CALL() Tn’is code is also the exit point, performing the standard
INT-2lh/function 4Ch to exit back to the calling program. Look
below, but don’t be mislead by the DOS3CALL(): this simply does
the same as INT-2lh, except by a FAR CALL rather than by Registef
software interrupt. As far as I’m aware, there is no other initialisati~
difference, except that the CALL is faster.
.CODE
;Here is the startup code...
start:
xor bp,bp ; zero bp
push bp
c a l l INITTASK ; Initialise the stack
or ax, ax
jz noinit
add cx,STACKSLOP ; Add in stack slop space.
jo noinit ; If overflow, return error.
mov hPrev,si
mov hInst,di
mov word ptr lpszCmd,bx
mov word ptr lpszCmd+2,es
mov cmdShow,dx
x o r ax,ax - 0-->ax
p u s h ax ;parameter for WAITEVENT
c a l l WAITEVENT ;Clear initial event that started this
; task.
p u s h hInst ;parameter for INITAPP
c a l l INITAPP ; Initialise the queue.
or ax,ax
jz noinit
p u s h hInst ;params for WINMAIN
p u s h hPrev / hW7TASKfl
push WORD PTR lpszCmd+2 ; 7 (seg. first)
p u s h WORD PTR 1pszCmd ; / (of/fset second)
p u s h cmdShow ;
c a l l WINMAIN
ix:
mov ah,4Ch
call DOS3CALL ; Exit with return code from app.
noinit:
mov al,OFFh ; Exit with error code.
imp short ix
The Bare Bones 97
DX nCmdShow parameter
cmp WC
ES segment address of PSP
j n e cl
INIITASK() also fills the first 16 bytes reserved in the data
segment with information about the stack. Prolog
WAITEVENT(), The parameter zero when supplied to WAITEVENT clears the code
lNITAPP() event that started the current task.
INITAPP() initialises the queue and support routines for the
application.
WINMAINQ
Below is the rest of the code segment, which has WINMAIN and
the callback function SKELETONPROC(). Functions that are to
be called by Windows must be declared as PUBLIC.
.CODE
PUBLIC WINMAIN
WINMAIN PROC NEAR ;entry point from Windows. First-
instance
Parameters passed on the stack will be as per the listing on page 77 handling
and will have been pushed on from left to right, with the return
address pushed on last (Figure 4.1). You can check this against
the startup code above.
ALL
push b p ;save BP so can use to access params. HOI;
m o v bp,sp ;BP will now point to top-of-stack. QW
c
sub sp,46 ;mov stack to free region.
mov W
Figure 4.1: Stack at entry to WinMain@ mov W
mov W
Stack Pointer SP
sub a
mov W
Base Pointer BP mov W
register points here mov a
c old BP mov 1
return address sub a
push a
This is what BP+4 d_ nCmdShow mov c
the stack looks BP+6 4 1pCmdLine sub c
like at this push i
point in the ~p+10 +, hPrevInstance % :
program BP+12 +, hbstance mov !
J (see next page) sub i
push i
mov i
cmp WORD PTR [bp+lO] ,0 ;hPrevInstance.
; (~0 if no previous i n s t a n c e ) .
jne createwin
Prolog One important thing to notice from Figure 4.1 is that after the
code prolog code, BP points to the parameters (so that the program has
ready access to them), while SP has been moved away (so that the
stack can grow downward in memory without interfering with the
parameters or the intermediate area that is to be used for
temporary data).
In Figure 4.1, increasing addresses are downward. Note that the
return address is not FAR, but NEAR, as WI
the startup code within the same segment, not directly fi-om
Windows.
Note that the old value of BP is saved on the stack. Note that
“IpCmdLine” is a 32-bit value and so occupies four memory
locations (for explanation of label prefixes, refer to page 82).
First- The first instance of the program has to create a window-class data
instance structure and call RegisterClass(). It determined this by testing
handling “hPrevInstance”, which is zero if this is the first instance. Note
that the handle for this particular instance is “hInstance”.
sub ax,ax
push ax I-O=no parent for this window.
push +O=use the class menu.
mov :;,WORD PTR [bp+l2; ;hInstance
ax ;
zh ax,ax
push ax .O=no params to pass-on.
1(32-bit long-pointer).
%F &iTEWINDOW
mov WORD PTR [bp-21,ax ;returns hWnd in AX
;(handle to the window).
;Here we save it temporarily.
push .ShowWindow() requires hWnd
push ~:RD PTR [bp+41 ’;and nCmdShow on the stack.
call SHOWWINDOW *Tells Windows to display window.
WORD PTR [bp-21 ;hWnd
::z: UPDATEWINDOW ;tells Windows to redraw now.
jmp SHORT messageloop ;go to the main message loop.
Message Refer back to page 77 for an explanation of the message loop. The
loop event-driven nature of a Windows application means that
GETMESSAGE goes to Windows and waits for a message from
the queue. After return, key presses are preprocessed by
TRANSLATEMESSAGEO, then control is passed to the callback
function via DISPATCHMESSAGE and Windows.
mainloop:
lea ax,WORD PTR [bp-201 ;far-addr of message
push ss
push ax
call TRANSLATEMESSAGE
; . . . . . . .
lea ax,WORD PTR [bp-201 *far-addr of message.
I
push ss
push ax
call DISPATCHMESSAGE
; . . . . . . . .
messageloop:
lea ax,WORD PTR [bp-201 *long-pointer (far addr) of
I
1
Prolog T
Ci
The above section of
SP code
a
code, starting at n
“messageloop:” and e
looping-back to BP-20
“mainloop:” is the jlltermtlre SM
message loop.
BP-18 prolog code
BP-16
Notice that the value
[BP-201 was pushed push bp
onto the stack. mov bp,E
BP-12 * Time
This an offset (address) BP-10 * PT
in the stack segment
where GETMESSAGE
will place the message
upon return.
The stack Segment iS a BP
convenient temporary --old BP
BP+2 - ret. addr, etc...
storage place.
Callback Function
Thus ends WINMAIN(). For the callback function, refer to the
listing on page 79. The parameters are passed on to the stack in
the order of left to right, with a FAR return address on top.
If this program looks similar to the example in my last book, it’s
not surprising, since both were originally created from a C
skeleton with the compiler set to generate assembly output (see
page 151). This listing is, however, substantially different from
before.
PUBLIC SKELETONPROC
SKELETONPROC PROC FAR
;The function is entered with far-return-addr (4 bytes),
;lParam (41, wParam (21, message-type (2), and
;window-handle (2 bytes) on the stack (ret-addr on top).
;.....
push ds I-This is some Standard preliminary
POP ax I*shuffling of the registers.
nop I /
inc bp ; $ (it is called the prolog code)
push bp ;
The Bare Bones 103
mov bp,sp ;
push ds ;
mov ds, ax 1
sub sp,146 *move ;he stack to a free region
; (so as no; to mess-up the params).
Prolog The above proZog code may seem strange. It is at the start of all
code callbacks. However, the above code can be simplified if the
application is never to run in Real mode. A Windows application
running in Real mode is only possible with Windows ~3.0 and
earlier and is an unlikely requirement these days.
Alternatke simphffied If the application will always be run in Protected mode,
pro/OS code the prolog can be simplified as follows:
push bp ; prolog
mov bp,sp ; / (set up stack frame)
push ds * / (save c a l l i n g f u n c t i o n ’s d s )
push ss I / ( move ss to ds -- local data segment)
POP ds ;/ ( l” )
sub sp,146 ; / (reserve local data area)
Stack Pointer SP
Base Pointer BP
1
register points here c. D S
old BP+1
return address
This is what , (FAR)
the stack looks B P + 6 - 1Param
like at this
point in the BP+10 *. wParam
program BP+12 -m message
d(see next page) BP+143 hWnd
IO4 Windows Assembly Language & Systems Programming
push i
mov ax,WORD PTR [bp+12] ;get message-number. call E
cmp ax,WM_CREATE ;message received after jmp E
je create ;CreateWindow() is called.
cmp ax,WM_DESTROY ;message received if a window is WM_ CHAR
; closed. message
je quitmessage
cmp ax,WM_PAINT ;message received if Windows has
-(already) redrawn any part of the window.
je paint ’
cmp ax,WM COMMAND ; any selection of the menu will char:
jne notcommand ;the WM_COMMAND ;I haven
jmp menu ;message. ;in this
notcommand: jmp !
cmp ax,WM_LBUTTONDOWN ;one of many m o u s e
jne notlbutton ,*messages.
jmp break WM_PAM
notlbutton: message
cmp ax, WM_CHAR ;message t h a t a k e y p r e s s e d .
je char
; . . . . . . .
;Default handling of messages....
push WORD PTR [bp+141 ;hWnd
push WORD PTR [bp+121 ;Message-type
Dush WORD PTR [bp+lOl : wParam
push WORD PTR Lb@+81 )hi-half of 1Param paint:
push WORD PTR [bp+61 ;low-half of 1Param push 1
call DEFWINDOWPROC lea i
jmp return ;Back to Windows.
“Case” The above code determines the type of message and jumps to an
statemeflt appropriate routine. If the message is not to be handled explicitly
by the callback, it falls through to DEFWINDOWPROC() for push i
default handling.
% I
W&f_ CREATE Follow through the case of WM_CREATE. The earlier case logic ;
push
message will bring execution to “create : I,‘ where I have obtained the mov i
push ax
call POSTQUITMESSAGE
jmp SHORT break
char:
; I haven't bothered to respond to key-presses in any way
,.in this simple skeleton . . .
jmp SHORT break
WM_PA/NT Even the most basic skeleton will need the following code in
message response to WM_PAINT. You will need to put in BeginPaint()
and EndPaint(), even if you don’t output anything. WM_PAINT is
sent if anything has happened to the window that-will require its
dent urea to be redrawn. I need a handle @DC) to the client area
before I can output to it.
paint:
push WORD PTR [bp+14] ;hWnd is handle of window.
lea ax,WORD PTR [bp-421 ;ps -- far-addr of
; paint-structure.
push ss ; (BeginPaint will fill,, the structure).
push
call EGINPAI~ *BeginPaint 'returns handle hDC.
mov WORD PTR [bp-;46],ax ;hDC -- display-context,
; required before can output to screen.
push ;hDC
push ZEemFont
call SELECTOBJECT ;attaches hOemFont to hDC.
push WORD PTR [bp-1461 ;hDC
mov ax,8 ;16-bit x-coord
push ax , /
mov ax,15 I*16-bit y-coord
push ax /
mov ax,OFFSET sout ; far-address of string to o/p
push ds ;
push ax ; $ (note lo-half pushed 2nd)
mov ax,9 I-number of chars in string.
push ax
call TEXTOUT
push WORD PTR [bp+14] ;hWnd
lea ax,WORD PTR [bp-421 ;far-addr of paint-structure
push ss ; (was filled y BeginPaint(
push ax ;
call ENDPAINT
jmp SHORT break
;........................
menu:
106 Windows Assembly Language & Systems Programming
@i/g Finally we have the epilog code, which compliments the prolog
code code on page 102. At this stage, BP is pointing to the saved “old I
BP+l” which we decrement twice so it points to the saved DS,
which we make the top of stack and then pop to restore the
original DS, followed by the “old BP+l”, which we decrement to
restore to its original value.
The Bare Bones 107
break:
sub ax,ax ;returns 0 in DX:AX. (callback functions
cwd I*return a 32-bit (long) value).
return:
dec bp I*final Standard manipulation of regs.
dec bp /
mov sp,bp 1 / (tt is called the epilog code).
POP ds ;
POP bp ;
dec bp i ;
ret 10 *removes parameters.
SKELETONPROC ENDP ’
;.......................................................
END start ; execution entry point of program.
SimpMed I showed earlier that there is a simplified alternative for the prolog
ep&g cafe code. The matching epilog is similarly simple:
POP ds Ieepilog
POP bp ; /
retf 10
So, here again refer to Figure 3.1 to see what it looks like.
Clicking on the “File” menu-item pops down two selections:
“Quit” and “Message...“. Selecting the latter results in a message
box looking very much like that shown on page 172.
Preamble
What’s in What I have for vou in this chanter is the same nromam from the
this cbapfer previous chapter (page 94), but-wow is it smaller! bne thing you
will have noticed from that first program is that it does an
incredible amount of stack manipulation: this makes the program
both long and very tedious to write.
The Borland and Microsoft (plus other vendors’) assemblers have
some high-level features that ease the coding burden considerably,
even to the point of the program being as short as the equivalent
written in C or some other high-level language. That’s saying
something!
What follows is a breakdown of each section of the previous
program, showing how it can be improved . . .
Equates Refer back to page 94. You will see a whole pile of equates, for
example, "WM_PAINT EQU 15". WM_PAINT is simply a
meaningful label, a constant, that equates to value 15. This means
that wherever the assembler finds the label WM_PAINT, it will be
,
109
1 IO Windows Assembly Language & Systems Programming
Skeleton Analysis
INCLUDEWINDOWS.INC
IDM QUIT EQU 200 ; m e n u - i d e n t i f i e r s : must be
IDMIABOUT EQU 201 ;same as defined .RC file.
.DATA LOCAL ’
szwintitle DB 'HULL0 DEMO PROGRAM',0 directive I
szwinasmlname D B 'WINASMl',O
hOemFont DW 0 ;handle to OEM font.
sout DB 'Hull0 World'
szabout DB 'Assembly Language Windows Demo',0
sztitle DB 'Karda Prints',0 ; /
;.................._._,.....,..,.........................
.CODE
PUBLIC WINMAIN
WINMAINPROC PASCAL NEAR hInstance:WORD,\
hPrevInstance:WORD,lpCmdLine:DWORD,nCmdShow:WORD
High-Level Assembly I 13
High-/eve/ Now for the first major enhancement. If you refer back to page 98
PROC you will see this same section of code and a picture of the stack.
The parameters passed on the stack have to be accessed by direct
addressing of the stack segment. "cmp WORD PTR [bp+lO] , 0”
for example, to get at "hPrevInstance". However, by declaring
all passed parameters as above, they can be accessed within the
procedure by name. The example would become "cmp
hPrevInstance,O" - simple hey! The assembler equates
hPrevInstance to [bp+lO], so it does the dirty work.
PASCAL There’s another important aspect to the above high-level PROC -
epilog/ the PASCAL qualifier. This eliminates the need to explicitly code
prolog the prolog and epilog code. Again, look back at page 98.
The standard prolog code, which is not part of the program listing,
is:
LOCAL The original prolog code contained "sub sp, 46" to move the
directive stack further down in the stack segment, allowing a free area in
which to store local data. Once again, we can eliminate the need
to explicitly code this. Declare all local data using the LOCAL
directive, with a syntax as shown above. Incidentally the default
type is WORD, so if the data is of type WORD you don’t have to
declare it.
Note that you cannot initialise this data, since it is only created at
execution entry to the procedure, not at assemble time.
For an introduction to local data, refer back to page 62.
I1 4 Windows Assembly Language & Systems Programming
f?f?@tt@r.@ The above block of code is setting up the data structure prior to
a window calling REGISTERCLASSO. Compare that with the previous
program, page 99. You will see there that we had to explicitly
access the stack segment between [bp] and [bp-461, in which the
instance of the structure was kept. (Locations greater than [bp]
contain the return address and passed parameters, while addresses
below [bp-461 is the new working area for the stack.)
WORD PTR override is introduced on page 63.
High-level Now we have another high-level feature, the high-level CALL.
CALL REGISTERCLASS only requires one parameter, the FAR
address of the sl data structure.
Refer back to how it was done before: after everything was loaded
into the structure in the stack segment, ss:[bp-461 was passed as
the FAR address required by REGISTERCLASSO. See page 99
onwards.
Below, we do the same thing but use the name of the structure
instead:
lea ax,sl
call REGISTERCLASS PASCAL,ss,ax
or ax,ax
jne createwin
jmp quitwinmain
116 Windows Assembly Language & Systems Programming
The time has come to create the window on-screen. The call 'I
lea a
high-level CALL has various qualifiers and can take multiple call I:
parameters. message1
Note that if the parameters have no defined size, they default to lea a
call G
WORD. or a.
Notice the qualifier PASCAL: jne n
;GetMess
;so here
createwin: mov a
call CREATEWINDOW PASCAL,ds,OFFSET szwinasmlname,\ quitwinm
ds,OFFSET szwintitle, 207,O 0 400 \ ret
300, 0: ~?"liInktanc&, 0,O WINMAINE
mov hWnd,ax
call SHOWWINDOW PASCAL,ax,nCmdShow
call UPDATEWINDOW PASCAL,hWnd
jmp SHORT messageloop ;go to main message loop.
You may have noticed that I have not used the FAR PTR override
for the call instructions: the assembler is smart enough to know
from the “EXTRNfunctionname : FAR” declarations that the call
should be FAR. The override could be put in, but for the
programmer’s information only.
PASCAL, So, what about the PASCAL qualifier? The choices here are
C, B A S I C , nothing, PASCAL, C, BASIC, FORTRAN STDCALL, or
FORTirzAN, PROLOG. The qualifiers available vary with different assemblers.
STDCALL, Normally, a CALL instruction just pushes the return address on to
PROLOG the stack, and the RET at the end of the called procedure pops it
qualifiers off.
The PASCAL qualifer will cause the parameters to push on in the
correct order and will also remove them, assembling a “RET
number ” at the end of the procedure, as discussed above and on
page 107. We require the PASCAL qualifier to call Windows
functions.
We would use the C qualifier to call C functions, perhaps some
third-party C library we want to use. The effect is the same, but
the parameters are pushed on in the reverse order and not removed
by the called routine: they are removed from the stack after
execution returns from the procedure.
Whatever language we are calling, the result is that the high-level
CALL instruction assembles with all of the pushes, pops, and other
stack manipulations generated automatically - unassemble such
code and you will see something like the program of the previous
chapter.
Stack Pointer SP
An instance s3 of
PAINTSTRUCT
is here. s3 actually PSrcPaint.rcT
are defined in
WINDOWS.INC
Note: hDC equates
Base Pointer BP
register points here
I’his is what
the stack looks
like at entry
to the callback
function (after
I1 8 Windows Assembly Language & Systems Programming
mov hc
;................................*....................... jmp d
PUBLIC WINASMlPROC ; . . . .
WINASMlPROC PROC WINDOWS PASCAL FAR \ xquitmesE
hWnd:WORD,msgtype:WORD,wParam:WORD,lParam:DWORD call P(
LOCAL dummy:WORD:5 jmp d
LOCAL hDC:WORD ;.....
LOCAL s3:PAINTSTRUCT xchar:
jmp d
; . . . . . . . *
WINDOWS Notice two things here: the WINDOWS qualifier, and the xpaint:
Local declarations can take a lea a:
qualifier "dummy" local variable. call B:
repeat-count, which in this case declares five words, the first mov hl
pointed to by label "dummy". call 5:
The WINDOWS qualifier takes care of generation of the special call T:
lea a:
prolog and epilog required for a callback function. Refer back to call E
page 103 for the prolog code, and page 107 for the epilog code. jmp S1
MASM v6 achieves the same thing with a different syntax, while ; . . . . . . .
32-bit programming uses the STDCALL language qualifier and xmenu:
doesn’t need further qualification. cmp WI
jne x'
Figure 5.1 shows the stack upon entry to the callback function. cmp w.
jne nl
Now for the case-logic that processes the messages . . . . jmp XI
notquit:
mov ax, msgtype ;get message-type. cmp w:
=mp ax,WM CREATE ;msg received after jne xl
je xcrea& ;CreateWindow() func is called. call M
cmp ax,WM DESTROY ;message if a window is closed.
je xquitrii&ssage ;.......,
cmp ax,WM_PAINT ;msg if Windows has (already)
xbreak:
-redrawn any part of the window
I- (due' to size-change for example). sub a:
je xpaint cwd
cmp ax,WM COMMAND;any selection of the menu will xreturn:
jne notwmEommand ; produce this message. ret
jmp xmenu WinasmlPl
notwmcommand: END
cmp ax,WM LBUTTONDOWN ;one of many mouse
jne notwm1button ; messages.
jmp xbreak
notwmlbutton:
=mp ax,WM_CHAR ;message that a key pressed.
je xchar
;Default handling of messages....
call DEFWINDOWPROC PASCAL,hWnd,msgtype,wParam, 1Param
jmp xreturn ;Back to Windows, which will in turn
; return to after DispatchMessage .
.MODEL Directive
I already introduced the .MODEL directive on page 111 and made
reference to the TINY, SMALL, MEDIUM, COMPACT, and
HUGE memory qualifiers. The .MODEL directive can also take
other qualifiers, for example:
This sets the defaults for the program, and “PASCAL" means that
all procedures are to be of Pascal-type, which also means that the
PASCAL qualifier is not needed in the PROC declarations.
However, high-level CALL instructions still need language
qualifiers to pass parameters automatically, so leave the PASCAL
qualifier in.
The choices of language qualifier are PASCAL, C, BASIC,
FORTRAN, PROLOG, STDCALL, or NOLANGUAGE.
The formal syntax for .MODEL is:
Prc
nef
Wl
see
“All argument names specified in the procedure header, Ml
whether ARGs (passed arguments), RETURNS (return Th
arguments) or LOCALS (local variables), are global in co1
scope unless you give them names prepended with the do
local symbol prefix.” Shl
da
You could have the habit of putting “@@” in front of every label cd
that is to be local to a procedure. This seems ok in principle,
except that I encountered assembly errors that do not make sense E
to me. Apart from my negative personal experience, you can take Fi
it as good policy to use “@@” prefixing as much as possible when
using TASM. Don’t forget to put the LOCALS directive at the
start of the file.
Look ahead to Chapter 6 for examples of using“@@“. I have had
a lot of trouble using “@@” in WINASMOOINC (Chapter 6) -
you can see in the listing on page 168 that I defined “now” as a
parameter passed to procedures, which according to Borland’s
statement above, is global. Yet I have reused it in many
procedures, and in each case it assembles correctly. This
indicates, though I can’t find it mentioned anywhere in the manual,
that the local definition of “now” overrides any other local
definitions.
So “now” is not really global. It only exists on the stack anyway,
so sensibly it is only valid for the life of the procedure in which it
is declared. Borland, would you kindly be a little more precise.
The only difficulty with “now” would be if you had a statically
declared “now” as well, say in the data segment - then you have a
clash. Then it is essential to use “@@” or another name.
Borland has tidied up label scope by using the C-like approach
found in MASM version 6 in their TASM version 5.0. See
Chapter 13.
i:k”;Ee ( a r b i t r a r y ) e q u a t e s c o u l d h a v e b e e n i n a n I n c l u d e
#deki.ne* ’ IDM_QUIT 200
124 Windows Assembly Language h Systems Programming
# NOTE this Make file has been modified for Borland C++,
# to be used with TASM and TLINK, however I'm still using
# Microsoft's NMAKE.
# To run this file: NMAKE WINHULLO.MAK
fn = winhullo
all:$(fn).exe
lpath = \borlandc\lib #path for libraries
ipath = \borlandc\include #path for Include files.
epath = \borlandc\bin #path for EXEs.
SW = /c /n /v /Tw /L$(lpath) #switches for tlink.
# /n =ignore-default-libs, /Tw =generate Windows exe,
# /L$(lpath) = lib path, /v =debug-on.
# Note that these paths all assume you are in the same
# drive.
High-Level Assembly 125
When you type this in, there is no need to put in all the comments,
but be careful about unnecessary blank lines, and leave a blank
line where I have put one. There are certain syntax requirements
that can be messed up otherwise. Note that it’s on the Companion
Disk (\SKELETN%), to save you all that trouble.
The .MAK tile shows where it expects all of the files to be located,
but you can make changes as necessary. You could even put
everything in the one directory, as I suggested, as a quick and dirty
option for Microsoft, if the SDK isn’t installed (see page 86).
Ditto, you could do this with the Borland tiles, but if you have the
complete distribution disks, then why not install properly, in which
case the files will load into the above directories by default.
Prototypes
The program at the end of this chapter can be assembled under
MASMG, or more correctly, ML. As the product is not terribly
compatible with earlier MASMs, Microsoft has renamed it, though
you do have the option of switching on compatibility with version
5.1.
MASMG has developed features that make it look more like C,
most notably the use of prototypes. These are skeleton
declarations of a procedure, which you place at the beginning of
the file, and are used by the assembler for syntax and type
checking. These are best illustrated by an example, and an
excellent example presents itself in the use of INVOKE.
Borland introduced the equivalent with TASMS, though they have
given it a different name: PROCDESC. See Chapter 13, page 308.
INVOKE MASM6 CALL is definitely low level, so to call Windows
nigh-level functions in the convenient manner that we have become
call accustomed to in this chapter, we need to use INVOKE instead.
In fact, TASM3’s high-level CALL is quite primitive alongside the
sophisticated INVOKE, as you’ll see.
The first line shows the call to MESSAGEBOX as we would do
it with TASM:
,.TASM 3.00 h i g h - l e v e l c a l l . . .
call MESSAGEBOX PASCAL, hwnd, ds,OFFSET szabout, ds, \
OFFSET sztitle, MB-OK
;MASM 6.00 high-level call...
INVOKE MESSAGEBOX, hwnd, ADDR szabout, ADDR sztitle,\
MB-OK
PROTO INVOKE does the same job! However if you put it in as shown, it
declaration won’t work, because something else is required - the prototype.
128 Windows Assembly Language & Systems Programming
You can only use INVOKE to call a procedure that has a PROTO
declaration, even if the procedure is external, as in the case of
Windows functions.
Previously I used EXTRN to declare MESSAGEBOX as
external, and that is still recognized by MASM, but PROTO can
be used to replace EXTRN. So, for each and every Windows
EXTRN declaration, replace with PROTO, as shown:
; T A S M ( a n d MASM) e x t e r n a l d e c l a r a t i o n . . .
EXTRNMESSAGEBOX:FAR
;MASM 6.00 prototype for INVOKE...
MESSAGEBOX PROTO FAR PASCAL, :HWND, :LPSTR, :LPSTR, :WORD
Callback Design
So, you can very happily go through the earlier TASM program of
this chapter replacing CALLS with INVOKES and EXTRNs with
PROTOs (or delete the EXTRNs entirely). However, a major
problem still exists: the lack of a WINDOWS qualifer for PROC.
CMACROS.- This means that you cannot have a high-level PROC declaration
/NC for the callback function, and you have to resort to a macro or
insert the prolog and epilog code manually. The demo program
uses CMACROSINC to achieve this.
Without the high-level PROC you can’t have the LOCAL
directive, for convenient creation of data on the stack
(CMACROSINC gets us around this problem).
Because a program isn’t going to have too many callbacks, it’s not
a total disaster, just a nuisance, if you don’t want to use
CMACROSINC. The manual approach is to insert the code as
follows:
You can have local data on the stack, but you will have to
manipulate the stack directly. To get at all of the data on the stack
segment, you could do something like this:
TEXTEQU, Something along these lines will give you access to the labels
EOU within the callback. Note that I used TEXTEQU, because EQU
cannot be used for text aliasing with ML, a major divergence from Which one?
earlier MASMs and from TASM. Notice that the text to be aliased
must be inside ‘I< >‘I.
Anyway, this is academic.
Other Incompatibiiities
PROC syntax So what else needs changing . . .
differences There is a slight difference in the syntax of the high-level PROC
directive. If you look back to the declaration for WINMAIN, you
will see that it started like this: "WINMAIN PROC PASCAL NEAR
hinstance:WORD . . . . .‘ With MASM6 it has to be rearranged
like this: "WINMAIN PROC NEAR PASCAL, hinstance:WORD
. . . . II ,
With TASMS, Borland has allowed MASM high-level PROC
syntax.
STRUC I also came across an interesting problem with fields of a structure.
assembler Incidentally, MASM6 allows nested structures, which previous
differences versions didn’t. Nesting is vital for OOP, though MASM6 is still
nothing like OOP-aware.
The problem is that the program of this chapter has a couple of
lines like this:
;MASM6's solution . . .
lea di, sl.clslpszmenuname
mov [dil,ax
mov [di+2],ds
Oh, and make sure that your callback procedure name is all capital H2INC /C ,
letters.
High-Level Assembly 131
Label scope Another major difference is in the scope of labels. I have covered
differences that topic beginning on page 120. This is one aspect of MASM6’s
move toward the code integrity we expect from a high-level
compiler. Prototyping is another. I think that many serious
programmers will choose MASM on this basis, and it is an area
where Borland had to play catch up, withTASMS.
Which one? All of these comments are, of course, my personal opinion, not the
final truth engraved in stone, and I suggest that potential buyers
consider most carefully what features are most important. Have a
look at reviews in magazines. If OOP is your thing, then look
closely at TASM. Do bear in mind that my comments are based
upon particular versions, and even “maintenance releases” of the
same version number can have significant improvements.
Therefore, take all of the above comments with a pinch of salt, and
check out the features for yourself before buying.
One interesting point is that MASM6 comes with Programmer’s
Workbench (PWB), an editor and IDE, as well as CodeView
debugger. Borland does not provide an editor or IDE, but the
Turbo Debugger is very nice.
I have made some further comments on this on page 309.
ML /c $(fn) .ASM
mov I
MASM6 Program Listing lea
mov
c
mov
mov s
;ASMDEM02.ASM --> ASMDEM02.EXE Windows demo program. mov E
-This skeleton assembly language program has been written mov i
ifor Microsoft MASM (ML.EXE) ~6.1. (Do NOT use mov E
;Borland's TASM!).
;It uses PROLOGUE.INC to force the correct Windows invoke
;prolog/epilog on all FAR PROCs. mov E
I*This program does NOT have the startup code built-in.
;Note that Borland provide startup object module as invoke
;COWS.OBJ (small model) and Microsoft provide mov f
;APPENTRY.OBJ with ~6.1. One of these must be linked.
;Note that APPENTRY.OBJ should be for the small model, to mov f
;suit this program -- if not assemble APPENTRY.ASM, with mov i
iswitches as described in APPENTRY.ASM comments. lea (
mov
.MODEL SMALL mov
lea (
WINVER EQU 0300h mov
?WINPROLOGUE EQU 1 -forces win prolog/epil on far procs. mov
INCLUDEwinasm60.INC ithis is not the same WINDOWS.INC
.used by the TASM programs. It is lea i
:generated by HZINC.EXE, and contains invoke
;prototypes. Generated by... or i
* HZINC /C /Gc WIND0WS.H . . . jne c
IDM_QUIT EQU 100 ’ ;menu-identifiers -- must be jmp c
IDM_ABOUT EQU 101 ;same as defined in .RC file.
createwi
EXTRN - astart:FAR *startup code, in APPENTRY.OBJ invoke
i (referenced at END)
;........................................................
.DATA mov 6
szwintitle DB 'HULL0 DEMO PROGRAM',0 invoke
szASMDEMOname DB 'ASMDEM02',0 invoke
hOemFont DW 0 ;handle to OEM font. jmp 6
soutstring DB 'Hull0 World'
szaboutstr DB 'Assembly Language Windows Demo',0 ;This is
;messagebox ;for mes
sztitlestr DB 'Karda Prints',0 ; / mainloop
lea i
;........................................................ invokt
.CODE lea 2
PUBLIC WINMAIN invokt
WINMAIN PROC NEAR PASCAL, hInstance:WORD, \ message1
hPrevInstance:WORD, lpCmdLine:LPSTR, nCmdShow:SWORD lea i
LOCAL @hWnd:HWND invokt
LOCAL sl:WNDCLASS or i
LOCAL s2:MSG jne n
cmp hPrevInstance,O ;=O if no previous instance. ;GetMess
je yeslst ;so here
jmp createwin mov z
yeslst: quitwinm
;Setup the window class structure for REGISTERCLASSO... ret
High-Level Assembly 133
mov sl.Style,3
lea di,sl.lpfnwndproc
mov [dil,OFFSET ASMDEMOPROC
mov [di+al,SEG ASMDEMOPROC
mov sl.CbClsExtra,O
mov sl.CbWndExtra,O
mov ax,hInstance
mov sl.HInstance,ax
invoke LOADICON, null, ID1 APPLICATION
mov sl.@HIcon,ax
invoke LOADCURSOR, null, IDC - ARROW
mov sl.@HCursor,ax
mov sl.hbrBackground,COLOR BACKGROUND
mov ax,OFFSET szASMDEMOname
lea di,sI.lpszmenuname
mov [dil ,ax
mov [di+2],ds
lea di,sI.lpszclassname
mov [dil,ax
mov [di+2l,ds
lea ax,sl
invoke REGISTERCLASS, ss::ax
or ax,ax
jne createwin
jmp quitwinmain
createwin:
invoke CREATEWINDOW, ADDR szASMDEMOname, \
ADDR szwintitle, OOCFOOOOh, 150, O,\
400, 300, 0, 0, hInstance, 0
mov @hWnd,ax
invoke SHOWWINDOW, ax,nCmdShow
invoke UPDATEWINDOW, @hWnd
jmp SHORT messageloop ;go to the main message loop.
;This is the main message loop, in which Windows waits
-for messages
mainloop:
lea ax,s2
invoke TRANSLATEMESSAGE, ss::ax
lea ax,s2
invoke DISPATCHMESSAGE, ss::ax
messageloop:
lea ax,s2
invoke GETMESSAGE, ss::ax, null, null, null
or ax,ax
jne mainloop
;GetMessage() returns FALSE (AX=01 if a "quit" message...
,-so here we are quiting....
mov ax,sZ.WPARAM ;return wparam to windows OS.
quitwinmain:
ret
134 Windows Assembly Language & Systems Programming
invoke SELECTOBJECT,ax,hOemFont
invoke TEXTOUT,@hDC,10,20, ADDR soutstring,ll
lea ax,s3 * -- far-addr of paint-structure.
invoke ENDPAINT,ihWnd,ss: :ax
jmp SHORT xbreak
;........_...............
xmenu:
cmp WORD PTR ilParam,O ;low-half of 1Param
jne xbreak I*test if a menu-message.
=mp iwParam,IDM_QUIT ;wParam.
jne notquit
jmp xquitmessage
notquit:
=w iwParam,IDM ABOUT
jne xbreak - ;no other menu items.
I*let's put up a message about this program...
invoke MESSAGEBOX, ihWnd, ADDR szaboutstr, \
ADDR sztitlestr, MB-OK
;.........................
xbreak:
sub ax,ax ;returns 0 in DX:AX. (callback functions
cwd I*return a 32-bit (long) value).
xreturn:
ret
ASMDEMOPROC ENDP
;........................................................
END astart I*name of startup code.
If you can’t quite see how to use this, look at the skeleton in
Chapter 13.
Run-time high-level IF/ELSEIF/ELSE constructs tidy up your
assembly code enormously, and I’m hooked on it. Note that it
assumes nothing and does not change any register values. This
136 Windows Assembly Language & Systems Programming
means that you can jump out from anywhere and jump around
inside, like this:
. I F ax==0
jmp place1 ;goto anywhere, quite legal.
.ELSEIF ax==1
place1 :
.ELSEIF ax==2
jmp place2
. ENDIF
place2:
A rationaie
for OOP
6
Program Design
Preamble
This chapter is about interfacing assembly language with C and
C++ and about one aspect of program design that is an outcome of
the interface with C++ - objects. I have not gone into any
general methodology of software design.
Histo/y of Programmers are migrating from C to C++. Ditto with other
OOP and languages, and of course the new kid on the block is Java. You
assembly have got to think in terms of objects. Early in 1991 I put a lot of
thought into object oriented assembly language, including the
presentation of a paper.
I developed techniques for OOP, but found the assemblers of that
time to be somewhat inadequate. So about mid-1991, I wrote to
Borland in the USA explaining in detail what was wrong with their
assembler and what it needed to be able to handle objects. Then,
in February 1992, I was fascinated to learn that Borland had
released a new assembler that they advertized as “object oriented”.
I like to think that I was one of their inspirations.
A rationale Why should you even bother with objects when programming at
for OOP the assembly level? The answer is very simple:
137
138 Windows Assembly Language & Systems Programming
Object Addressing
Windows C++ has a lot of terminology that can be very intimidating. Yet
assembly, the underlying concepts are quite simple.
generic It is also quite true that you can read an explanation from a C++
Windows, manual or textbook a dozen times, and not fully grasp it. But if
386/486 you were to see how that concept is implemented at the assembly
architecture, level, it would become clear.
OOP, C*+! This is one reason why I am in favour of this book being used as a
prerequisite, not just to Windows, but also to C++ programming.
Class The way we write a program using 00 techniques is by grouping
data and code that naturally belongs together into a class (structure
definition). A structure need not contain just data; it can also have
pointers as fields in the structure (or a pointer to another structure
of pointers), and this is one of the key features of the 00
technique.
Calling a Function
Object = With C++ there are objects, and a procedure or function (now
instance called a function-member or method) is part of a class. The objects
are instances of a class. Data is also part of the class. An instance
is a complete copy of the class, with possible unique
initialisations, created in memory.
Program Design 139
.DATA
WINDOW STRUC
active DB 0 I-example data-member.
TExTOuT DW t e x t o u t m a i n ;example f u n c t i o n - m e m b e r .
. . .
WINDOW ENDS
W;&LASSA STRUC ;sub-class of WINDOW.
WINDOW -Inherits everything
WINCLASSA &I& lfrom WINDOW.
W;kLASSB STRUC ;Ditto, but a function override.
WINDOW textoutdlg >
WINCLASSB E-&k
. . .
.creating instances . . .
hindowl WINCLASSA c >
window2 WINCLASSB c >
window3 WINCLASSA c 5 ; etc . . . .
.&E
lea si,windowl
call textoutmain PASCAL,parl,par2,si
call [si].TEXTOUT PASCAL,parl,par2,si
'ika si,window2
call textoutdlg PASCAL,parl,par2,si
call [si].TEXTOUT PASCAL,parl,par2,si
. . .
textoutmain PROC PASCAL pl,p2,THIS
. . . ;this is the textoutmain procedure . . . etc...
An object Further down in the code you would have to have the two
combines procedures: textoutmaino and textoutdlgo.
code and Look very carefully at the above listing. First I defined a class
data (structure) called "WINDOW ", with a data-member “active” and a
function-member "TEXTOUT". The latter is a pointer to a
procedure' called "textoutmain".'
’ The purists are probably very unhappy with my interspersionof the words "procedure" and
"function" asthoughtheymean the same thing. For ourpurposesthey do. So there!
* Most assemblers do not let you put a forward-reference into a structure field. It must be done
when the instances are created. In this example, “textoutmain” would have to be placed in the
“< >” portion of each instance-declaration. This is messy. TASM ~3.0 is the first truly object
oriented assembler, and has a mechanism for allowing forward-references, as shown in the
second half of this chapter.
Program Design 141
Early Binding
The first call in the above listing is an example of early binding.
Why? Because I have hard-coded the address of the function I
want to call into the CALL instruction, in this case textoutmain().
’ A warning here, though, is that if your instances are LOCAL and if you use a memory model in
which data and stack segments are different, then there are potential problems with using SI. A
memory access to the stack segment requires BP-relative addressing or an SS: override if using
SI.
It is possible for the object to be located in some other segment entirely, and in that case THIS
would have to equate to a FAR address, such as ES:[SI]. This comment does not apply to
32-bit programming, which uses a FLAT memory model in which there is only one segment.
142 Windows Assembly Language & Systems Programming
Late Binding
The second call in the above listing (page 140) is an example of
late binding. The meaning of this is “call the TEXTOUT function
in the instance windowl”. Another way of writing it is:
c a l l [OFFSET_windowl + TEXTOUT]
This code calls textoutdlgo. The last two lines are actually the
same, due to the way in which the assembler treats the window2
label in this context, but I recommend that you stick with the latter
to avoid confusion. THIS passed on the stack must always be the
register, not the label, so be consistent and use SI in both places.
This implements polymorphism.
C++ Binding
Examine this C++ code . . .
1.. ”
.. The program starts by declaring a class called WINDOW and the
operator data and function members it has. I only put in two members:
active and TEXTOUT. After that I put in the actual code for
TEXTOUT. Notice the syntax for doing this - the I’::” means that
this function belongs to the class named to its left, which is
WINDOW.
Subclassing Because I wanted this code to do exactly what the assembly listing
with override does (page 140), I created two subclasses - WINCLASSA and
WINCLASSB. WINCLASSA is identical in every way to
WINDOW, but in WINCLASSB I have overridden TEXTOUT.
Notice that I didn’t have to give the new procedure a different
name.
Then I declared three static instances (permanently in the data
segment). I could have made them automatic simply by moving
them down into main().
144 Windows Assembly Language & Systems Programming
“_> ” The code within main0 shows how easy it is to call the function
object associated with a particular object. A call to this function means
pointer that TEXTOUT will execute but will automatically work on the
data and functions that are part of the referenced object. This is
because the THIS pointer is passed on the stack (see page 140).
The example of late binding may look rather complicated. “ptr”
is a label that is a pointer to data of type WINDOW. The “*”
simply declares that it is a pointer. The data type tells C++ that ptr
can only be used to address objects (instances) of WINDOW.
The next line sets ptr to point to windowl.
The following line uses ptr to call windowl.TEXTOUT(). This
line corresponds exactly with the assembly language code:
i.& si,windowl
jmp redraw
iii si,window2
jmp redraw
. . .
redraw:
call [si].TEXTOUT PASCAL,x,y,si
. . .
Use of THIS
THIS is a pointer to the current object and is already introduced
and discussed at length earlier in this chapter. However, this
section will consider the rules of usage of THIS,
I have explained how SI is passed on the stack to the function.
Why pass it on the stack, since SI will be the same value upon
entry to the function anyway and can thus be accessed from the
register?
C++ does it that way, but your assembly program doesn’t
necessarily have to. However, it may be wise to stick with C++
conventions to enable smooth linking with C++ code.
Accessing You can see back in the class definition for WINDOW (page 140)
the right that I put an example data field labelled “active”. Perhaps this is
data a flag indicating whether this is the active window or not -
whatever.
146 Windows Assembly Language & Systems Programming
lea si,windowl
call [si].TEXTOUT PASCAL,x,y,si
Encapsutation A function should only write to (and even only read from)
ofdata within data-members of the current instance, as pointed to by THIS.
an object In OOP terminology, this is the principle of encapsulation. The
data belonging to a particular object should only be accessed by
functions belonging to that object, and only if THIS is set to that
object. C++ does allow you to get around this, but think of it as
the ideal to be aimed for.
In assembly language you can break all the rules, but you should
try not to write 00 code that accesses data belonging to other
objects. If your function wants to access some data elsewhere, the
proper way to do it is to change THIS to that object and then call a
function that is part of that object. If no such function exists, then
you will have to write one.
Stfucture 0fa Referring back to our earlier call to TEXTOUT, with THIS set to
function window 1, the actual procedure called will be textoutmaino, which
-member could have the following structure:
Compile a The trick is to write the assembly language module into the C
.ASMstub program in the form of a stub or skeleton. That is, it won’t do
anything except have the data transfer C instructions. Compile it,
and look at the assembly listing for that routine. Extract that
routine into a separate assembly language tile, and delete the
original stub.
Name This works fine and is surprisingly easy to do. The method
-mangling overcomes some serious hurdles, especially that of
name-mangling. It is a C++ feature that the source code can have
the same name for different functions, and other labels can also
have identical names. The compiler gets around this problem by
“mangling” the labels - applying an algorithm so that even labels
of the same name will have new unique names. The problem is
that, if you are writing an assembly language module that must
access labels in C++ modules, you can’t reference them by name
- you can only reference them by their mangled names.
The only way to know the mangled names is by the stub method
described above, because the assembly language output will show
all labels in their mangled form.
In-Line Assembly
A completely different approach is not to write the assembly
language module as a separate tile, but to write it in-line with the
C code. You have to have a compiler that supports this, and of
those that do, the in-line assembler is not quite so fully-featured as
the stand-alone assembler. You lose in one way, but gain in
another. What you gain is seamless integration with the C
program. You can write the assembly code with full access to the
C labels, and the registers that you use are automatically saved and
restored by the compiler upon entry to and exit from your
assembly module.
Here is a simple example:
c l a s s WINDOW
{
public:
int active;
virtual void TEXTOUT (int) ;
1;
void WINDOW :: TEXTOUT tint x)
1
asm mov si,this
asm mov dx,[si].active //addr relative to DS.
asm mov ah,2
asm int 21h
1;
Program Design 149
Static versus I have shown two ways of creating the instance windowl. The
automatic commented-out example is static, because it is outside main(),
instances while the other is automatic, because it is created on the stack, for
the duration of execution within the function.
See how I have addressed the data-member “active” from
assembly code. Actually, this is dependent upon memory model
and whether the object is static or automatic. For the SMALL
(and FLAT) model the SS and DS registers are the same, so there
is no problem. For those models in which SS and DS may be
different, the code given here would be ok for a static object, but
SS override will be required for automatic data. This can be taken
care of by using BP instead of SI, since BP by default references
the stack segment.
What the By the way, the above program passes the value 07 to
above TEXTOUTO, which sends it to the screen. 07 is the “bell”
program character, so you get a beep to indicate success.
“does” Although a parameter is passed to TEXTOUTO, I haven’t used it
within the assembly routine. I put it in to show that it is an option.
Note that the compiler will give a warning (at least Borland’s BCC
compiler does) that the passed parameter is unused.
In-Line DOS and Don%
While we are on the topic of in-line assembly, I might as well
cover the major do’s and don’ts.
I’ve grouped these below for easy reference:
150 Windows Assembly Language & Systems Programming
asm je place1
asm nop
place1 :
asm mov ax,vall
l You have complete access to all data and code labels in the C
program, barring the usual C constraints.
l Note that the compiler saves and restores some CPU registers
upon entry and exit from an in-line assembly section.
Compilers differ in what they save and restore.
l You cannot use the ” ; ” (semicolon) to start a comment.
Instead you have to use the standard C delimiters. For
example:
l But also note that you do not use the “;” to separate in-line
statements, not even the last one.
WINDOW *ptr;
ptr = &windowl;
ptr -> TEXTOUT (vail, Va12);
So that you are absolutely clear on what this compiles down to,
here is the actual assembly language generated:
Program Design I5 I
Looking back again at the code from "C++ Binding" on page 142,
you will see the definition of TEXTOUTO. But if TEXTOUT is
to be the assembly language module, you would leave it in the C
program for now, as a stub. You would put in the skeleton code,
as follows:
BCC -c -S filename. C P P
ret
BWINDOW@TEXTOUT$qii ENDP
END
The skeleton program gives you the mangled names and how to
access the dataandfunctionmembers. Thenyoucango aheadand
flesh out the assembly module.
Your next step would be to remove the stub from the C++ module
and compile as follows:
Note also that C++ does have an EXTERN declaration, so that any
function that is referenced in the C++ module but is defined in the
assembly module can be declared as EXTERN. However, this
also has limitations and is optional anyway.
There are eleven lines there, but take off the comment line and put
the code-label on the same line as the following instruction, and it
becomes nine lines.
This program is the most basic skeleton, putting only a window on
the screen and nothing else. In a moment I’11 show you how
simple it is to add the menu-bar and message box, as per skeletons
from previous chapters. But first have a look at the above.
Kickstat: In the data segment I created an instance of a WINDOW structure
called "windowl". In the code routine called "kickstart :'I I set
THIS to window1 and then called make( ), which, as its name
suggests, creates the window and puts it on the screen.
I.54 Windows Assembly Language & Systems Programming
You may have noticed that the syntax for creating the instance of
WINDOW doesn’t look much like that for structures (see page 65),
but don’t worry about that for now.
Hiding the There is a tick here: I have taken all the “red tape”, the
“red tape” complexity, of the Windows program and hidden it away in the
Include file WINASMOOINC. This hiding of the unnecessary
complexity and exposing only what is needed can only be done by
using 00 techniques.
My object oriented Include file is a world’s first. Nobody has done
this before. No Microsoft or Borland documentation will tell you
how to do this. The Microsoft documentation is appalling from
the assembly language programmer’s viewpoint. The Borland
manuals keep getting thinner too. Mind you, the simple program
you see above didn’t just materialize in my mind. I just about tore
my hair out at times.
Siinpfe C++ I came across a very interesting article by John Dimm titled “A
classes for Tiny Windows Class Library” in Programmer’s Journal, USA,
Windows Dec. 1991. I also studied Norton and Yao’s Borland C++
Programming for Windows, Borland/Bantam, USA, 1992. A few
ideas come from these and other sources, but I ended up doing my
own thing, and what is presented in this chapter is quite simple and
elegant.
It is written in Borland TASM version 3.0, for the simple reason
that this assembler is specifically designed for OOP. However, I
must emphasize that the code is very general and with some
modification will work with earlier versions of TASM and with
MASM. I have pointed out the divergence from non-OOP
assemblers within this chapter. The disadvantage of the non-OOP
version is that it is awkward, cumbersome, and verbose. The OOP
version is easier to use, conceptually simpler, and requires fewer
lines of code.
Look on the Companion Disk for various example 00 programs.
A Skeleton Program
You might like to recall how complicated and enormous was the
skeleton from Chapter 5. Now, here is the same thing . . .
.DATA
window1 WINDOW { szclassname= t"WINASMOO", sztitlename= \
"Main Window", paint= wlpaint, create= wlcreate,\
command= wlcommand }
; . . . . . . . .
.CODE
kickstart:
lea si,windowl Ieaddr of window object.
call [sil .make PASCAL,si ;make the window.
ret
; . . . .
wlpaint PROC PASCAL
LOCAL hdc:WORD
LOCAL paintstructa:PAINTSTRUCT
lea di,paintstructa
call BEGINPAINT PASCAL, [sil.hwnd, ss,di
mov hdc,ax
call SELECTOBJECT PASCAL,ax, [si].hfont
call TEXTOUT PASCAL,hdc,10,20, cs,OFFSET sout,l6
call ENDPAINT PASCAL, [si].hwnd, ss,di
ret
sout DB "Demo 00 Program!"
wlpaintENDP
; . . . .
wlcreate:
call GETSTOCKOBJECT PASCAL,OEM -FIXED - FONT
rnn; [si] .hfont,ax
; . . . .
wlconmtand:
cmp WORD PTR [si] .lparam,O;lo half
jne notmenu
cmp [si].wparam,200 ;IDM -QUIT. Is llQuitlt selected?
jne notquit
call [si].destroy
ret
notquit:
cmp [si].wparam,201 ;IDM- ABOUT. selected?
jne notabout
call MESSAGEBOX PASCAL, [si].hwnd, cs,OFFSET szmsg, \
cs,OFFSET szhdg, MB-OK
notabout: ret
notmenu: ret
szmsg DB "Created by Barry Kauler, 1992",0
szhdg DB "Message Box",0
ret
END
precisely how to do this, of course, but for now just look at the
overrides in the above example.
I have given the window a class-name of “WINASMOO” and I
have given it a title to appear in the title-bar at the top of the
window. If you remember back to Chapter 3, and in particular
page 77, you’ll know that whenever anything happens over your
application’s window while the window is active, such as a
menu-item being selected or key being pressed, then Windows
will send a message via the message loop in WinMain() to the
window’s callback function. It is then up to the callback function
to process the message.
Overrides
Overriding WINASMOOINC handles all the messages in a default manner,
PAINT but should you want to process any message, just put in an
message override when creating the instance of the window. All Windows
messages are prefixed with “WM_“, such as “WM_PAINT”, or
“WM COMMAND”. In my skeleton program I wanted to
override default handling of WM_PAINT, so I put "paint =
wlpaint", where “wlpaint” is my routine (see above). You will
find the code for WM_PAINT handling is just about identical to
that of Chapter 5.
COMMAND Ditto for WM_COMMAND. I put in my own routine called
message "wlcommand", because I wanted to respond to menu-bar
override selections. I also overrode WM_CREATE. It’s that simple.
PROC... One thing you will notice with my routines "wlcommand" and
ENDP "wlcreate" is that I didn’t put in PROC - ENDP directives.
syntax These are not essential, and the routines work perfectly well
notes without them. Putting them in would make no difference. In fact,
putting the PASCAL qualifier on would also make no difference,
since no parameters are being passed.
However, notice that I did put PROC PASCAL - ENDP around
the “wlpaint” routine. The reason for this is that I wanted to
have LOCAL data, and only TASM’s “high-level” PROC
automatically takes care of LOCAL declarations. The simple act
of putting the PASCAL qualifier onto the PROC directive
transforms it into a “high-level” PROC.
Leave off PROC [PASCAL] - ENDP if you wish, but put it on if
your routine has LOCAL data. The only effect of the high-level
PROC will be to correctly handle LOCAL data within the
procedure.
This is a syntactical deviation from the main discussion, so I will
weave my way back to the next step.
Program Design 157
Kickstart
“kickstart :‘I is where the ball starts rolling. Of course the entry
point to your program is at WinMain(), but this function is inside
WINASMOOJNC. WinMain() takes care of all the red tape and
ends up calling “kickstart :‘I. “kickstart :” must always be
in your object oriented program. Again, I’ve left off the PROC
[PASCAL] - ENDP, for the sake of brevity and simplicity.
A static instance of the WINDOW structure already exists in the
data segment, so the first thing that kickstart does is get the
object’s address. The next thing it does is actually create the
window and display it on the screen. You will remember from
previous chapters that this was a particularly long-winded process.
Look back to page 116 and you will see that the application calls
Windows CREATEWINDOW() function to create the window,
then SHOWWINDOW() and UPDATEWINDOW() to actually
show it on the screen. All of this is red tape and is hidden away.
Message Handling
Processing After creating the window, Windows sends a WM_CREATE
the CREATE message to the window’s callback function. I used this message to
message get the handle to a particular font that I used in the program (yes,
even fonts have handles!). Hence I put in the wlcreate() routine.
Processing Whenever Windows redraws any portion of the client area of the
the PAINT window, it lets the callback know by sending WM_PAINT. This
message is so the callback can redraw the client area or the portion that
requires redrawing. The UPDATEWINDOW() function also
generates a WM_PAINT message.
I wanted to put out a simple text message, in this case “Demo 00
Program!“. It also uses the font that I previously got a handle for,
rather than the default font. There is a bit of red tape involved to
output the message, and some temporary data storage is required.
“hDC” is the handle to the window’s client area, that is, the area of
the window that we can output to, and this handle must be
obtained before we can gain access to the window client area. It is
normal practise to release this handle immediately after use, which
has been done by ENDPAINTO.
Processing The other thing I did in my skeleton was respond to the “File”
the menu-item, with its “Quit” and “About . ..‘I sub-items. The normal
COMMAND way to define these is by the resource file .RC, and I have used
message exactly the same one as before. The WM_COMMAND message
needs to have its lparam and wparam analysed to determine what
kind of command has been sent to the callback, and this example
158 Windows Assembly Language & Systems Programming
shows that if lparam = 0 then the command has come from the
window’s menu-bar. In such a case, wparam is analysed to see
which item has been selected from the menu-bar.
Handling Notice that selection of “Quit” results in calling destroy(). Notice
QUIT also that it is prefixed with ” [ si] . I,‘ as are all the other
menu-item parameters of the window. You can understand this from the
principle of structures. The SI register contains the address of the
object or the instance of the structure. “Destroy” is a field in this
structure. Fields can, in OOP terminology, be data-members or
function-members (methods in Pascal terminology). The field
‘Ides t ray” does not contain data, but a pointer to a routine.
To effectively use this object oriented approach, you need to know
the fields of the WINDOW structure and the purpose of each . . .
.DATA
WINDOW TABLE {
VIRTUAL definewndclass:WORD = WINDOWdefinewndclass
VIRTUAL create:WORD = WINDOWcreate
VIRTUAL paint:WORD = WINDOWpaint
VIRTUAL command:WORD = WINDOWcommand
VIRTUAL timer:WORD = WINDOWtimer
VIRTUAL resize:WORD = WINDOWresize
VIRTUAL mousemove:WORD = WINDOWmousemove
VIRTUAL 1buttondown:WORD = WINDOWlbuttondown
VIRTUAL 1buttonup:WORD = WINDOWlbuttonup
VIRTUAL char:WORD = WINDOWchar
VIRTUAL defaultproc:WORD = WINDOWdefaultproc
VIRTUAL destroy:WORD = WINDOWdestroy-
VIRTUAL make:WORD = WINDOWmake
VIRTUAL wndproc:WORD = WINDOWwndproc
VIRTUAL hwnd:WORD = 0
VIRTUAL wmessage:WORD = 0
VIRTUAL wparam:WORD = 0
VIRTUAL 1param:DWORD = 0
;;;J;E classst,yle : WORD = CS VKEDR$W+ CS_HREDRAW
szlconname:BYTE:32 =
VIRTUAL szcursorname:BYTE:32 = 0
VIRTUAL hbrbackground:WORD = COLOR_BACKGROUND
VIRTUAL szclassname:BYTE:32 = 0
VIRTUAL sztitIename:BYTE:32 = 0
VIRTUAL hmenu:WORD = 0
VIRTUAL hwndparent:WORD = 0
VIRTUAL wheight:WORD = 200
VIRTUAL wwidth:WORD = 250
Program Design 159
VIRTUAL y_coord:WORD = 0
VIRTUAL x coord:WORD = 150
VIRTUAL createstvlelo:WORD 0
;;;J;E creat$f;t&le..RbW”p”RD = WS_OVE$CAPPEDWINDOW
This doesn’t look like any structure definition you’ve seen before!
Instead of using STRUC, I have used TASM’s TABLE directive,
which has some advantages but a different syntax.
00 The Borland programmers will probably gag when they see how I
/imitations have used their TABLE directive, but I found it useful to define
of STRUC both data and procedures. I wanted to retain a program that would
work with other non-00 assemblers with only minimal change.
The above TABLE can be replaced with the conventional STRUC,
but the latter has disadvantages, the two most glaring being:
l it cannot initialise fields with forward references; and
l initializing fields of instances is rigid and awkward.
However, it can be done - check out “Object Oriented
Programming in Assembly Language” by R. L. Hyde, Dr. Dobb’s
Journal, March 1990, p. 66-73, 110-I 11.
The TABLE directive only exists with TASM version 3.0, not
before. I have only bitten off a little bit of the new TASM’s 00
capability; however, my end result is quite simple and elegant.
TASM User 3 Despite a wonderful new assembler, Borland’s manual has only
manual: about two and a half pages devoted to Windows programming and
limtations & only two demo programs on disk. The OOP neophyte will find the
TABLE TASM manual to be quite daunting, with all of the 00
description terminology. The manual supplied with TASM version 5 has even
less documentation. This book addresses all of these problems.
Not only do I demystify OOP, but I show how to write windows
programs effortlessly.
The Borland manual describes the use of TABLE to define
function-members (methods) for an object, with the data-members
defined separately. There are certain reasons for this, but I wanted
a system that is conceptually simple.
Notice the VIRTUAL qualifier in front of every field declaration.
Don’t worry about this -just pretend it isn’t there.’
’ Readers with some knowledge of OOP will know that VIRTUAL is a qualifier used with
functions, but I’ve also put it in front of data-members, because I have used TABLE in a way
that Borland never intended (or thought of!).
160 Windows Assembly Language & Systems Programming
window1
definewndclass WINDOWdefinewndclass
create WINDOWcreate
paint WINDOWpaint
. . . etc . . . . . . etc . . .
TABLE Apart from being able to declare forward references, the beauty of
overrides the TABLE directive (and TASM’s new extended STRUC) is the
declaration of overrides when creating instances.
A TABLE or STRUC declaration is only for the assembler’s use,
and is not actually assembled. It is the instances that get
assembled. A static instance is one that you declare in the data
segment. You could declare automatic instances on the stack or
dynamic instances on the heap. Dynamic instances involve an
extra level of complexity, so let me shelve that one for now.
The skeleton program on page 154 declared a static instance as
follows:
.DATA
window1 WINDOW { szclassname= "WINASMOO", sztitlename= \
"Main Window", paint= wlpaint,create= wlcreate, \
command= wlcommand )
Program Design 161
WINMAIN( )
WimUain() WINASMOOINC has the job of hiding the “red tape” of a
is hi#den Windows program. It must handle multiple instances of a
inside the program; that is, if you double-click on the program’s icon more
JncJude fie than once. It must handle multiple windows within the one
instance.
Any one window would have its own instance of the window
structure or object, as I did by creating “window1 “. For a second
window, I could create an instance of WINDOW called
“window2”.
Program Jisting WinMain looks similar to code that you would find in a
continues fmtiJ conventional WinMain() function, with some curious
p a g e IfI differences. Well, look at the whole lot ..,
;......................................................_.
PUBLIC WINMAIN
WINMAINPROC PASCAL NEAR O@hInstance:WORD, \
@~hPrevInstance:WORD,44lpCmdLine:DWORD,@~nCmdShow:WORD
LOCAL msg:MSGSTRUCT ;see WINDOWS.INC
Notice the use of the “@@” prefix. This keeps these labels unique
to this procedure. Refer back to page 12 1.
lea si,mainl
mov ax, @ahinstance -save params i n m a i n 1 o b j e c t .
m o v [si] . hinstance, ax : /
loopback :
call TRANSLATEMESSAGE PASCAL, ss,di
call DISPATCHMESSAGE PASCAL, ss,di
messageloop:
lea di,msg
call GETMESSAGE PASCAL, ss,di, null, null, null
or ax, ax
j n e loopback
mov ax, [dil .msWPARAM ; return wparam to windows.
ret
WINMAIN ENDP
Program Design 165
One callback There is nothing new about the message loop. Remember how
for all Windows calls GETMESSAGE to get a message from the
windows application’s queue, then calls DISPATCHMESSAGE() to send it
on to the callback function. Because each window has its own
callback function, we have to design the program so that the
message will end up at the correct callback - except that in this
program there is a trick. There is only one callback function,
called exportwndproc().
It is a common practise with Windows programming to reuse one
set of code with different data for each window.
Most Windows programs can have multiple instances, that is,
multiple copies running simultaneously without conflict, even
though they use the same code. Each time you double-click on the
application’s icon, a new data/stack/heap segment is loaded, but
the original code segment is used. This practical functionality is
enabled in the .DEF file by specifying the data as MULTIPLE (see
page 177).
The same principle can be applied to multiple windows within the
one instance.
Callback
One callback, Now this is interesting. Despite the fact that a program can create
but each as many simultaneous windows as it wants, there is only one
window is a callback function, exportwndproc(). Exportwndproc() determines
separate which window has sent the message, which is easy enough,
object because its handle, hwnd, is passed to the callback, then it gets the
address of the corresponding window object, which it loads into
SI.
This is conceptually quite simple. Any activity related to the
active window on the screen will result in Windows sending a
message. The callback can use the same code for all windows,
except for overrides - all it needs to know is the address of the
object (the data and pointers) for that window . . .
PUBLIC exportwndproc
exportwndproc PROC WINDOWS PASCAL FAR \
@@hwnd:WORD,@@message:WORD,\
@@wparam:WORD,@@lparam:DWORD
LOCAL dummy:WOR6:5
; . . . .
cmp pwindowflag,O ;Make() controls this flag.
jne normalwndproc
call DEFWINDOWPROC PASCAL, @@hwnd,@@message, \
@@wparam, O@lparam
166 Windows Assembly Language & Systems Programming
ret
; . . . .
normalwndproc:
push si ; callback must preserve si.
push di ;and di
call GETWINDOW'WORD PASCAL,@@hwnd,O
;O=offset in Windows internal data.
mov pwindow,ax ;get addr of current window object.
mov si,ax ;don't use LEA
Having got the address of the object, I then save the parameters
that Windows passed to the callback into the object.
I then called wndproc(), whose address is actually in the object.
By default it is WINDOWwndproc(), shown below. You can
Program Design 167
I could have been a bit more impressive and emulated the case
statement with a dual-column table and a program loop to find a
message that matches, which would be better if a lot of messages
are to be handled. The above code is ok though.
168 Windows Assembly Language & Systems Programming
Now for the part that actually creates the window; herein are some
secrets that make the program work. By referring back to
Chapters 4 and 5 you will see the code that remains from before,
such REGISTERCLASSO, CREATEWINDOW(),
SHOWW&OW() and UPDATEWINDOW().
The data structure WhDCLASS is there, or rather an instance of
it. It needs to have data put into it, and rather than do it in-line I
have called the function definewndclasso to do it. Compare this
with the listing starting on page 112 - look back there also to see
how WNDCLASS is defined in WINDOWSINC. The data for
this structure is from the window object (pointed to by SI).
Window class
data s~rucfure Continuing the program listing . . .
The above routine simply copies data from the object into
wndclassa.
Default What follows are the default routines (function-members, or
message methods) that the WINDOW structure is initialized to. As you can
handling see, they don’t do much, and if not overridden, all you will get on
the screen is a blank window. It will have a system menu, so you
can quit the program, and it can be minimized, etc. - all of this
functionality was set by REGISTERCLASS and
CREATEWINDOWO.
WINDOWdestroy:
call POSTQUITMESSAGE PASCAL,0
ret
;......
WINDOWcreate:
WINDOWpaint:
WINDOwcommand:
WINDOWlbuttondown:
WINDOWlbuttonup:
WINDOWchar:
WINDOWtimer:
WINDOWresize:
wr~owmousemove:
WINDOWdefaultproc:
call DEFWINDOWPROC PASCAL, [si] .hwnd, [sil.wmessage,\
[si] .wparam, Isil .lparam
ret
Inheritance
Example 00 The next example shows how to create a control. You will need to
program with refer to a Windows programming book to learn all about controls;
a control however, this example will give you some idea.
A control is a child window, that is, a window that resides within
the client area of the parent window and normally sends its
messages to the callback function of the parent.
The example creates a simple “button”, with the title “OK” inside
it. When the mouse is clicked over the button, it disappears.
Pressing any key brings it back. Not much, but it does illustrate
some useful principles. Figure 6.2 shows what it looks like.
The button that is added by this program is the one on the main
window. The message box is also a type of child window, created
b y MESSAGEBOXO. Controls can be all sorts of things,
including edit boxes, check boxes, buttons, and scrollbars.
I72 Windows Assembly Language & Systems Programming
Control Since a control is just a window, why not use the WINDOW
Ci&%S structure and make()? Well, yes, it can be done, except that
controls do have some special requirements.
If you think in terms of conventional programming, you would
probably delve into make0 and see how to patch in the handling of
such a special case. Unfortunately, this is one of the major
problems with such programming; the continual patching of code
to handle special cases. If your code works, the process of
patching is liable to make it less stable and predictable.
Better to leave well enough alone. We have a functional make0
for normal windows, so let’s think like 00 programmers. We
could simply create another instance of WINDOW, say
“window2”, and override the make0 with a new routine.
That is ok if all we ever want to do is create one control, but it is
nicer if we think in the long term. Why not create another class,
call it CONTROL, and let it inherit everything from WINDOW,
but with any necessary overrides?
Comp/eie 00 This is what has been done with my program, and the new make0
program with routine can become part of WINASMOOINC, along with the new
a corltro/ class. First, here is the final program:
ret
szmsg DB "Created by Barry Kauler, 1992",0
szhdg DB "Message Box",0
hIchar:
-let's bring back the button if any key pressed...
’ lea si,controll I-since si points to windowl.
call [si] .make PASCAL,si
ret
;......................
END
;...
ending : ret
CONTROLmake ENDP
comparison You can treat the control object just as you would a window
befween object, using all the same data and function members. To make
CONTROL and this statement almost completely true does actually require a little
WINDOW more refinement - message, wparam and lparam data members
classes of the control object are not actually used, so it would be wise to
put in some testing to avoid them being accidentally accessed -
though this is unlikely. Ditto for most of the functions.
The problem with inheritance is that I can’t throw away the
previous structure’s fields. All I can do is redefine them.
Actually, although there is redundancy here, it is possible for a
control to have its own callback, which means that all of the fields
would be of use.
One immediate refinement could be to override all of the message
handlers for the CONTROL class, so that they just return without
doing anything.
Anyway, I’ve kept this code as simple and as elegant as possible.
Getting it Together
OOP One thing you may be starting to appreciate is that Windows adds
overhead an incredible processing overhead - even a simple key press has
to go through so many steps before it reaches the destination.
Then we go and make things even worse by using 00 techniques,
that add yet another layer of processing. If you want code that
rockets along, for a video game for example, you will want to
know mechanisms for speeding things up. OOP may make the
coding easier, but it may be going against a fundamental reason
why we are using assembly language. Let me post this as a
thought for now.
Make file Oh yes, the WlNASMOO.MAK file has a couple of minor
changes from before, so here is the listing:
# NOTE this Make file has been modified for Borland C++,
# to be used with TASM and TLINK, however I’m still using
# Microsoft’s NMAKE, as Borland’s MAKE has some strange
# quirks . . . though the version supplied with TASM ~3.0
# claims to have improved compatibility with NMAKE . . .
# this I h a v e n ’t y e t t r i e d .
# To r u n t h i s f i l e : NMAKE WINASMOO.MAK
fn = winasmoo
all:$ (fn) .exe
I76 Windows Assembly Language h Systems Programming
:::hyze ( a r b i t r a r y ) e q u a t e s c o u l d h a v e b e e n i n a n i n c l u d e
#de;ine.&M QUIT 200
#define IDMIABOUT 201
winasmoo MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM ltQuitll I IDM_QUIT
MENUITEM "About . . .VI , IDM- ABOUT
Program Design I77
END
END
ICON-1 ICON winasmoo.ico
NAME WINASMOO
DESCRIPTION 'Demo 00 asm program'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS exportwndproc
Multiple What I would like to point out in particular here are the
instances specifications for the data segment. PRELOAD means that it
loads when the program is first loaded. MOVEABLE means that
it can be moved by WINDOWS. MULTIPLE means that every
instance will have its own copy of the data segment. The latter
point is important if you want the program to support multiple
instances. I have designed the code to support multiple instances
with the same ease that it supports multiple windows within the
same instance, but this only works if each instance has its own
complete copy of the data/stack/heap. Note that all instances will
use the same code segment, which is no problem at all.
This works because code cannot be changed. Even though you
can keep data in the code segment, and I have done so in the
skeleton program, you cannot change it. Windows sets the
attribute of code segments such that they cannot be written to, and
your program will crash if you try. Most interestingly, though,
there is a way around this, because Windows has a function that
gives you a DS selector for a code segment (see Chapters 10, 11
and 12).
SMALL Note that my OOP code is designed for the SMALL model. The
model major limiting factor is the pervasive use of NEAR pointers. It
would probably be easier to design a completely different Include
file for other memory models. It should be easy to upgrade to
32-bit code though.
V.ftual
TASM ~3.0 encourages the classical implementation of objects, in
MeHod which the pointers to procedures (Virtual Method Table, VMT) are
Table
178 Windows Assembly Language h Systems Programming
not stored physically with the data of each object instance, but
somewhere else (which is why they invented the TABLE that I
have misused). There are arguments for and against this. Any one
class can have one VMT, and instances could all access a single
instance of the VMT. This would be efficient in terms of memory
but would not allow individual overrides by each object instance.
As mentioned earlier, I decided on an approach that allows easy
conversion to non-OOP assemblers, is conceptually simple, and
offers some flexibility advantages that the VMT doesn’t.
Improving Make0 has been presented in this chapter in a simple, uncluttered
Make0 form, as has the rest of the code. The .INC tile can be massaged in
various ways to do more. For example, make0 can be made to
handle normal child windows with only minor modifications.
Thus the same WINDOW class could be used for parent and child
windows. The alternative would be to create another class, called,
say, CHILD, just like I did for CONTROL. The product is
evolving all the time, and you may find some interesting new stuff
on the Companion Disk or my Web site.
Postamble
You can have a lot of fun playing with these tools. You may think
of improvements - let me know.
7
PC Hardware
Preamble
This could be an enormous chapter. I’m an electronic engineer, so
the hardware is my forte, and I could keep writing for some time.
However, the publisher only agreed to a book of around 400 pages,
and I’m already pushing it!
Very few assembly language books delve deeply into the
hardware, and certainly no Windows books do. Well, many
Windows programming books do cover, more or less, the CPU
architecture and memory management, as I have done in Chapter
1. For systems programming, it is very helpful if you understand
something about the hardware beyond the CPU, i.e., the other
chips on the motherboard and plug-in cards, how they work
together, and how to utilise them.
CPU Bus
Look at any block-diagram of a computer system, and you are
likely to see more than one distinct bus shown. In a nutshell, the
bus carries the address and data, and the bus that is directly
connected to the CPU, or processor, is called the CPU, system, or
processor bus.
The other possible buses perform the same basic task, i.e., carry
address and data, but they are optimised for some specific purpose,
179
180 Windows Assembly Language & Systems Programming
Control Bus
To understand the problem introduced above of how the bus
performs access to two different kinds of chips - memory and I/O
- it is necessary to have a closer look at the control bus. First,
look at Figure 7.1. Also look at Figure 7.2.
Machine For a memory access, say, to read the next instruction, the CPU
cycle goes through what is called a machine cycle, which simply means
it reads or writes memory. There is also such a thing as a “null
cycle”, in which the CPU is doing something within itself for that
clock-period.
When the CPU wants to access the memory, it puts an address
onto the address bus at the beginning of the cycle, then it puts ALE
low to let the rest of the system know there is a valid address.
Depending upon whether the CPU wants to do a read or write
operation, it pulses MEMR* or MEMW* low. In the case of a
PC Hardware 181
memory read it would send MEMR* low, which tells the memory
chips that they are supposed to send data to the CPU.
The memory responds by putting the data on the data bus, and the
CPU reads what is on the data bus near the end of the cycle - the
exact moment when the CPU reads the data bus is when MEMR*
goes high.
Figure 7.1: CPU bus showing some of the control signals.
Address bus
CPU and (20 bits for 8088/6)
24 bits for 80286
associated
r 32 bits for 80386 1
chips
Data bus
(8 bits for 8088)
16 bits for 8086 & 80286)
I32 bits for 80386)
Control bus
INTA Interrupt Acknowledge)
IOR* (f/O Read)
I I IOW* (I/O Write)
MEMR* (Memorv Read)
/ MEMW* ‘(Memory Writhe)
These are control signals ALE (Address Latch Enable)
from the CPU (& maybe
via decodin /buffering
associated c?li s). There
are also signa Ps going the
ESTE’
“*” appended to some
signal names indicates they
other way. are “low active”.
Address Decoder
Basically, a memory chip has a data bus, an address bus, chip
select input(s), and read/write control input(s). This example
RAM (Random Access Memory) chip has an active-low chip
select line coming from an address decoder.
This decoder detects the presence on the address bus of the
appropriate addresses for this particular memory chip - this chip
is being addressed, it “selects” the memory chip.
Note that the address decoder itself has a CS* (chip select) input
- ALE is connected to this. It ensures that the address decoder
only operates when there is a valid address on the address bus.
Assuming that the RAM is addressed correctly, the CPU tells it via
MEMR* and MEMW* which way the data is to go.
Notice that only Al7 to A19 go to the address decoder - this is an
example circuit only, and specific circuits may differ from this, but
generally it is only necessary for some of the address lines to go to
the decoder. This is because the memory chip resides at a range of
addresses - the lower order address bits go directly to the chip to
select a particular memory byte.
Get the idea? The higher address lines select the chip, while the
lower lines select a particular location on that chip.
PC Hardware I83
There are three address lines into the decoder in this example, Al 7
to A19. Say that the decoder is designed to detect an input of 101
binary:
BIT: 19 18 17 16 15 14 13 12 11 10 9 8 7 . . . 0
1 010 0 0 0 0 0 0 0 0 0 . ..o
1011111111111...1
This means that the RAM chip occupies address range AOOOOh to
BFFFFh, and the size of the RAM would have to be 2”17 = 128K
bytes.
I/O Ports
If you peek back at the diagram of the control bus for the CPU
(Figure 7.1), you will see that there are a couple of lines called
IOR* and IOW*. These are for reading and writing I/O ports.
Unlike some CPUs, such as the 6800 family, that do not
distinguish between memory and I/O operations, the Intel 86
family have special instructions and special control lines for I/O.
Figure 7.4 is a typical I/O circuit. Notice its similarity to the
memory interface shown in Figure 7.3. A major difference is that
*
IOR* and IOW* go to it, instead of MEMR* and MEMW*.
Figure 7.4: Interface, CPU to I/O port.
‘1
ALE Detects the address
range of the RAM
A0 -- A19 kuuress chi , so has the
address bus decoder higiJler order
I address bits as
IOR*
IOW”
data bus
ProgrammableMore special chips are used for the interface between the buses
Peripheral and the external world. By external I also mean the keyboard, disk
InterAace drive, etc. Notice that the I/O chip in Figure 7.4 is labelled “PPI”.
This is the name given to a chip used in early-model PCs. PPI
means Programmable Peripheral Interface, and it is a simple
general purpose I/O chip, with three external &bit ports, as shown.
The functionality of the original PPI is still in the latest PCs - it
is just contained within a larger chip. We refer to big chips as
VLSI technology (Very Large Scale Integration).
Notice that the PPI in Figure 7.4 has only two address lines going
directly to it. That is because it only has four ports, or registers.
Three of them are ports A, B, and C, and the fourth is a
configuration port.
I/O Instructions
Although the address bus is used to select I/O ports, only A0 to
Al5 are used, so the address range is only 64K. With the I/O
instructions, data is always via the AX register. The I/O port
address must be placed in DX before executing the I/O instruction
if the address is over 256.
Examples:
Keyboard Interface
This section talks a little bit about interrupts in general, since
interrupts are tied in with how the keyboard interfaces to the
computer. I have introduced interrupts on page 33, and in further
depth on page 250.
Hardware Refer to the circuit of Figure 7.5. The keyboard scancode is routed
description to port A on the PPI chip, when PB7 = 0. The address of port A is
60h, port B is 61h, port C is 62h, etc. The keyboard also generates
an interrupt to the 8259 Interrupt Controller chip, causing INT-9.
With AT-class PCs, including most 386, 486, and Pentium PCs,
we can still visualise the operation as following this pattern. There
are two microcontrollers, an 8031 on the actual keyboard, and an
8042 on the motherboard. The latter implements the functionality
of the original PPI with some changes. For example, port C (62h)
PC Hardware 185
has completely fallen by the wayside. The 8042 has itself been
consumed into larger VLSI chips.
Figure 7.5: Keyboard interface.
Programmable ‘_
- IRQ1 5
CPU ,IRQ I n t e r r u p t
IRQl
Controller (PIC)
0
VLSI chip
SOJl
Kscan
Bus +
Keyboard
\
8042 microcontroller on
older AT-class PCs.
Testing f o r There are a whole lot of commands that you can send to port-64h.
the XT Of course, this presumes that you are not using an IBM-XT PC. I f
model your software is to run on AT-class machines only (including
MCA, EISA, PCI), then you may have to state that fact with the
documentation, and/or your software could perform a simple test.
For example, the AAh command to port-64h is a self-test, and if
the keyboard controller passes the self-test, it will return the value
55h in port-60h. The XT would not respond to this at all. Of
course, what you read from port-60h in an XT could accidentally
(though very unlikely) be scancode 55h.
Some of these commands result in data returned via port-60h, but,
as noted above, you must read port-64h, in a loop, testing bit-O.
Further Further details, such as the commands that port-60h can send to
references the 803 1, are to be found in The Undocumented PC by Frank Van
Gilluwe, Addison-Wesley, 1994.
For further details on keyboard interrupt handling, refer to Chapter
10.
PC Expansion Buses
If you look under the lid of a PC, the plug-in cards are most
obvious. These may include video, printer, serial communication,
and disk adaptor.
188 Windows Assembly Language & Systems Programming
Some PCs will have some of these on the motherboard rather than
as plug-in cards.
The socket into which these boards plug is basically an extention
of the CPU bus, with address, data, and control lines, but usually it
is in a somewhat modified form.
Some expansion bus standards have become history, such as
MCA, VESA local bus, and EISA, so I won’t mention them
further. The ancient ISA (Industry Standard Architecture) standard
is remaining popular and is on just about all new PCs. New PCs
usually have another bus for high speed known as the PCI
(Peripheral Connect Interface) local bus.
on
motherboard
640K
JO000
Plugs inL5
expansion bus.
;oooo
1M
I
PC1
PC1 bridge cards
I PCI-bus
I
PC1 bridge ISA
cards
I ISA-bus
PC/ bridge The PC1 bridge is a chip, and although it is not obvious from the
figure, there are different kinds of chips for different bridges, such
as between CPU-PC1 and PCI-ISA. Also, the PC1 plug-in cards
themselves will have a PC1 chip. One great advantage of having a
special bridge chip between buses is that they allow address
translation, so that a memory or I/O address on the CPU bus will
be a different address on the PC1 bus. In fact, the.bridge chip is
highly programmable and has its own configuration memory that,
most importantly, is independent of the main memory and I/O
map.
Canfiguration With PC systems, the standardized method of accessing the
memory configuration memory of a PC1 chip is by two reserved 32-bit I/O
ports, OCF8h and OCFCh. The former is used for addressing a
location in configuration memory and the latter for reading/writing
it.
The former, OCF8h, is called CONFIG_ADDRESS, and the latter,
OCFCh, is called CONFIG_DATA.
It is important to know that these two ports can allow you to
access the configuration memory on any of the PC1 interface chips
192 Windows Assembly Language & Systems Programming
(on any adaptor card). The 32-bit data that you write to
CONFIG_ADDRESS, is formatted as in Figure 7.8.
Figure 7.8: CONFIG_ADDRESS write format.
31 24 23 16 15 11 10 87 20
0 Bus# Device Function Register
& I
\ Enable CONFIG_DATA Translation Type 1
Protected The normal BIOS that we have considered so far in this book is
mode PC/ designed, at least originally, for an 8088 CPU, which only runs in
BIOS Real mode. In a nutshell, Real mode uses the now-familiar
segment:offset form of addressing, which has a 1M upper limit.
The 286 and 386 CPUs are able to operate in Protected mode,
which uses a different addressing mechanism and is able to
address extended memory beyond 1M (as explained in Chapter 1).
One of the greatest criticisms of Windows 3.x, is its reliance on
DOS and BIOS - to call any of these software interrupts, the
CPU must switch back into Real mode (which takes time).
PC Hardware I93
Real mode. Use MT-1 Ah, AH = B lh, like any other software interrupt
16-bit Protected mode . . . ditto . . .
Virtual-86 mode . . . ditto . . .
32-bit Protected mode BIOS is scanned, for a signature, indicating presence of
32-bit BIOS, and an entry point is located. The services
are accessed by a FAR CALL.
Postamble
I have introduced PC hardware, but so much remains to be
explained. I covered the keyboard interface and expansion bus,
but these are only “samplers”. What about parallel and serial, disk
drive, timer, real-time clock, and other interfaces? Some of these I
do touch on in later chapters, however this book will grow into
something enormous if I try to cover everything.
I could cover these in the next edition though. Let me know if you
really like the idea.
Choice of keyboard interface and expansion bus serve as case
studies, so that you can see how the principles earlier in the
chapter are applied.
7
8
BIOS, DOS, &
Windows Low-Level
Services
Preamble
What’s This chapter introduces the services available to the Windows
in t h i s programmer, but from a viewpoint that you would expect of a
book on assembly language. I have covered two major aspects:
the DOS services and the Windows low-level services.
This chapter gives an overview, and the next chapter provides
practical code.
We haven’t been so far away from the operating system in earlier
chapters, but now is the time to delve in further.
DOS/BiOS In this chapter I have particularly been concerned about the
INTs relationship between DOS and Windows. We have a new
operating system running on top of DOS, with the CPU in
Protected mode - how much of the old DOS can we still use?
Then there is the related issue of how DOS itself has been changed
to handle the new CPUs and operating conditions. What are these
195
196 Windows Assembly Language & Systems Programming
DPMI First, I will fit DPMI into its place in the overall scheme of things
overview (the meaning of life and all that), before getting into a look at the
standard BIOS and DOS services:
“DPMI enables DOS applications to access the extended
memory of PC architecture computers while maintaining
system protection. It also defines a new interface, via
software interrupt 31h, that Protected mode applications
use to do such things as allocate memory, modify
descriptors, and call Real mode software (using
segment:offset addressing and running within the 1M
limit).”
198 Windows Assembly Language & Systems Programming
’ Much of the material from the Developer’s Nofes has found its way into the latest SDK for
Windows version 3.1. This consists of about 12 books. DOS and DPMI notes are to be found
in Microsoft Windows Programmer’s Reference, Volume I: Overview, the first of four volumes.
This is now on CD-ROM supplied with the SDK, though in many cases Microsoft will sell
printed versions.
BIOS/DOS/Windows Services 199
If you think that the above two quotations are contradictory, join
the club. What’s it to be: 0.9 or l.O? I received a clarification
from Microsoft that Windows 3.0 and 3.1 (and now 95) only
support DPMI 0.9. Their reply to me also had another interesting
comment:
‘I... Standard mode understands how to allocate memory
from a DPMI provider . . . Enhanced mode does not.”
When to There are Windows functions that overlap DPMI services, but
use DPM/ most of the latter are undocumented, and in the light of the above
services comments from Microsoft, we are left between a “rock and a hard
place”. Andrew Schulman, PC Magazine, Jan. 28, 1992, page 323,
puts it this way:
“You’re stuck with using either DPMI INT 31h functions
. . . which Intel documents but Microsoft doesn’t sanction
. . . or Windows KERNEL functions, which Microsoft
doesn’t document. What a choice!”
Windows95 Most of my code has also been tested under Windows 95 and
works. However, this statement is true of 16-bit applications
running in Windows 95 - native 32-bit applications are
somewhat more restricted. There are work-arounds. For example,
many of the low-level API functions are available as 32-bit
versions, but are not supported by the import library (during
linking), nor are they documented. However, we can still use them
(refer page 235).
One problem is that you can’t just call 16-bit functions such as the
16-bit API functions from 32-bit code. Most of the interrupt
routines also assume that the caller is 16-bit code.
This is a direct quotation from the Developer’s Notes. The term “Real mode” in this publication
is also taken to cover virtual-86 mode.
202 Windows Assembly Language & Systems Programming
INT-2Fh Extensions
Display Apart from the DPMI extensions to INT-2Fh provided as part of
driver the DPMI, Windows also provides other extensions.
services Functions 4000h to 4007h are for use with the display driver.
Note that conceptually there are two different display drivers: the
virtual driver (VDD) at the Windows end and the actual driver that
does the dirty work:
AX = 4000h
A program calls this function to determine how much work
the Windows Virtual Display Driver (VDD) must do when it
switches Windows between the foreground and the
background. It also tells the VDD to allow the program to
have direct access to the video hardware registers.
AX = 4001h
Tells the display driver to save the current video state.
AX = 4002h
Tells the display driver to restore the video hardware state
saved by 400 1 h.
AX = 4003h
Tells Windows Virtual Display Driver (VDD) that execution
is currently in a critical section. This function appears to
make the VDD pause until 4004h releases it.
AX = 4004h
Tells VDD that critical section is finished.
AX = 4005h
Similar to function 400 1 h
AX = 4006h
Similar to function 4002h.
AX = 4007h
A program tells the VDD that it has finished accessing the
hardware registers. This is the complement of 4000h.
I think it unlikely that you will need to call 4000h and 4007h,
unless you are designing your own display driver. 4000h is
designed for use by a display driver to communicate with the VDD
prior to the VDD calling 4005h. This sequence terminates when
206 Windows Assembly Language & Systems Programming
the VDD calls 4006h to let the display driver restore its state and
continue functioning. After this the display driver calls 4007h to
tell the VDD that it’s all over. Chapter 9 has an example of usage,
and Appendix D is an INT-2Fh reference.
Real and Another group of INT-2Fh functions has to do with
vi&al driver communication between DOS Real mode drivers and virtual mode
interaction drivers (VxDs).
I have noted below that some of the functions have been used in
example programs, along with more detail on their usage. Also
Chapter 11 discusses these functions in more depth.
Note that only functions 1605h and 1606h are available in
Windows Standard mode.
Note also that these services, although designed for
communication between device drivers, are quite general and can
be used by any program. Chapters 11 and 14 develop a TSR that
uses them.
. AX = 1600h
Obtains the version number of 386 Enhanced mode Windows.
. Ax = 1605h
Windows calls this to tell DOS drivers that it is loading
(example of usage Chapter 14).
. AX = 1606h
Windows calls this to tell DOS drivers that it is quitting
(example of usage Chapter 14).
. AX = 1607h
A virtual driver calls a DOS driver.
. AX = 1608h
Windows calls this to tell DOS drivers that it has completed
initialisation.
. AX = 1609h
Windows calls this to tell DOS drivers it is exiting Enhanced
mode.
. AX = 1680h
Yields the current virtual machine’s time slice.
. AX = 1681h
A driver calls this to tell Windows not to switch virtual
machines.
. AX = 1682h
This is the complement of 1681h
. AX = 1683h
Returns the ID of the currently executing virtual machine.
BIOS/DOS/Windows Services 207
AX = 1684h
Allows a DOS mode driver to request services from a virtual
driver.
AX = 1685h
Allows a driver to switch virtual machines (examples of
usage, Chapters 11 and 12).
IN%4Bh: Windows drivers also make use of INT-4Bh for virtual Dire&
DMA Memory Access (DMA), and I refer you to page 264.
services Again, these are extensions that are not part of DOS but are
provided by Windows. They are designed especially for the
difficulty of using DMA controllers with a CPU running in
Protected mode.
Windows Functions
There are some Windows functions that perform in a similar
manner to DPMI services, so there is overlap.
Overview What I have done in this section is not give exhaustive definitions
of the functions, as that would require a complete book on its own.
You need a lot of reference material for Windows development,
and where appropriate I have given the reference.
There are two broad groups of functions: those available in USER,
KERNEL, or GDI DLLs and those available within device drivers
and other DLLs.
In the latter case, you will find functions of the same name. For
example, enable0 and disable0 exist in all drivers. Obviously
your program must be able to select which one it is to call, and that
I have shown in the next chapter.
The Windows functions are all in files known as Dynamic Link
Libraries (DLLs), and are loaded at run-time.
1 ow-/eve/ . ALLOCCSTODSALIAS
lISE..GDJ/ Not described in the SDK. Allocates a new data selector that
KERMEL aliases an existing code selector. *
function . ALLOCDSTOCSALIAS
summary Accepts a data segment selector and returns a code segment
selector that can be used to execute code in a data segment. *
l ALLOCSELECTOR
l DISABLEOEMLAYER
Not documented in the SDK. Turns off Windows display,
keyboard, and mouse and changes to text mode; restores DOS
I/O. Complement is ENABLEOEMLAYER. *
l DOS3CALL
. GETKEYBOABDSTATE
Copies an array that contains the state of keyboard keys.
. GETKEYNAMETEXT
Retrieves a sting containing the name of a key from a list
maintained by the keyboard driver.
. GETKEYSTATE
Retrieves the state of a virtual key.
. GETNUMTASKS
Returns the number of tasks currently executing in the system.
*
. GETSELECTOBBASE
Not described in SDK 3.0. Gets the linear base address of the
specified selector from the descriptor table. *
. GETSELECTORLIMIT
Not described in SDK 3.0. Gets the limit of the specified
selector from the descriptor table. *
. GETSYSTEMDEBUGSTATE
Only available in Windows 3.1. Returns system status
information to a debugger. *
. GETWINDEBUGINFO
Available in Windows 3.1 only. Queries current system
debugging information. *
0 GLOBALDOSALLOC
Recommended by Microsoft instead of equivalent DPMI
service. Allocates a block below IM linear address space.
Returns both a selector and segment. Complement is
GLOBALDOSFREE. *
. GLOBALFIX
Prevents the memory block from moving in linear memory.
You would use this in Standard mode to lock a block in place.
Complement is GLOBALFREE. $ *WOWGetVDMPointerFix
. GLOBALHANDLE
Supplies a selector and returns a handle to the memory block.
. GLOBALPAGELOCK
Prevents a segment from being paged out or moved. You can
use this in Enhanced mode to guarantee a segment will be
present at all times. Locks the segment at a physical address.
Complement is GLOBALPAGEUNLOCK. *VirtualLock()
l GLOBALWIBE
I’m not sure what this one does - it seems to be similar to
GLOBALFIX. $ *
BIOS/DOS/Windows Services 21 I
l HARDWARE EVENT
Available in Windows 3.1 only. Places a hardware-related
message into the system queue. *
l HMEMCOPY
Not described in SDK 3.0. Sets the linear base address of the
specified selector in the descriptor table. *
l SETSELECTOIUIMIT
SETWINDOWSHOOK
Installs a system and/or application filter function.
Applications specific to Windows 3.1 should use
SETWINDOWSHOOKEX. *
THROW
Restores the execution environment to the specified values.
Complement is CATCH. *
UNHOOKWINDOWSHOOK
Removes a Windows filter function from a filter function
chain. Complement is SETWINDOWSHOOK. Applications
specific Windows 3.1 should use
UNHOOKW~OWSHOOKEX. *Supported but
recommend UnHookWindowsHookEx().
SETWINDEBUGINFO
Only available with Windows 3.1. Sets current system
debugging information. *
WINEXEC
Executes a separate application. *Supported but recommend
CreateProcess()
YIELD
Halts the current task and starts any waiting task. *
. MEMORYREAD
Reads memory from an arbitrary global heap object.
. MEMORYWRITE
Writes memory to an arbitrary global heap object.
. MODULEFINDHANDLE
Retrieves information about a module.
. MODULEFINDNAME
Retrieves information about a module.
. MODULEFIRST
Retrieves information about the first module.
. MODULENEXT
Retrieves information about the next module.
. NOTIFYREGISTER
Installs a notification callback function.
. NOTIFYUNREGISTER
Removes a notification callback function.
. STACKTRACECSIPFIRST
Retrieves information about a stack frame.
. STACKTRACEFIRST
Retrieves information about the first stack frame.
. STACKTRACENEXT
Retrieves information about the next stack frame.
. SYSTEMHEAPINFO
Retrieves information about the USER heap.
. TASKFINDHANDLE
Retrieves information about a task.
. TASKFIRST
Retrieves information about the first task in the task queue.
. TASKGETCSIP
Returns the next CS:IP value of a task.
. TASKNEXT
Retrieves information about the next task in the task queue.
. TASKSETCSIP
Sets the CS:IP of a sleeping task.
0 TASKSWITCH
Terminates an application.
. TIMERCOUNT
CreateToolhelp32Snapshot
Takes a snapshot of the Win32 processes, heaps, modules, and
threads used by the Win32 processes.
. Heap32First
Retrieves information about the first block of a heap that has
been allocated by a Win32 process.
. Heap32ListFirst
Retrieves information about the first heap that has been
allocated by a specified Win32 process.
. Heap32ListNext
Retrieves information about the next heap that has been
allocated by a Win32 process.
Heap32Next
Retrieves information about the next block of a heap that has
been allocated by a Win32 process.
. Module32First
Retrieves information about the first module associated with a
Win32 process.
. Module32Next
Retrieves information about the next module associated with a
Win32 process or thread.
. Process32First
Retrieves information about the first Win32 process
encountered in a system snapshot.
. Process32Next
Retrieves information about the next Win32 process recorded
in a system snapshot.
Thread32First
Retrieves information about the first thread of any Win32
process encountered in a system snapshot.
. Thread32Next
Retrieves information about the next thread of any Win32
process encountered in the system memory snapshot.
. Toolhelp32ReadProcessMemory
Copies memory allocated to another process into an
application-supplied buffer.
Driver What follows are functions available inside the drivers. They
functions cannot be called directly as you would a normal Windows
function, but require an extra step. See the practical code in the
Chapter 9. Also, they are not documented in the SDK.
216 Windows Assembly Language & Systems Programming
Mouse . INITIALIZATION
driver Initialises the mouse device driver.
functions . DISABLE
Suspends interrupt callbacks from the mouse device.
. ENABLE
Enables calls to the Windows mouse event procedure.
. INQUIRE
Gets information about the mouse characteristics.
. MOUSEGETINTVECT
Gets the interrupt level used by the mouse hardware.
. WEP
Performs cleanup when the Windows session ends.
COMM . CCLBBBK
driver Clears the Comm line break state.
functions . CEVT
Returns the address of the Comm event word.
. CEVTGET
Clears and gets specified events in the Comm event word.
. CEXTFCN
Performs an extended driver function.
. CFLUSH
Discards the contents of a receive or transmit buffer.
. COMMWIUTESTIUNG
Transmits a block of data over the serial port.
. CSETBBK
Initiates a Comm line break state.
. CTX
Transmits a single byte before all others in the transmit queue.
. GETDCB
Returns the address of the DCB structure for the specified
port.
. INICOM
Initializes the specified Comm port.
. BEACTIVATEOPENCOMMPORTS
Re-enables Comm ports disabled by
SUSPENDOPENCOMMPORTS().
. READCOMMSTIUNG
Reads bytes from the Comm receive buffer.
. BECCOM
Reads a byte from the Comm receive buffer.
BIOS/DOS/Windows Services 217
SETCOM
Sets the device configuration and state.
SETQUE
Specifies the memory input/output buffers.
SNDCOM
Places a character in the transmit queue.
STACOM
Gets the hardware and buffer status of the specified port.
SUSPENDOPENCOMMPORTS
Temporarily disables all Comm port activity.
TRMCOM
Closes the specified port.
DISABLE
Suspends interrupt callbacks and removes hooks.
ENABLE
Enables calls to the Windows keyboard event procedure.
ENABLEKBSYSREQ
Enables or disables SysRq key processing.
GETBIOSKEYPROC
Gets the address of the BIOS interrupt service routine.
INQUIRE
Returns the keyboard configuration structure that contains the
DBCS ranges.
NEWTABLE
Loads the keyboard translation tables.
System . CREATESYSTEMTIMER
driver Allocates a system timer to be used by a device driver.
functions . GETSYSTEMMSECCOUNT
Gets the amount of elapsed time.
. INQUIRESYSTEM
Gets various system configuration parameters.
KILLSYSTEMTIMER
Frees a timer to be used by a device driver.
Undocumented Many functions available in Windows 3.0, 3.1, and 95 are not
functions described in the SDKs, nor anywhere for that matter. These are
“undocumented” functions, which means that Microsoft doesn’t
want us to know about them (see also page 235).
Reference There are various chaps who have dug up the dirt, and written
books books.
Undocumented Windows: A Programmer’s Guide to the Reserved
Microsoft Windows API Functions by A. Schulman, D. Maxey,
and M. Pietrek, Addison Wesley, USA, 1992.
Unauthorized Windows 95 by Andrew Schulman, IDG Books,
USA, 1994.
Windows 95 Systems Programming Secrets by Matt Pietrek, IDG
Books, USA, 1995.
BIOS/DOS/Windows Services 2 I9
Thunking
The mismatch between 16- and 32-bit code is a major headache.
Windows internally is also a mixture, including Windows 95
(especially Windows 95!). I have shown in this chapter that some
functions available to 16-bit applications are not available to
32-bit applications and vice versa. This is because each has its
own set of API DLLs (see page 235).
However, we can “mix and match” - with caution of course.
The process of translating between 32- and 16-bit code is known
thunking as thnking, and Windows 95 provides two mechanisms: Generic
thunking and Flat thunking.
Flat Flat thunking is specific to Windows 95 - it is not portable to
tbunking Windows NT. It allows 16- to 32-bit and 32- to 16-bit function
calls, so it is most flexible.
Generic thunking works on both Windows 95 and NT but only
allows a 16-bit application to call 32-bit functions, not the other
way around.
Universal Universal thunking is for Windows 3.1 applications to access the
thunking win32s API.
Reference A good explanation of Flat thunking is to found in Inside Windows
soufces 95 by Adrian King, Microsoft Press, USA, 1994. Also look at the
Win95 SDK CD-ROM.
Generic thunking is also explained in the Win95 SDK CD-ROM,
in file DOC\MISC\GENTHUNK.TXT. The following information
is based on this and other documents on the SDK CD-ROM.
Another excellent document that covers both Generic and Flat
thunking and has detailed descriptions of all the Generic API
functions is Programmer’s Guide to Microsoft Windows 9.5 by the
Microsoft Windows Development Team, Microsoft Press, USA,
1995.
Generic Thunking
Windows on Win32 (WOW) presents 16-bit APIs that allow you
to load the Win32 DLL, get the address of the DLL routine, call
the routine (passing it up to thirty-two 32-bit arguments), convert
16:16 (WOW) addresses to 0:32 addresses (useful if you need to
build a 32-bit structure that contains pointers and pass a pointer to
it), and free the Win32 DLL.
Function I hope you can read C code. I have taken these examples straight
prototypes from the SDK documentation.
220 Windows Assembly Language & Systems Programming
Note that although these functions are called in 16-bit code, they
need to be provided with 32-bit handles, and they return 32-bit
handles. Do not forget that the 32-bit functions must be called
with the STDCALL convention.
Ca//PrOC3’wflCallProc32W() follows the PASCAL calling convention. It is
designed to take a variable number of arguments, a Proc address, a
mask, and the number of parameters. The mask is used to specific
which arguments should be treated as being passed by value and
which parameters should be translated from 16: 16 pointers to Flat
pointers. Note that the low-order bit of the mask represents the last
parameter, the next lowest bit represents the next to the last
parameter, and so forth.
Code I didn’t really want to put actual code into this chapter, but a little
examples sample of Generic thunking is useful while I’m on the topic.
Assume that the Win32 DLL is named DLL32. First you need to
load the 32-bit library:
Then you need to get the address of the 32-bit function, in this
case MyPrint():
CallProc32W((DWORD)TestString,(DWORD) hWndlOxffffOOOO,hProc,2,2);
FreeLibrary32Wl ghLib );
IMPORTS
kernel.LoadLibraryEx32W
kernel.FreeLibrary32W
kernel.GetProcAddress32W
kernel.CallProc32W
T h e u s e o f t h e 16-bit v e r s i o n s LOADLIBRARY a n d
GETPROCADDRESSO is described in Chapter 9. The principles
apply to the 32-bit versions also.
WOW Very briefly, here they are:
funcfions . CallProQZW, CallProcEx32W
called from Used by 16-bit code to call an entry point function in a 32-bit
f6-bit code DLL.
. FreeLibrary32W
Allows 16-bit code to free a 32-bit thunk DLL that it had
previously loaded by using the LoadLibraryEx32WO function.
. GetProcAddress32W
Allows 16-bit code to retrieve a value that corresponds to a
32-bit routine.
. GetVDMPointer32W
Allows 16-bit code to translate a 16-bit FAR pointer into a
32-bit FLAT pointer for use by a 32-bit DLL.
. LoadLibraryEx32W
Allows 16-bit code to load a 32-bit DLL.
WOW These are a different group of WOW functions:
functions
called from l WOWCallbackl6, WOWCaIlbackl6Ex
32-bit code Used in 32-bit code called from 16-bit code to call back to the
16-bit side.
l WOWGetVDMPointer
Converts a 16:16 address to the equivalent FLAT address.
222 Windows Assembly Language & Systems Programming
. WOWGetVDMPointerFix
Converts a 16: 16 address to the equivalent FLAT address.
Unlike the WOWGetVDMPointer() function, this calls the
GlobalFix function before returning.
. WOWGetVDMPointerUnfix
Uses the GlobalUntix() function to unfix the pointer returned
by WOWGetVDMPointerFix().
. WOWGlobaIAllocl6
Thunks to the 16-bit version, GLOBALALLOCO.
. WOWGlobaIAllocLockl6
Combines the functionality of WOWGlobalAllocl6() and
WOWGlobalLockl6().
. WOWGlobalFreel6
Thunks to the 16-bit version of GlobalFree().
. WOWGlobalLockl6
Thunks to the 16-bit GlobalLock()
. WOWGlobalLockSizel6
Combines the functionality of WOWGlobalLock16() and
GlobalSize().
. WOWGlobalUnlockl6
Thunks to 16-bit GlobalUnlock().
. WOWGlobaIUnlockPreel6
Combines the functionality of WOWGlobalUnlockl6() and
WOWGlobalFree16().
. WOWHandlel6
Maps a 32-bit handle to a 16-bit handle.
0 WOWHandle
Maps a 16-bit handle to a 32-bit handle.
Threads
A Windows process is an application, be it a Windows application
or a DOS Virtual Machine (VM). However, 32-bit Windows 95
applications can also have multiple threads of execution, and the
thread becomes the basic unit that can be scheduled by the
operating system.
With Windows 3.x, the System VM (running all the Windows
applications) and the DOS VMs (each running a DOS application)
are preemptively scheduled, while the Windows applications
themselves are cooperatively scheduled (i.e., amongst themselves).
Windows 95 adds to this picture with 32-bit applications that have
one or more threads that can be preemptively scheduled. Because
scheduling is thread-based, the term process is awkward - the
16-bit applications become one thread and each DOS VM is one
thread.
Here are all the Windows 95 thread- and process-related functions:
AttachThreadInput, CommandLineToArgvW, CreateProcess,
CreateRemoteThread, CreateThread, ExitProcess,
ExitThread, FreeEnvironmentStrings, GetCommandLine,
GetCurrentProcess, GetCurrentProcessId,
GetCurrentThread, GetCurrentThreadId,
GetEnvironmentStrings, GetEnvironmentVariable,
GetExitCodeProcess, GetExitCodeThread, GetPriorityClass,
GetProcessAfiinityMask, GetProcessShutdownParameters,
GetProcessTimes, GetProcessVersion,
GetProcessWorkingSetSize, GetStartupInfo,
GetThreadPriority, GetThreadTimes, OpenProcess,
ResumeThread, SetEnvironmentVariable, SetPriorityClass,
SetProcessShutdownParameters, SetThreadAffmityMask,
224 Windows Assembly Language & Systems Programming
Postamble
This chapter is notable more for what it doesn’t say than what it
does! Various functions, interrupts, and concepts introduced here
are developed in the chapters ahead.
9
Direct Hardware
Access
Preamble
Wbaf 3 This chapter contains practical code to “get behind the scenes”.
in this The first part of the chapter focuses on the issues of direct reading
chapter from and writing to memory, particularly video-RAM, and the
second part focuses on I/O.
I have shown the use of DPMI NT-3 lh services and of the
TNT-2Fh extensions, plus the use of low-level Windows functions.
I have pointed out overlap between the two where it occurs.
You will be amazed to learn that it is possible to have an
application running in a window, yet the application can write
directly to the video hardware, at breathtaking speed, without all
of the Windows rigmarole. This is the kind of practical code
developed in this chapter.
You will also learn about I/O aspects, such as use of the IN and
OUT instructions.
Mostly I view the material of this chapter as educational. It pokes
around doing fun things that may be viewed as “hacking”. It may
be that you will never use some of the less orthodox material in
professional applications, but what will be formed now is a good
solid foundation of understanding of the fundamentals.
225
226 Windows Assembly Language h Systems Programming
Initialisation
Is DPMI First 1’11 address the question of initialisation. Since your program
available? is running in Protected mode, alongside other programs, you can’t
simply go reading and writing all over memory and I/O. There
have to be rules to prevent contention. Initialisation is code that
clears the way for you to get directly at the hardware.
The code below is a good way to start. For the moment, don’t
worry about the red tape of PROC - ENDP; etc. You’ll put it
together later.
Before you can use DPMI services, you need to check out a few
things:
.DATA
dpmiflag DB 0 ;=l dpmi running ok
dpmiversion DW 0 I*ah=major, al=minor.
386modeflag DB 0 ;=l 386 dpmi type.
realmodeintsflag DB 0 ;=l Real mode interr.
virtualmemflag DB 0 ;=l virt. mem support.
cputype DB 0 ;=2,3,4 286,386,486
; . . .
.CODE
mov ax,1686h *test if dpmi running.
I
int 2Fh
or ax,ax
jnz nodpmi
mov dpmiflag,l ;set flag, dpmi ok.
mov ax,0400h ;get dpmi version.
int 31h
mov dpmiversion,ax
mov al,bl
and al,01 ;bit-0 =l if 386 dpmi
mov 386modeflag,al
mov al,bl
shr al,1
and al,01 ;bit-1 =l if not virtual86 int handling
mov realmodeintsflag,al
shr b1,2
and b1,Ol ;bit-2 =l if virtual mem. supported.
mov virtualmemflag,bl
mov cputype,cl ;c1=2,3,4 if 286, 386, or 486.
Note that most of the INT-2Fh services work in Real and Protected
mode, with or without DPMI, but INT-31h will only work with
DPMI and in Protected mode.
Which The next service is INT-3 lb/AX = 0400h, which returns the DPMI
version of version number, plus other status information. Since version 1.0
DPMI is of DPMI has more features than version 0.9, this test is necessary
fun fling? if you want to use the extra features of ~1.0. Note that Windows
3.x and 95 only support DPMI ~0.9 (refer to page 198). I have
written the code in this chapter for ~0.9. So, again, this test is not
really required.
I have stored all of the flags as static data, to be used as needed by
the rest of the program.
Addressing Segments
Direct Assuming that DPMI is up and running, which it should be under
access to Windows, you are ready to start doing interesting things. One of
memory your objectives is to access real memory directly. That is, you
and video hunger for the good old days when you could write directly to the
-RAM video-RAM, not via some tortuous method using GETDC(),
TEXTOUTO, and RELEASEDCO, with a hundred messages to
worry about. You want control (slobber, slobber), and you want
speed!
You may even be totally retrograde and want to run your Windows
program with the screen in text mode (horror!). Remember good
old text mode? It was good enough for most things, and even did
quite a good job at graphics, using the IBM graphics character set.
The MDA (Monochrome Display Adaptor) only has a 4K
video-RAM, with the result that screen redrawing is instantaneous.
Forget about delays with text mode.
Text-mode This text mode topic raises an interesting side issue. There are a
Windows lot of other “retros” like me out there, and there is even a special
applications product available for those who want to write Windows programs
but don’t want to give up the advantages of Real mode and of
text-mode video. The product is called Mewel,’ and it is a
complete library for writing Windows applications that run
without Windows, under DOS, in Real mode (or Protected mode),
with the screen in text mode (or graphics mode). It’s a lovely
product and works well. The only major deficiency is that there is
no multitasking. Mewel even allows source code to specify
mov ax,BOOOselector
mov es,ax
mov bx,O
mov es: [bx] ,‘lxff
Linear This code will write the ASCII character “x” directly to the
address video-RAM at address BOOO:OOOO. From the theory in Chapter 1,
that will be a physical and/or linear address of OOOBOOOOh. I made
the complete lineati/physical address up to 32 bits, since that’s
’ Intel’s DPMI specification places a few caveats upon the 0002 function.
The descriptor’s limit will be set to 64K.
Multiple calls to this function with the same segment address will return the same selector.
Descriptors created by this function can never be modified or freed. For this reason, the
function should be used sparingly. Clients which need to examine various Real mode addresses
using the same selector should allocate a descriptor with INT-3 Ih/AX = OOOOh and change the
base address in the descriptor as necessary, using function 0007h.
* Note that all WinApps share a single LDT. The system VM maintains one each LDT, GDT,
and IDT.
’ Notice above that I used the word “linear” address. This is explained in Chapter 1. Basically, in
Direct Hardware Access 229
what the 386 actually puts out. In the case of the 286 it will only
be 24 bits.
Look carefully at that above code fragment. See that I treated the
selector as the exact equivalent of the segment (paragraph) address
it represents. Behind the scenes, the CPU will use the selector
value in ES to lookup the LDT and get the physical address.
This service is wonderful, because it gives you direct access to all
memory below 1M. It also gives you enormous potential to “stuff
up” the system.
*.. now, Pardon the crudeness, but “there’s more than one way to skin a
the same cat”. Ditto with DPMI services and low-level Windows functions.
thing, but If the two overlap, which ones do you use? Interestingly, some of
using a the Windows functions internally call the DPMI services!
Windows In the above case, the Windows function equivalent is - well,
function there are choices here, just as there are some different avenues
with DPMI. SETSELECTORBASE()’ is appropriate: it creates a
new entry in the LDT and will set the “base address” (linear
address) field in the descriptor. You provide a selector value as a
parameter to this function, the descriptor of which is used as the
model for the new descriptor. So, if you want to treat the new
memory block as data, use DS as the model. The SDK 3.1
documentation does not explain any of these vital details.
Note that SETSELECTORBASE() is available in Windows 3.0 but
was undocumented until Windows 3.1 made it official.
Direct Video
You don’t want to “stuff up” the system, of course, so you need to
take whatever precautions are necessary. If your Windows
application is going to do something drastic, like change the screen
to text mode, then obviously it will not be outputting to a pretty
Saving and little Windows box. The Windows screen will no longer be there.
This may be ok for what we want, but if our program is to work
restoring the
with other Windows programs, and with Windows itself, then our
Windows program must be able to restore the original screen.
screen:
INT-2Fb/ Microsoft does have a very suitable service: INT-2Fh/AX =
4001h.
AX=400~/2h __ __ ~-.-It is summarised in Appendix D, described in Microsoft’s
Windows Standard mode (286 mode), the linear and physical addresses are one and the same.
In Enhanced (386) mode, an address goes through an extra paging step, so the physical address
is renamed as the linear address, and is no longer the actual physical address.
’ SETSELECTORBASE() is passed two parameters: the selector (16 bits), and the starting linear
address (32 bits). It returns a new selector value in AX, or AX = 0 if an error.
230 Windows Assembly Language & Systems Programming
The next obvious step is to change the video mode. There are
some interesting thoughts here. Won’t Windows and other
applications expect to be able to output to the screen also?
Yes they will, but always remember that Windows’ 16-bit task
management is non-preemptive. This means that once Windows
has passed control to your program, you can keep control for as
long as you like. You can lock out other applications and do
whatever you want.’
Normally, when Windows sends a message to the callback
function, your callback processes it, then has nothing more to do
so returns to Windows. If control stays in the callback, for
’ There are two other services, AX = 4005h and 4006h, that are similar to 4000h and 4001h,
respectively. The description of 4005h is “The Windows VDD calls this function to tell the
display driver to save the video hardware state.” And for 4006h “The Windows VDD calls this
function to tell the display driver to restore the video hardware state that was saved by the last
call to function 4005h” (Ririting Windows Device Drivers, page 78). The 4000h and 4007h
services are used in conjunction with 4005h and 4006h. 4000h gives the display driver direct
access to the video hardware registers, while 4007h disables register access and tells the VDD
that the display driver has finished accessing the video hardware.
2 Though the DPMI host does perform preemptive time slicing between VMs (see Chapter 11).
Even this can be disabled by a DPMI service.
Direct Hardware Access 231
Restore Video
For now, I’ll just say that you can save the screen upon entry to the
callback, and you can write directly to the screen. But before going
back to Windows, you must restore things to how they were. This
means that whatever you displayed in text mode (or whatever) will
be lost, unless you save it in a buffer.
This is some video cleanup code prior to returning to Windows:
’ The Windows library file supplied by your software vendor, such as LIBW.LIB (Microsoft) or
IMPORT.LIB (Borland), provide your program with access to the DLL functions. Whether or
not you can access REPAINTSCREEN directly from your program is determined by the
inclusion of the linkage information in these link files. You will find that later versions may
provide the linkage, even to undocumented functions; however, I have shown how to do it the
hard way here, in case you have to do it for any functions, including those in other DLLs.
232 Windows Assembly Language & Systems Programming
int 10h
; . . .
mov ax,BOOOselector
mov es,ax
mov bx,O
mm:
mov cx,OFFFFh
mmm: nop
loop mmm ;delay
mov BYTE PTR es: [bxl,"XW
mov BYTE PTR es: [bx+11,10001111b ;attribute
inc bx
inc bx
cmp bx,1998 ;put 1000 X's on screen.
jbe mm
; . . . ;Undocumented RESURRECTION0
mov ah,00 ;will change back to graphics
mov al,winvideomode ;mode and restore Windows
int 10h ;display driver...
mov ax,4002h call RESURRECTION PASCAL\
int 2Fh ,hdc,O,O,O,O,O,O
; . . . call RELEASEDC PASCAL,hwnd\
call lprepaintscreen \ ,hdc
PASCAL ; (Thanks to Undocumented Windows
;... * for showing me how many params
nodpmi: i to feed RESURRECTION0 !I
nomodule:
ret
directvideo ENDP
There are a host of things I can say about this routine. I have
itemized major points below.
Call REPAINTSCREEN
Calling a I mentioned earlier that I have used REPAINTSCREEN() as an
function in example to show how to get at a DLL function at run-time, which
a DLL is one option if linkage information is not provided in the library
file. The standard technique is to call GETMODULEHANDLEO
to get a handle for USER.EXE (a file is a module in Windows
parlance, but the file name can be different from the module
name), then call GETPROCADDRESSO to get the FAR address
of the function within that module. If you would like to see
another example of accessing a function in this way, Microsoft’s
Programmerk Reference, Volume 2: Functions, provided with the
SDK 3.1 (and available separately), gives an example of
LOADLIBRARY ( i n s t e a d o f GETMODULEHANDLEO),
GETPROCADDRESSO, and FREELIBRARYO to access a
function in TOOLHELP.DLL.
Thity-two-bit applications are somewhat more constrained - see
notes on page 235.
Direct Hardware Access 235
Ordinal Coordinates
EXE USER.EXE is a Dynamic Link Library and is a standard feature of
header Windows. It has a heap of useful functions, and the question
extraction naturally arises: what are the other functions in USER.EXE?
utilities Furthermore, where did I get that ordinal coordinate of 275?
Each function in USER.EXE, or any DLL for that matter, can be
referenced by a unique ordinal coordinate. You can find out all of
the functions in a DLL and their ordinal coordinates, by use of a
utility program supplied with Microsoft C/C++, called
EXEHDR.EXE (or TDUMP.EXE from Borland C++). Since you
may not have access to this utility, I have listed the output of
EXEHDR.EXE for many of the Windows DLLs and drivers (see
the Companion Disk). The file on the disk has a comprehensive
alphabetical list of functions, with a short description, where it is
documented, what DLL it belongs to, and its ordinal coordinate.
Each device driver has built-in functions that can be called also.
32-bit Thirty-two-bit applications are a problem. Apart from crashing if
applications you try to use a software interrupt, the low-level undocumented
(and many previously documented) functions are not readily
available. Matt Pietrek, arguably the Windows systems
programming guru of gurus, covers this problem in Dirty Little
Secrets about Windows 95, on-line at:
http://ftp.uni-mannheim.de/info/OReilly/windows/win95.update
/dirty.html
Reference In this Web page, Matt is actually quoting from his book Windows
book 95 Systems Programming Secrets, IDG Books, USA, 1995:
“In Unauthorized Windows 9.5,’ Andrew Schulman made
extensive use of undocumented functions in
KERNEL32.DLL. Although there obviously weren’t
header files for these functions, the functions appeared in
the import library for KERNEL32.DLL. Calling these
functions was as simple as providing a prototype and
linking with KERNEL32.LIB.
In subsequent builds of Windows 95 after Andrew’s book
came out, these functions disappeared from the import
library for KERNEL32.DLL. (Surprise! Surprise!) At the
same time, these function names disappeared from the
exported names of KERNEL32.DLL. These
undocumented functions were still exported, however.
The difference is that they were exported by ordinal only.
Suppressing The problem with the above (overwriting the current Windows
redrawing screen) is that when you exit your callback and return control to
Windows, the screen will be redrawn. Of course you may not want
to return to Windows until you have finished running your game
or whatever, but suppose you do. A return to Windows without
redrawing the screen can be done by not executing INT-3 1hlAX =
4002h, or REPAINTSCREENO.
238 Windows Assembly Language & Systems Programming
These can be executed later, when the time is right, or not at all.
Message Input
Dumping One thing to bear in mind is that although Windows 3.x is
the queue non-preemptive, the device drivers are still working
asynchronously, as indeed is the case in Windows 95. Key presses
and mouse activity can still generate messages, which will be
placed into your application’s queue.
So, your program may have saved the Windows video state and
gone to mode 7, or whatever, and done its thing. When finished,
and after the clean-up of restoring the video state and maybe
calling REPAINTSCREEN(), your program would normally
continue on in the normal fashion - if execution is within a
callback, control will continue on and return to Windows, and a
message waiting in the queue will then be sent to the message loop
in WinMain .
Experimenting
writhlg One thing that you might like to do as an exercise is modify my
pixels to code so that the mode is not changed. Leave it as it was, and
the video change the segment address from BOOOh to AOOOh, then you will
-RAM have a selector to the graphics video buffer. The EGA and VGA
physical video buffers are at segment address AOOOh. If the
program sends ASCII “X9 to the screen, you won’t see “X”s,
because the screen is in graphics mode.
ASCII codes are only appropriate when the screen is in text mode.
In graphics mode you write pixels to the buffer, and to know how
to do that you need a good EGA/VGA programming book. In this
simple example, the “X5 will produce an interesting pattern on
the screen. You might like to experiment with commenting out
the INT-3 lh/4002h and the REPAINTSCREENO.
. DATA
window1 WINDOW ( szclassname="DPMI",sztitlename= \
"DPMI DEMO" paint=wlpaint, create=wlcreate, command= \
wlcommand, 'createstylehi= WS OVERLAPPEDWINDOW+ \
WS CLIPCHILDREN, char=wlchar,-sziconname="icon l", \
y_Eoord= lO,timer= wltimer,destroy=wldestroy }-
control1 CONTROL { \
szclassname=l~BUTTON1~,sztitlename="OK",\
x_coord=20,y_coord=4O,wwidth=3O,wheight=20, \
hmenu=IDOK,createstylehi=WS CHILD+WS_VISIBLE,\
createstylelo=BS_PUSHBUTTON-}
CODE
kickstart:
lea si,windowl ;addr of window object.
call [si] .make PASCAL,si ;make the window.
lea si,controll
call [sil .make PASCAL,si ;make child window
ret
;............................
wlpaint PROC PASCAL
LOCAL hdc:WORD
LOCAL paintstructa:PAINTSTRUCT
lea di,paintstructa
call BEGINPAINT PASCAL,[si].hwnd, ss,di
mov hdc,ax
call SELECTOBJECT PASCAL,ax, [si] .hfont
call TEXTOUT PASCAL,hdc,10,20, cs,OFFSET outstring,
call ENDPAINT PASCAL,[si].hwnd, ss,di
ret
outstring DB I'Click button for direct video It
wlpaintENDP
;....................._.......................
wlcreate:
call GETSTOCKOBJECT PASCAL,OEM - FIXED - FONT
mov [si].hfont,ax
ret
;.............................................
wlcommand:
cmp WORD PTR [si] .lparam,O
;lo half=0 if a menu selection.
jne notmenu
ret
notmenu:
cmp [si] .wparam,IDOK ;button child window selected?
;note that lo-word of lparam has handle of control
;window, hi-word of lparam has notification code.
jne notbutton
Direct Hardware Access 241
Print Manager
Ghosting If you try the program, one “feature” that you will observe is that
“ghosting” can occur in windows moved underneath, so an
improvement would be to hook all WM_MOVE messages and
append a WM_PAINT message. The problem is that whenever
you move (drag) a window on the screen, Windows simply
performs a shift of the window image, and does not tell the
window callback to repaint the window. Thus, shifting a window
under our “special” window can result in the underlying window
picking up a ghost of our special window. I have toyed with
various ideas for telling the window to repaint its client area, but
did not put any code into this example, for the sake of simplicity.
Anyway, I see this more as a learning exercise, and I don’t think
you should put these techniques into that professional office
business suite you’re working on! On the other hand, you never
know when low-level knowledge like this will come in handy.
244 Windows Assembly Language & Systems Programming
I/O Ports
DOS assembly language programmers will be accustomed to using
the IN and OUT instructions to talk with I/O ports.
Of course, with DOS it was very straightforward. Execute “OUT
28h,AL” to send a byte of data from the AL register to port
(address) 28h, and it happens immediately, without question.
E/LAGS However, with the CPU running in Protected mode, there is some
registef extra rigmarole. Since more than one task can be executing, there
has to be a mechanism to prevent contention. First, look at the
flags register inside the CPU (Figure 9.2):
Figure 9.2: EFLAGS register.
FLAGS REGISTER
1 VM RF # NT IOPL OF DF IF TF SF ZF # AF # PF # CF
17 16 14 13/2 11 10 9 8 7 6 4 2 0
# = reserved
CF = carry flag FLAGS = 16-bit
PF = parity flag register,
AF = aux. carry flag 86/286
ZF = zero flag EFLAGS = 32-bit,
SF = sign flag 386
TF = trap flag
IF = interrupt enable
DF = direction flaa
OF = overflow
IOPL = I / O privilege level72861386 only
NT = nested tank flag
RF = resume flag -7386 only
VM = virtual-86 mode
// lxJ.J i
1
else
{
// Make Flat-Thunk Win32 DLL to communicate with Win16 DLL;
// Make some front end Win32 app using Flat-Thunk Win32 DLL ;
1
1
else
{
// Spend $$$$$ on Win95 MSDN, DDK, Nu-Mega's llSoft-IceU';
// Write a VxD for Win95;
// Write your Win32 (or Winl6) front end;
Example Now for some example code. What I have here is a simple routine
program; to emit a tone from the loudspeaker. Nothing startling, but it is
Joudspeaker significant because it is done by programming the I/O circuitry
control directly. The PC has three hardware timers, the first dedicated to
producing an interrupt every 55 milliseconds (ms): the INT-8
hardware interrupt. The second generates continuous pulses that
are used by the dynamic RAM refresh circuitry. The third is
general purpose, and is most often used to produce tones on the
loudspeaker, since its output is connected physically to the
loudspeaker.’
Windows A little note before I launch into the real-time section - Windows
timers does have “software” timers that can be programmed to time out at
regular periods, just like the hardware timers discussed above. See
the usage of SETTIMER on pages 239+. However, upon time
out they send a message to the application over the standard
message queue, so its arrival time at the application is highly
unpredictable. It is even possible for the timer to time out a few
times, and queue the messages, before the application gets them -
suddenly the application will get three or more timer timeout
messages at once! Hardly useful if you want your application to
be triggered at precise intervals.
As a final thought, Windows has an undocumented function,
CREATESVSTEMTIMER(), that is documented in the DDK,
Daniel Norton’s book (see page 203) and in Undocumented
II5ndow.s (see page 218). It bypasses the message queue and calls
the callback directly. Thus, it is possible to make code execute at
precise intervals (though the callback has the major restriction that
it can only call certain Windows functions, just like an ISR).
Threads Windows 95 has made timers less important, with the introduction
of threads. These introduce an execution overhead though.
Threads are only supported in 32-bit applications, with the Win32
API. Even though a 16-bit application can call 32-bit API
functions (see thunking section in Chapter 8), it can’t use the
248 Windows Assembly Language & Systems Programming
Preamble
Windows So you think 16-bit Windows applications are non-preemptive?
preemptive Think again!
aspects Just about everything you read will tell you that a disadvantage of
Windows 3.x is non-preemption. That is, once control is passed to
an application, Windows cannot regain control until the
application has passed control back, by a RET. One of the touted
advantages of 32-bit applications under Windows 95 is
preemption.
Actually, whether it be Windows 1 .O or 95, interrupt-driven device
drivers, including keyboard input, must always be working in the
background. When a key is pressed, a hardware interrupt is
generated, which invokes the keyboard device driver.
The immediate response to a key press is preemption, nothing else,
and contrary to common knowledge, Windows 3.x applications
can make use of similar mechanisms.
Also, the DPMI host maintains preemptive time-sliced switching
between VMs on Windows 3.x and 95.
Preemption Just as device drivers can be interrupt driven, so too can your own
by interrupts application to provide predictable real-time response.
It is not all peaches and cream however.
The chapter starts with code for software interrupts, because it is
the easier case. The interrupt mechanism is particularly useful for
signalling and passing data between Windows programs.
The chapter then progresses to hardware interrupts, with example
code.
TSRs
What originally started me thinking about this topic was a problem
some colleagues of mine at Edith Cowan University were having.
They wanted a Windows 3.0 application to sit in memory, like a
TSR (Terminate and Stay Resident) program, logging external
real-time events, while Windows was running other applications.
In other words, they were asking for preemption. Windows, they
concluded, was not suitable, so they chose OS/2.
Hooking an After some experimentation, I discovered that it is very simple to
interrupt create a Windows application that behaves just like a DOS TSR
and hook an interrupt vector, yet be operating in Protected mode
and be in every respect a normal Windows application.
“Hooking an interrupt vector” means to change the entry in the
interrupt table (refer back to page 33) to point to the new TSR. In
DOS it was very common for a TSR to hook INT-I6h - the code
that follows also hooks this vector, but note that Windows doesn’t
use INT-16h for keyboard input, so it doesn’t matter what damage
we do to this vector!
Hooking a Vector
What I have done in the first part of this chapter is put together a
program that hooks INT-16h. The new INT-16h service routine
uses the music code from page 246, so there is audible feedback of
it executing.
Once the service routine is installed, INT-16h can then be
executed from anywhere, including another program, and the
service routine will be invoked.
TSR The program can be any basic skeleton to which you patch the
installation following code. The “install” portion could be wherever you want
routine it; in WinMain(), in kickstart: (00 program), or in the callback.
You could start the program up as an icon (or invisible) and
immediately execute the install code.
This is what the install code would have to be:
.DATA
offsetint DW 0 ;old int-vector
selectorint DW 0 I /
.CODE
i n s t a l l PROC PASCAL NEAR *no params
I
USES ax,bx,cx,dx,si,di,es
mov a1,16h ;get vector in idt
mov ah,35h
int 21h ;returns vector in es:bx
mov offsetint,bx ;save old vector.
mov selectorint,es I /
mov dx,OFFSET runtime ;new vector.
push ds ;save ds.
push cs
POP ds ;new vector in ds:dx
mov a1,16h ;int to be hooked.
mov ah,25h ;set vector
int 21h
$z': ds ;restore ds.
install ENDP
252 Windows Assembly Language & Systems Programming
INT-Zlb/AH Some interesting points arise from this code. INT-21h/AH = 35h
= 35.25h or 25h are functions for getting the interrupt vector and for setting
it. Look back to the special note on how these work with
Windows on page 200.
/DT vs /VT It is most important to know that they work on the IDT, not the
IVT. When the CPU is running in Protected mode, an interrupt
will cause the CPU to look in the IDT to find the selector:offset of
the interrupt routine.
In the code above, I have not hooked the old INT-16h routine in
the IVT. I have only hooked INT-16h in the IDT, which for
normal Windows programs isn’t used.
The rest Notice in the above code that I saved the old vector. This is in
of the case I want to call it or jump to it, possibly from within the new
WinApp interrupt service routine.
Having done that, all that remains is to go into the usual message
loop, as per a normal program, which returns control to Windows.
There is one little complication with this - since the vector has
been hooked, don’t close the application, because executing that
interrupt from some other application will cause the CPU to try to
execute a service routine that is no longer there. In fact, it will
crash rather rudely. It is possible to create a window for the
program but keep it invisible,’ to prevent accidental closure, or
unhook it before closing. See an example of unhooking on page
260.
fixed vs Is that all there is to it? Yes. Even the old .DEF file can be used,
moveable and you can have MOVEABLE and DISCARDABLE segments.
segments It is not necessary for the CODE and DATA segment statements in
the .DEF tile to have FIXED qualifiers. FIXED forces Windows
to leave the segments at a fixed place in memory, rather than
moving them around as it normally does. You would think, from
the way TSRs are designed under DOS, that a resident interrupt
handler should be FIXED, but not with Windows.
If the operating system determines that the segment referenced via
the IDT is not actually in memory, then it will get it back, and
update the descriptor. If you want, modify the .DEF file as
follows:
’ Note that it possible to have an application without any window at all. Since all messages
usually are posted to a window, this requires special consideration. For example,
POSTAPPMESSAGE() will post a message to an application without a window and leave the
message’s hWnd parameter NULL.
Real-Time Events 253
Related Specifying FIXED is not a bottleneck itself, from the point of view
issues of memory management, as some books will have you believe: I
discuss this issue on page 324.
Perhaps I am getting ahead of myself, since I haven’t even
discussed the service routine itself. The above points do tie-in
with the service routine however. We may want to store writable
data in the code segment of the ISR, which will cause problems.
Also, hardware interrupts are a special case. In practise you may
have to do more than just specify FIXED: I have gone into this in
more detail on page 323. Also some relevant Windows functions
(GLOBALHANDLE, GLOBALFIX, and GLOBALPAGELOCK)
were introduced on page 2 10.
While I’m referring you all over the place for extra information, I
might as well do it some more. The above install routine works
for hardware or software interrupts, that is, any entries in the IDT
(or IVT if the CPU is running in Real mode). There are DPMI
equivalents: see the Appendices. What about exceptions? These
have to be treated as a special case: see page 258.
’ Implementation as a DLL does have some advantages, however. If a DLL segment is declared
FIXED in the .DEF tile, it loads below I M, and is also guaranteed to be in contiguous memory.
These features allow the DLL to have Real mode code as well as Protected mode code. The
DLL runs at privilege level 3 (level I in Windows 3.0), so I/O still causes an exception.
* MAKEPROCINSTANCEO can be used to attach prolog code that binds data to code, though I
have not used it here, for certain reasons. See further notes in the Companion Disk.
254 Windows Assembly Language & Systems Programming
points to the same code segment. This will allow you to write to
the code segment.
Windows has various functions for segment manipulation, though
many of them were unofficial until 3.1 was released. Of most
interest is CHANGESELECTORO, which is official for both 3.0
and 3.1 (see page 208). ALLOCCSTODSALIAS() is an unofficial
alternative. With Win95 they all go back to being unofficial.
There is another interesting, related function introduced with
TOOLHELP.DLL, and so is backwards compatible with 3.0:
MEMORYWRITEO. This will copy a block of memory from one
segment to another, regardless of their attributes. Thus it will
write to a code segment.
Actually, it is quite easy to get data segment addressability from
within an ISR, but I’ll leave that one for now.
Data alias Before I show you the actual ISR, I’ll provide a little bit of extra
to code setup code using the abovementioned DPMI service:
.CODE
-I've put this data in the code segment . . .
&selector DW 0 ;data alias to code seg
musicflag DB 0 ;turn music on/off
kltime:
pusha I-save all regs.
push ds
push es
mov es,cs:dsselector ;get alias
push es ;can also set ds to alias.
POP ds ;(so seg.override isn't needed to access data).
Real- Time Events 2.55
*enable interrupts.
-(ST1 and reentrancy issues discussed on page 323).
cm; musicflag, -musicflag i s used as a counter, for
t jb jumpout iturninq the tone on or off on each
musicflag,O *lcth entry to the routine.
I
j jmp turnoff -
jumpout3:
inc musicflag
cmp musicflag,lO
jne jumpout
timeron
mov al, Ob6h ;turn on the hardware timer.
out 43h,al
mov bx,07cSh I-frequency 600Hz.
mov al,bl
out 42h,al
mov al,bh
out 42h,al
in a1,61h
or al,03
out 61h,al
jmp SHORT jumpout
turnoff
in a1,61h ;turn off the hardware timer
and al,Ofch
out 61h,al
jumpout2:
POP es
POP ds I*restore all regs.
popa
iret
Testing
Stick this service routine somewhere in your program, then
assemble and link as per normal. To test it, you will have to
modify some other program, by inserting an “INT 16h” instruction
into it. Perhaps you could put this instruction into the other
program’s WM_CHAR case, so whenever you press a key and the
other program’s window is active, the program will execute “INT
16h”, which will call the service routine.
Don’t be confused here. A key press has nothing to do with
INT-16h under Windows, at least as far as normal code is
concerned. I have just arbitrarily suggested that you use the
WM_CHAR message as a convenient means of invoking the
service routine.
Having modified another program, start both it and the “TSR”
program. With the “other” program active, try key presses, at least
ten, and you should be able to toggle the tone on and off.
256 Windows Assembly Language & Systems Programming
On/u one If you know much about LDTs and GDTs, you might be puzzled
LDTand as to how the above code can work. The classical theory states
IDT that each application has its own LDT (see Chapter 1), so
modifying the TSR’s LDT has nothing to do with any other
application’s LDT. Not so with Windows! As is explained in
more detail in the next chapter, all WinApps share the same LDT.
Ditto for the IDT.
The IDT is a very grey area. It is another case of Microsoft hiding
the truth. The classical model for the IDT would be that there is
only one, but Windows does maintain copies, as far as I know, for
each VM. So maybe there is just one “main” IDT that interrupts
“go to” but the interrupt handler references the copy in the current
VM. This is a very very grey area, but you can get by with just
thinking that there is only one IDT. Certainly, as all WinApps are
in the same VM, this assumption is safe.
Hardware Interrupts
You will notice that my example code earlier in this chapter dealt
only with software interrupts. Hardware interrupts can work, but
there are some complications. The problems are associated with
how interrupts are mapped and the difference in treatment of
interrupts in Protected and Real modes.
The issue is very complicated and it behooves us to start with the
handling of hardware interrupts from the point of view of the XT;
that is, with an 8088 or 8086 CPU.
XT Hardware Interrupts
IRQ O-7 The PC model XTs are based upon the 8086 CPU and have a
hardware interrupt controller chip that allows eight devices to
interrupt the CPU. That is, the chip has eight inputs, labelled
IRQO to IRQ7 and one output labelled IRQ (Interrupt ReQuest)
that feeds into the maskable interrupt pin of the CPU.
A flag named IF (Interrupt Flag) enables this IRQ input with the
ST1 instruction or disables it with the CL1 instruction (see page
33).
The interrupt controller chip can be, and is, programmed to map
IRQO to IRQ7 to any group of eight entries in the IVT or IDT
Real-Time Events 257
(look ahead to page 268 for the relationship between the IVT and
IDT) (see page 185 for an introduction to the interrupt controller
chip).
The XT maps IRQO through 7 to entries 8 to OFh in the IVT. Thus
if you were to access these by software interrupt, you would
execute “INT 8” to “INT OFh”.
AT Hardware Interrupts
The IBM model AT, based upon the 80286 CPU, introduced more
hardware interrupts, by cascading a second interrupt controller
chip, as shown in Figure 10.1.
Figure 10.1: AT hardware interrupts.
REAL-MODE
HARDWARE
INTERRUPTS
Breakpoint
Overflow
:
to IRQ Print screen
pin
on CPU : reserved
reserved
t
ii 55mSec timer
9 Keyboard
A Cascade for IRQS-
B Usually COM2
C Usually COMl
Usually LPT2
: Diskette
F Usually LPTl
1RQB-i u 10 Video I/O
11 Equipment check
70 Realtime clock
72 genera1
73 general
~ general
controller chip ;4 1 Coprocessor
added to the AT. 76 Hard drive
77 g e n e r a l
258 Windows Assembly Language & Systems Programming
Windows Windows (and OS/2) map IRQO through Fh elsewhere in the IDT,
remapping at INT-5Oh to -5Fh. Obviously, these entries would point to the
of vectors same routines as before, but even so, there is room here for
trouble.
You might deduce from this that if you wanted to hook the
original INT-8, you should instead hook INT-5Oh. This is valid,
but only to a certain extent. Windows can be in Protected or V86
mode at the time of interrupt, and in the latter case we have to go
back to the IVT in the V86 virtual machine currently active.’
Therefore, we (may) actually have to hook two (or more) vectors.
Headache!
’ I don’t want to be misleading here. In Windows Standard mode when a DOS program is
running, the CPU will be in the one-and-only Real mode and interrupts vector via the
one-and-only IVT. However, in Enhanced mode with a DOS VM active, it is still a Protected
mode, and hence, hardware interrupts still go to the IDT in the current VM (note the emphasis
on current). Because the DOS VM is supposed to behave in all respects like an XT-model PC,
the interrupt will eventually come down to the IVT.
ReaLTimeEvents 259
’ Put Windows in Standard mode by typing “WIN /S” when loading it. That is what it will be
anyway if the CPU is a 286, or a 386 with insufficient RAM (usually less than 2M).
260 WindowsAssemblyLanguage& SystemsProgramming
mov ax,msgtype
cmp ax,WM_CREATE
je xcreate
=mp ax,WM_DESTROY
je xdestroy
cmp ax,WM_USER
je xuser
. . . etc . . .
;h&e' is the handling of the WM CREATE case . . .
xcreate: -*atever you want, plus . . .
call installint ihooks the vector.
jmp xexit
;here is the handling of the WM- USER case . . .
xuser:
push ax
push dx
mov ah,2 ;write char to scrn
mov dl,Oi' ;beep
int 21h
POP dx
POP ax
jmp xexit
xdestroy:
call POSTQUITMESSAGE PASCAL,0
;unhook the int-5lh...
push dx
push ds
mov dx,offsetint ;this is the old INT-51 vector
mov ds,selectorint /
mov ax,2551h ;before quitting, we are restoring it.
int 21h
POP ds
POP dx
jmp xexit
Iewha tever else you want here . . .
. . 1
xexit:
sub ax,ax ;returns 0 in DX:AX.
cwd ;return a 32-bit (long) value).
ret
DPMICALLBACK ENDP
.DATA
descrbuffer DB 8 DUP(0)
offsetint DW 0 ;old int. vector
selectorint DW 0 , /
.CODE
dsselector DW 0 ;data alias to code seg
hwndcs DW 0 ;save window handle for use in isr
installint PROC I-no params
pusha
push es
push ds
;will create alias in ldt of current task...
mov ax,OOOAh ;create alias data descr. for code seg.
push cs
;selector to be aliased
::': ::h ;returns ax
push ax
POP es
mov ax,hwnd
mov es:hwndcs,ax ;handle of window
mov ax,es
mov es:dsselector,ax ;alias
I-now to get the old INT-5lh vector, and save it . . .
mov a1,51h ;get vector in idt
mov ah,35h ;-->ES:BX
int 2Ih
push es
POP ax
mov offsetint,bx ;save the old vector.
mov selectorint,ax ; /
mov dx,OFFSET runtime ;get the new vector
push cs ; /
;new vector in ds:dx
‘Op :: 51h
mov
mov ah:25h ;set vector
int 21h
’ pop ds ;restore ds.
push ds *save it again
;let's hook int60, to use as 'old vector...
mov dx,offsetint
262 Windows Assembly Language & Systems Programming
mov ax,selectorint
mov ds,ax
mov ax,2560h
int 2Ih
;installation now finished . . . .
POP ds
POP es
popa
ret
I can put the interrupt service routine in the same procedure as the
install code, if I wish, but before listing it, I want to comment on
the above code.
To be able to get at data in the service routine (I’ll call it an ISR
from now on), I had to create a data alias; that is, a data selector
that points to the code segment. This enables me to write to the
code segment.
Into the code segment I saved the handle (hwnd) of the
application’s window. The reason for this is that within the ISR I
called POSTMESSAGEO, which needs the handle as a parameter.
Caliiflg the You can see that I hooked the vector and saved the old vector, but
o/dhand/er I also put the old vector into INT-60h. That is, I hooked INT-60h
so that it now points to the Windows keyboard handler. This is
convenient, because from within the ISR I wanted to be able to
call the old ISR, for proper handling of the keyboard input.
Note that there are other ways of doing this, such as by use of a
CALL instruction.
Now for the ISR:
runtime :
i n t 60h ;call the old INT-5lh
pusha I'save all registers.
push ds
push es
push ss
mov ax,cs:hwndcs ;get window handle
; call POSTMESSAGE PASCAL,ax,WM_USER,O, 0,O
;no, will do it this way, as PASCAL qualifier very
iinefficient . . .
push
push ;; USER
push 0-
push 0
push 0
call POSTMESSAGE ;put message on queue.
POP ss I*now restore and get out.
POP es
POP ds
popa
iret
Real-Time Events 263
installint ENDP
END
See how simple the ISR is! I was able to call the original
keyboard handler for proper handling of the key press/release,
though note that I could have put the "INT 60h" at the end of the
ISR if required.
I accessed "hwndcs", the handle of the window passed as data in
the code segment, and then called POSTMESSAGE().
Note that I did not make use of aliasing in this simple skeleton.
I chose to explicitly push the parameters onto the stack prior to the
CALL, rather than use the PASCAL qualifier - TASM’s
generation of code with the PASCAL qualifier is horribly
inefficient, so I felt better about doing it this way.
Real mode So the earlier example code that I wrote to hook INT-5 lh for
keyboard illustration purposes simply needs to be modified to hook INT-9,
handler and it will work in both Standard and Enhanced modes.
Unfortunately there is is still one complication - DOS.
I keep hoping it will go away - but it won’t. The hardware
interrupt handler developed in this chapter will work with any
number of Windows applications multitasking, but not when a
DOS program is running. In the former case, it doesn’t matter if
the program containing the ISR is iconized and another WinApp
has the active window - still, all key presses will in real time be
routed to the ISR and be posted to the iconized program - and
Windows will call the iconized program’s callback function,
giving it the message, even though it is iconized.
So you’ll always get the beeps when pressing and releasing a key.
However, if you run the “DOS Prompt” program, the beeps will
stop. Upon exiting back to Windows, the beeps will start once
again.
264 Windows Assembly Language & Systems Programming
If you really must have the ISR continuing to function when the
CPU is running a V86 or Real mode program, refer to Chapter 11,
as I decided to make the handling of Real mode a special chapter
all on its own. See also the footnote on page 258.
What the I suppose you do realise by now what the example program does
program -! it beeps the loudspeaker every time you press or release a key.
“does” Because the ISR only posts a message to the main Windows
program, it is what I would class as pseudo-real-time response.
Don’t forget, however, that the ISR shares the same code segment
as the main program, and by way of a data alias, data can be
passed to and fro. Or the actual WinApp data segment can be
readily accessed.
For example, harking back to the problem that my colleagues had
- they wanted to measure an external parameter at precise
intervals and log it for internal analysis. The interrupt mechanism
provided the precise intervals, and the ISR could have read the
parameter from the input port and recorded it, then exited. Simple
enough.
You will find the program on the Companion Disk in USRl .
INT-4Bb INT4Bh provides the extensions to DOS for DMA handling, and
you will find these documented in the above Microsoft reference
- not anywhere else, that I’m aware of.
The services, available from both Windows Standard and
Enhanced modes, are:
l INT-4Bh/AX = 8 103h VDS_LOCK
l INT-4Bh/AX = 8 104h VDS_UNLOCK
l INT4Bh/AX = 8 1 OBh VDS_ENABLE_TRANSLAT.
l INT4Bh/AX = 8 1 OCh VDS_DISABLE_TRANSL.
Preamble
Why hot/re/ The topic of Real mode has already been encountered at various
with Real earlier stages in the book. There is, however, a lot more to the
mode? issue of Real mode.
Windows 3.1 won’t run in Real mode, only Standard or Enhanced,
version 3.0 loads in any of the three, while 95 only loads in
Enhanced mode. “Real mode” in this context means that the
WinApps themselves run in Real mode, which just isn’t practical.
So, we load Windows in Standard or Enhanced mode - why
bother with Real mode?
One need is to run a DOSApp. In the case of Standard mode, the
CPU has to switch back to Real mode, effectively freezing
Windows. However, Enhanced mode will create another VM
(virtual machine) in which to run the DOSApp, and we still say
that the DOSApp is running in Real mode (though it would be
more correct to say virtual-86 mode).
Then there are DOS device drivers and TSRs. Most likely these
will be running in Real mode. And there are the BIOS and DOS
services that we may still want to use.
A lot of code is still being developed to run in a DOS box, maybe
in Protected mode, but still involving transitions between
virtual-86 (“Real mode”) and Protected mode in the DOS VM.
267
268 Windows Assembly Language & Systems Programming
Windows application:
...
INT n
I I I
4
Windows
handler
Original Real
mode routine
(below 1M): J
...
IRET
hterrupts The INT-2lh/AH = 35h retrieves the vector from the IDT. When
reflected an interrupt occurs, the IDT points to a special handler that passes
from IDT to control to the Real mode DOS routine pointed to by the IVT (and
/VT
Real Mode Access 269
Real mode Figure 11.1 relied upon a Windows handler to transfer control to
execution the original Real mode routine, but this only works for the
versus data recognized BIOS and DOS services. Any other interrupt will most
access likely crash.
The question of an interrupt being reflected down to Real mode or
not is a different question from the “typical problem” above, in
which it was necessary to look at a certain offset inside the Real
mode code.
I will not worry too much about the various scenarios that will
require you to access Real mode software; just think for now what
the solution is. I outlined above how to locate a Real mode routine
for data access, but what if you want to call it?
DPMI to the rescue again!
Routine to
call a Real There’s an invaluable service, 0300h, that does everything. Some
mode ISR code will illustrate:
.DATA
regstruc STRUC ;Real mode register data structure
edil DD 0
esil DD 0
ebpl DD 0
resl DD 0
ebxl DD 0
edxl DD 0
ecxl DD 0
eaxl DD 0
flags1 DW 0
es1 DW 0
270 Windows Assembly Language h Systems Programming
dsl DW 0
fsl DW 0
gsl DW 0
ipl DW 0
CSl DW 0
spl DW 0
ssl DW 0
regstruc ENDS
ICODE............................ . . . . . . . . . . . . . . . . . . .
/NT-3fb/ Intel’s DPMI s,pecification does place some caveats upon the
AX= 03001 0300h function.
The CS:IP in the Real mode register data structure is ignored by this function. The appropriate
interrupt handler based upon the value passed in BL will be called.
If the SS:SP fields in the Real mode register data structure are zero, a Real mode stack will be
provided by the DPMI host. Otherwise, the Real mode SS:SP will be set to the specified values
before the interrupt handler is called.
The flags specified in the Real mode register data structure will be pushed on the Real mode
stack’s IRET frame. The interrupt handler will be called with the interrupt and trace flags clear.
Values placed in the segment register positions of the data structure must be valid for Real
mode; i.e., the values must be paragraph addresses and not selectors.
All general register fields in the data structure are DWORDs, so that 32-bit registers can be
passed to Real mode. Note, however, that l6-bit hosts are not required to pass the high word or
32-bit general registers or the FS and GS registers to Real mode.
The target Real mode handler must return with the stack in the same state as when it was called.
When this function returns, the Real mode register data structure will contain the values that
were returned by the Real mode interrupt handler.
Real Mode Access 27
What the What I have done here is called INT-16h/AH = 5, which puts a
above character into the old DOS keyboard buffer. The character has to
program be provided in CX (as scancode:ascii).
“does” All of the register values to be passed to Real mode have to be
placed into an array pointed to by ES:DI.
That’s it. The Real mode routine executes, then returns. To find
out if the character really was placed in the buffer, I then called
INT-16h/AH = 0, which gets a character from the buffer (and will
hang if nothing is in the buffer!). Notice that I called this in the
normal fashion - this will go via the IDT and IVT as per normal.
The previous INT-16hAI-I = 5 would have worked in this way
also, but I have used the DPMI service to show how to call code
that is not necessarily a Standard BIOS or DOS service.
By this DPMI mechanism, you can call any code below 1M with
the CPU running in Real mode - actually, this opens up some
possibilities.
Staying on track for now, I used INT-16h/AH = 0 to get the
character back off the buffer - and the character I chose was 07,
the “beep” character. I sent it to the display, using INT-2lh/AH =
2, supplying the ASCII code in DL.
The “beep” character doesn’t go to the screen, however; it is
treated as a control character (all characters below 32 decimal are)
and in this case causes a beep on the loudspeaker.
Hence, there is immediate feedback that the code has worked.
Calling a The above code works fine, at least for calling a BIOS or DOS
DOSApp service, but if you want to call code or access data in a DOSApp,
there are more complications.
A DOS program (DOSApp) running under Windows would be
running in Real mode in what is sometimes called a “DOS
compatibility box”. Windows in Standard mode can only have one
of these running at any one time, as Standard mode is based upon
the capabilities of the 286 CPU (which cannot just flip between
Real and Protected modes on a per-task basis). Windows in
Enhanced mode is based upon the virtual-86 capability of the 386,
which allows multiple “DOS boxes” or virtual machines.
Virtual Machines
viiual There is a section back on page 29 that introduces the concept of
Real virtual 8086 machines. The 386 can happily multitask just about
mode any number of these virtual machines, although Windows has a
limit of 16. However, it does place a caveat on everything I’ve
written so far about the so-called “Real mode”.
272 Windows Assembly Language & Systems Programming
’ It is this feature that enables Windows Enhanced mode to multitask DOS applications in
Windows, not only full-screen as required by Standard mode.
Real Mode Access 2 73
DOS TSRs
DOS TSR (Terminate and Stay Resident) programs, which also
of TSk include device drivers, are covered in many DOS programming
books. They load like any other program, but only have a short
“install” procedure then exit back to DOS. The exit is via a special
DOS service that leaves the program resident in memory, rather
than freeing up that memory space, as with normal programs.
TSRs usually hook a vector, such as INT-8, -9, or -16h.
For example, by hooking INT-16h or -9 all DOS keyboard input
can be filtered. Usually the TSR passes control to the old vector
after doing whatever it wants.
Once a TSR is loaded and control returns to DOS, you can then
load another program, so even under “single-user” and
“single-tasking” DOS you have two (or more) programs sitting
together in memory. The TSR will be executed, or rather its
“run-time” portion will be executed, whenever the particular
interrupt is called.
The Companion Disk has a useful TSR skeleton that hooks
INT-16h with many of the tricks of the trade incorporated into it,
fully commented for your convenience. Look in DOSTSR.
A TSR sits If you load the TSR from within Windows or at a DOS prompt
inside a VM within Windows, the TSR will be inside a virtual machine. If the
TSR hooks an interrupt vector in the IVT, it will only be hooking
the vector in the virtual machine.
Whenever a DOS virtual machine is created, Windows copies
everything from the actual 1M region into it, or rather, “maps” it
in. The IVT is not the same IVT as the original IVT.
This is the crux of the problem. Perhaps Figure 11.2 will help:
274 Windows Assembly Language & Systems Programming
and Protected
All WinADDs
A TSRloaded By loading the TSR before loading Windows, for every virtual
before machine that Windows creates, it will also “copy” the hooked
Windows vector and the TSR. Thus by this method you ensure that the TSR
appears in is available to all applications.
every VM Note that I put the word “copy” in quotes, as this is not always to
be taken literally. See ahead for clarification (page 343).
Each VMbas Note also something most important: the descriptor tables. The
iis own L D T system VM will have just one of each LDT and IVT. Despite the
fact that one of the fundamental concepts behind the LDT is that
there should be one per task, Windows maintains just one for the
entire VM. This is why obtaining a selector when installing a TSR
Real Mode Access 2 75
Accessing The conclusion here is that accessing Real mode code (via the
Real mode IVT) from a Protected mode WinApp accesses it in the system
code in all VM. If you want to get at code or data of a DOSApp or TSR in
VIM.9 another VM you have to look into mechanisms for going between
VMs - or, if you load a DOS TSR before Windows, it will be
automatically in all VMs and thus its code and data will be global.
Even its hooking of the IVT will be in every IVT.
Thus the DOS TSR is one convenient mechanism for
communication between Protected and Real modes across all VMs
and is developed further in this chapter. Also, a method for
switching VMs is developed.
He does not see any way around this problem except by loading
the DOS TSR before loading Windows. Actually, if you only
want the DOS TSR to load into the system VM, and not
subsequent VMs, you can force this by naming it in a file called
WINSTART.BAT, which Windows looks at to see what has to be
done before loading itself (but after creating the system VM).
Simply put the name of the TSR in it, as per a normal batch file.
4@@?g f.4e When I say “there’s nothing to it”, I’m being a bit flippant. A DOS
IVTand TSR TSR loaded before Windows can have a data area that a Windows
across v~’ program can get at, but there are certain extra considerations.
If the TSR is being copied to each V86 machine as it is created,
won’t each have its own code and data? Therefore, if a Windows
program looks in the IVT to access the DOS TSR, which one will
it see? Will it just see the copy in the system VM?
Yes, the WinApp will only see the IVT in the system VM and
hence the TSR in the system VM, but Microsoft arranged things so
that the subsequent copies of the TSR are not really “copies” as
such - they all map back to the one physical TSR. So there only
appear to be multiple copies of the TSR.’ Thus the TSR is truly
global.
I have elaborated upon this point with a supporting figure on page
343.
There is still another major problem. Yes, the WinApp can get at
the DOS TSR, but what if a DOSApp in a VM, via the TSR (or
whatever method), wants to asynchronously send a message to a
WinApp in the system VM? I talked about signalling between
applications back in Chapter 10, but that was between WinApps.
Getting a DOSApp to signal a WinApp across VMs is a new ball
game.
’ You can verify this by running COMMAND.COM in two different windows. Run the DOS
“MEM” program to see where the DOS TSR is located, then go into DEBUG.COM and dump
the start of the TSR (use the Dump command), then enter a new value somewhere (Enter
command) .,. and you will find the same new value showing up in the other DOS window.
Note that DEBUG is a standard DOS program, and DRDOS also has a program (almost)
equivalent to DEBUG. I do have a modified DEBUG that will run on any version of DOS, but
at this stage I don’t have permission from Microsoft to put it on the Companion Disk. You may
be able to locate a similar modified DEBUG on the Internet. Usage of DEBUG is described in
many DOS programming books.
Real Mode Access 2 77
Reference Walter Oney has solved this particular problem in “Using DPMI to
source Hook Interrupts in Windows 3”, Dr Dobb’s Journal, February
1992, page 16. He does not tackle hardware interrupts; his focus is
purely on the issue of passing a message from a DOSApp to a
WinApp across VMs.
Mechanism A DOS TSR can be made to load into the system VM only, by
for forwarding specifying it in WINSTART.BAT; however, what we want is to
up to a hook an IVT vector that will appear in all VMs. The reason for
WinApp this is that we want a mechanism for a DOSApp in any VM to be
able to find out the address of a “forwarding” routine (in the DOS
TSR) in the system VM.
Did I just say that we want the TSR to be in every VM? It will be,
but the IVT hook’s appearance in every VM is what matters: we
want a DOSApp in another VM to pass control over to the “copy”
of the TSR in the system VM, which can in turn pass control up to
a WinApp. This may seem complicated, but hopefully I can
explain it clearly.
First consider the DOS TSR. It will have to be loaded before
Windows and will have to hook a vector in the IVT:
So there you are, a complete DOS TSR! Note that this particular
one has been written without the “simplitied” directives, which is
no big deal. Actually my own experience has been that it is
difficult to write .COM programs using the simplified directives,
and you are better off sticking with the “long hand” notation
shown above. You can write a TSR using .EXE format and the
simplified segment directives, which I have done for one of the
examples of Chapter 14 (see also directory \TSR2WIN on the
Companion Disk).
hstall Have a close look at what the “install” portion does. It hooks
portion INT-61h in the IVT then exits.
Because this TSR is loaded before Windows, it will be in the
system VM and will hook the vector in the system VM. But it will
also be copied to every VM.
Thus, every time a DOS program is run within Windows Enhanced
mode, the new VM will have that hooked vector.
But what you should note in particular is that INT-61h contains the
address segment:offset of the “forwarder” code for the TSR.
Passing Control to the WinApp
A major problem is created if our code must work for both
Standard and Enhanced modes. With Standard mode, the question
Real Mode Access 279
of VMs doesn’t arise.’ This means that all access to the IVT from
a WinApp is to the actual, original, real, physical, bona fide IVT!
That’s not the problem: in fact that’s good, because there’s no need
to jump VMs. However, Windows itself is in a strange state while
a DOSApp is running. I have elaborated more upon this in
Chapter 12.
Both Enhanced and Standard modes, however, can use the same
mechanism for transferring up to Protected mode.
’ Actually, this is a qualified statement. It is better to say that Standard mode cannot have V86
VMs, or DOS VMs, since it can, by the DPMI host, have multiple Protected mode VMs.
Windows, however, only runs the one VM, in which all WinApps reside.
* Intel’s DPMI specification places some caveats upon function 0201h:
The address placed in CX must be a Real mode segment address, not a selector. Consequently
the interrupt handler must reside in DOS memory (below 1 M) or the client must allocate a Real
mode callback address. See functions OlOOh and 0303h in Appendix C.
If the interrupt is a hardware interrupt, the memory that the interrupt handler uses must be
locked.
3 The Intel DPMI specification places these caveats upon function 0303h:
A descriptor may be allocated for each callback to hold the Real mode SS descriptor. Real
mode callbacks are a limited resource. A client should use the Free Real Mode Callback
Address function (0304h) to release a callback that is no longer required.
The contents of the Real mode register data structure are not valid after the function call, only at
the time of the actual callback.
280 WindowsAssemblyLanguage& SystemsProgramming
j m p doneit
;Snhanced :
,*see if forwarder TSR is present by checking interrupt
ivector 61h...
m o v ax,3561h ;get int-6lh vector address
i n t 21h ; / -- >es:bx
mov ivt6loff,bx ;save i t .
mov ivt6lseg,es /
mov ax, es :be s u r e t h e r e i s o n e .
or ax, bx /
mov t s r l o a d e d , a x ; set if TSR loaded.
js cantcall ;if n o t , c o m p l a i n a n d q u i t .
I
This program, or any DOS application with this code in it, looks at
the INT-6lh vector to see if there is anything in it (there will be 0
if not hooked). If so, the program goes ahead and calls the
“forwarder” portion of DOSTSR.COM, the DOS TSR.
However, this is where you need to think. If you loaded the TSR
from the DOS prompt before loading Windows (in contrast to
loading it from WINSTART.BAT), there will be a copy of the
DOS TSR in the current VM where the DOSApp is running, but
the TSR is useless. The reason is that its purpose is to call the
WinApp, but it will try to call the Protected mode WinApp in the
current VM, where it isn’t.
I’ll look at this diagrammatically in Figure 11.3 :
I
MT-2Fb/AX = 1685h is described in Appendix D. It is for switching VMs.
CX = bit-0 is set to indicate that Windows must wait until intemtpts are enabled before calling
the callback in the VM; bit-l is set to indicate that Windows must wait until the critical section
is unowned before calling the callback in the specified VM; the remaining bits must be zero.
DX:SI = the 32-bit amount by which to boost the target VM’s priority before changing contexts.
ES:DI = the segment:offset of the routine to call in the target VM.
282 Windows Assembly Language h Systems Programming
If you have a look at the DOSApp, you will see that it looks at
vector 61h in the IVT to get the address of the “forwarder” routine
in the TSR, and then it uses INT-2FWAX = 1685h to switch over
to the system VM and also to execute the forwarder code in the
copy of the TSR located in the system VM.
These little programs are two pieces of the puzzle, but there is a
third. The WinApp has to hook INT-60h in the IVT of the system
VM.
RealModeAccess 283
.CODE
offsetrealint DW 0 ;old ivt vector
segmentrealint DW 0 /
&selector DW 0 idata a l i a s t o c o d e s e g
hwndcs DW 0 ;save window handle for use in isr
callbackbuffer REGSTRUC c > ;Real mode register structure
Lstallint PROC ;no params
install:
pusha
push es
push ds
;will create alias in ldt of current task...
mov ax,OOOAh ;create alias data descriptor for code.
push cs
POP bx ;selector to be aliased
int 31h ;returns ax
;
push ax
POP es
mov ax,hwnd
mov es:hwndcs,ax ; save handle of window in code seg.
mov ax,es
mov es:dsselector,ax ;save data alias in code
. . .
I*could put some code for hooking the IDT . . .
...
hookreal:
POP ds *restore it again.
;Ok, now to hook Real mode in;.... hook 60....
mov ax, 02OOh ;get Real mode vector
mov b1,60h
int 31h ;-->cx:dx (seg:off)
mov es,cs:dsselector
mov es:offsetrealint,dx ;save old vect
mov es:segmentrealint,cx ; /
;
;now must reflect the Real mode int up to prot mode
;code....
push ds ;save
mov es,cs:dsselector ;get alias. Addr of buffer in es:di
mov di,OFFSET callbackbuffer ; /
284 Windows Assembly Language & Systems Programming
Real mode The data structure referred to as "callbackbuf f er" is the same
register callback structure used to pass register values between Real and
structure Protected modes, as discussed on page 269, where function 0300h
is introduced (this is for calling a Real mode interrupt from
Protected mode, which is going the other way).
WinApp Actually, the piece of the puzzle, consisting of the WinApp code,
ISR is in two parts: the “install” portion above, and a “run-time”
portion. The latter is the ISR (Interrupt Service Routine) that is
the end result. Wherever the interrupt originated, control should
end up there. I want this ISR to behave much like the ISR
introduced in the previous chapter; that is, to post a message to the
main window.
A Protected mode ISR is shown back on page 262, illustrating how
to post a message.
Because the WinApp has hooked INT-60h in the system VM, any
software interrupt within the system VM while the CPU is in Real
mode will cause execution of the Protected mode ISR “run-time”
portion of the WinApp. You can see in the DOS TSR that this was
very simply done by an “INT 60h” instruction.
Entry to When control is “passed up” from Real to Protected mode, the ISR
the ISR is entered with certain registers loaded:
DS:SI = Real mode SS:SP
ES:DI = Real mode call structure
The “call structure” is that same data structure containing the Real
mode register values. Return from the ISR is by an IRET, but the
data structure is modified as appropriate. At exit, the registers
ES:DI must be pointing to the data structure, because the DPMI
handler will put whatever is contained in the structure into the
Real mode CPU registers.
Real Mode Access 28.5
Exit f r o m For example, if we want the ISR to chain to the old ISR, we need
the ISR to get the old vector and put it into CS:IP in the data structure:
...
;end of ISR . . .
mov ax,cs:segmentreal
mov e s : [dil .csl,ax
mov ax,cs:offsetreal
mov e s : [di] .ipl,ax
iret
On the other hand, if the ISR is not to chain to the old vector but
instead is to return from whence it came, the return address on the
stack must be put in CS:IP in the data structure:
cld
lodsw ;get Real mode IP off stack.
mov es: [di] .ipl,ax ;put it into IP in data structure.
lodsw ;get Real mode CS off stack.
mov es: [di] .csl,ax ;put it into CS in data structure.
lodsw ;get Real mode flags.
mov es: [di] .flagsl,ax ;put into flags1 in data structure
add es:[di].spl,6 ; adjust SP on data structure.
iret
DPIW 1.0 This is all quite involved, just to post a message from a DOSApp
global to a WinApp, but while I think of it, if your need is not to signal or
memory execute but just to share data, DPMI version 1.0 does have a neat
solution. Ok, this is academic, as no versions of Windows run
DPMI vl .O - but maybe one day.
DPMI version 1.0 (not ~0.9) has a function, ODOOh (Allocate
Shared Memory), that creates and allocates a memory block that is
accessible across all VMs. Thus all Windows and DOSApps have
access to it.
There are also ODOlh (Free Shared Memory), OD02h (Serialize on
Shared Memory), and OD03h (Free Serialization on Shared
Memory).
The latter two allow synchronization of access to the shared block.
12
32-Bit Ring 0
Preamble
Privilege As explained in Chapter 1, the 286 and 386 have four privilege
levels levels, numbered from 3 to 0. With Windows 3.0, the operating
system kernel and device drivers run at the most privileged level,
0, while Windows applications and DLLs run at level 1. DOS
applications, being the least trusted, run at level 3.
However, Microsoft changed its mind with Windows 3.1, and
moved Windows applications and DLLs down to level 3 also.
This includes all the DLLs of the Windows API.
When I upgraded from Windows 3 .O, to 3.1, I had the distinct but
subjective feeling that the new version was a tad slower. The
changes in privilege could be the reason. Of course, Microsoft
claimed just the opposite - that the new version was faster, which
could have been true, taking into account the new 32-bit file and
disk access (which I originally had turned off).
Then, when I upgraded to Windows for Workgroups 3.11, I again
had the subjective feeling that everything had slowed down. I
have never tried to quantify this. Version 3.11 seemed to take
longer to load, which may have had something to do with the fact
that when going from 3.1 to 3.11, I decided to network two PCs.
Then, when I upgraded to Windows 95 . . .
Anyway, the current situation with Windows is that applications
run at level 3, least privileged. Unfortunately, this seriously
287
288 Windows Assembly Language & Systems Programming
2 Base- 0 -7
Accessed
3 Base- 8 -15 Readable
Conforming
4 Base-1 6-23 Code-data
App_system
5 Access-byte 5-6 D P L
7 Present
6
f-
7 Base_24_3- Limit_16-19:
’ BIT Bits O-3 in offset-6 of a
4 Unused descriptor, is the upper part
/ 5 Always-0 of the size of the segment.
High part of the linear
starting address (bits 24-3 1). ! Seg_16-32
Granularity
Instruction Just to keep confusing you: even if the the segment is in 16-bit
size-prefix mode, you can still use the 32-bit registers!
For starters, I’ll take the case of an “old fashioned” Windows
application, running in 16-bit segments, and consider a very
ordinary instruction that may appear in that program:
0907:0200 58 POP Ax
0907:0201 6658 POP EAX
58 POP EAX
6658 POP Ax
The situation is now reversed: the “58h” means "POP EAX", but if
we write an instruction that only accesses a 16-bit register, it will
have the prefix appended. It doesn’t say much for Microsoft, but
Codeview version 4.01, despite being fully operational in 32-bit
mode, able to display the 32-bit registers, and able to trace, did not
unassemble correctly. At the time of writing, 4.01 is my latest
version - it came with MASM version 6.1 - and I’m sure that by
the time you read this book, the bug will have disappeared.
32-bit Real So what of Real mode and virtual-86 mode? In both of these
mode modes, the default is 16 bits, but you may be very surprised to
learn that in both modes, you can use the 32-bit registers. Of
course, the prefix (or prefixes) will be in front of every 32-bit
instruction.
This may come as a complete surprise, but use of 32-bit registers
allows you to have segments greater than 64K - up to 4.3G -
and thus break the 1M conventional memory limit for Real mode.
Of course, Real and virtual-86 modes have paragraph addresses in
the segment registers, so these can only reference the first 1M:
however, you are quite at liberty to use offsets to access code and
data beyond 1M.
Reference A bit of setting-up is required to use Real and virtual-86 modes in
this way, and I recommend a good book: Al Williams has worked
it all out, and has an entire chapter dedicated to this, in his book
DOSS: A Dmeloper’s Guide; Advanced Programming Guide to
DOS, M&T Publishing Inc., USA, 1991. There is probably a more
recent version of the book (probably with a new title!), but the
chapter on 32-bit programming is still quite relevant, even in the
1991 book.
Interrupt I kind of glossed over this little detail in an earlier discussion (look
gate back at Figure 11. l), but the interrupt services are at ring 0, so the
entries in the interrupt descriptor table (IDT) of the form
seZector:offset reference an interrupt gate, not a descriptor.
An interrupt gate, or any gate for that matter, sits in the LDT or
GDT as an 8-byte entry, just like any other descriptor (see Figure
12.1), but it has a different format. In the case of interrupt
handling, if there is to be a ring transition, i.e., if the ISR is at a
more privileged level than 3, then the entry in the IDT is not a
descriptor: it is an interrupt gate. However, the code descriptor for
the ISR is still there at another entry (also in the IDT, I presume).
Call gate A call gate is the mechanism for a CALL instruction to call code at
a more privileged level. It works just like the interrupt gate, in
which the descriptor in the LDT or GDT, of the code to be called,
is not called directly. Instead, you call a call gate, which in turn
calls the more privileged code via its descriptor.
A call gate is 8 bytes and can be an entry in the LDT or GDT, just
like a descriptor. However, it has a different structure to a
descriptor, as Figure 12.2 shows:
Fieure 12.2: Detail of the call gate.
CALL GATE
OFFSET Low part of the offset to be called (bits O-l 5).
0
Linear starting address of the segment
1
2
O - 3 Param_count
3 Selector- 8 -15 4-7 These bits must be zero.
4
BIT
5 / O-3 Type (4=286 call gate, C=386)
4 App_system
6 O f f s e t _ 16-23 fi 5-6 DPL
7 Present
7 Offset_24-3 1
High part of the offset
I
to be called (bits 16-31).
Actually, what distinguishes this as a call gate, and not some other
kind of gate, is the Type field. The value Type = 4 means that it is
a call gate to a 16-bit (286) segment, while a value of C (hex)
means that it is a call gate to a 32-bit (386) segment. For the
record, the other possible values are 5 = task gate, 6 = 286
interrupt gate, 7 = 286 trap gate, E = 386 interrupt gate, and F =
386 trap gate.
The Selector field is the ring-0 segment that we want to call, and
Offset is where in the segment. Note that the code descriptor for
the ring-0 selector still has to exist, and it will be elsewhere in the
LDT or GDT.
App_system would normally be zero and Present set to 1. The
DPL field is important: it specifies the least privileged code that is
permitted to use this call gate. Therefore, we set it to 3.
Puffing Ca// If we create a call gate, we can then put it in the LDT or GDT, and
gate & thus we will have a selector for it. Then, all we need to do in our
descriptor program is call the selector: the CPU will recognize it to be a call
together gate, look inside it, and get out the selector:offset. The CPU will
294 Windows Assembly Language & Systems Programming
then use that selector to get the code descriptor and will call the
code.
Note, however, one peculiar thing: if you perform a FAR call from
your application to the call gate selector at some offset, any offset
that you specify is ignored. Instead, the offset in the call gate is
used.
At this point, I think it best to show some code.
Creation For this demo program, I chose to use the WM_CREATE message
of a call to call makeringOselector0, which sets up the addressing to the
gate ring-0 code.
Then, I arbitrarily chose to use a press of the “ok” button on the
messagebox, which occurred in response to IDM_ABOUT, to call
RINGOCALLGATE, which is a pointer to the call gate, which takes
execution to the ring-0 code.
Finally, before exiting from the program, it calls
freeourselectors(), which removes the descriptor and call gate that
we had created in the LDT.
Now for the part that does the real work:
.DATA
dpmiproc DD 0 ;dpmi extensions entry point.
RINGOCALLGATE LABEL DWORD *use t h i s t o c a l l r i n g 0 c o d e .
ringO_of f DW 0 ; callgate selector for RINGOFUNC
ring0 cs DW 0 * / (offset is ignored)
ms dos str DB "MS-D&" ,O
1dF selector DW 0 *for direct writing to ldt.
des?riptor_selector DW 0 jring0, cannot be accessed
;directly.
ringoerrormsg DB "Error creating ring 0 access... \
aborting program.",0
.CODE
makeringOselector PROC
invoke GLOBALPAGELOCK,cs
cmp ax,0
je lockfailed
;find out where the LDT is . . .
lea si,ms dos str
mov ax,l6?%h - ;get dpmi extensions entry point.
int 2Fh ;-->es: di (undocumented)
; *** cmp al,0 ?????
; *** jne extensionsnotfnd
mov WORD PTR[dpmiprocl,di ;save e n t r y point
mov W O R D PTR[dpmiproc+2l,es ; /
mov ax,lOOh ;undocumented
call dpmiproc ;-->ax=selector to ldt.
jc extensionsnotfnd
mov ldt selector,ax
mov es,;?x
*create a ring 0 32-bit descriptor...
I
push es
invoke ALLOCSELECTOR,cs ;-->ax=alias to cs.
POP es
cmp ax, 0
je selectorerror
and ax, OFFF8h ;get offset of descriptor in ldt.
mov bx,ax
32-Bit Ring 0 297
/NT-2fh, The first thing that makeringOselector() does is lock the segment in
function memory, as the ring-0 descriptor and call gate that are about to be
f68Ab created will have their present bit set, indicating that they are in
physical memory.
The next problem is, where is the LDT? The exact location of the
LDT is not something that a ring-3 program is supposed to know,
but an undocumented feature of INT-2Fh, function 168Ah with
address of string “MS-DOS” in the SI register, returns a selector to
the start of the LDT.
298 Windows Assembly Language & Systems Programming
Creation of a The next job is to create a descriptor for the ring-0 code. This is a
ring-0 code SMALL model program, which means that all code is in the same
descriptor segment. ALLOCSELECTOR() creates a new descriptor in the
LDT that is an alias to, in this case, CS. The code immediately
after uses the selector to the LDT to directly access the LDT and
modify the privilege level of the segment. Also, since the newly
created descriptor is an alias to CS, it is a 16-bit segment: this
example code requires the ring-0 code to be 32 bits by default.
Therefore, the seg_16-32 bit is altered also.
16- & 3Z-bit Normally, an application cannot directly modify an entry in the
c o d e i n s a m e LDT, for the simple reason that you don’t know where it is. Now,
segment having modifed it, you can’t call it because it is a ring-0 descriptor
whereas your code is running at ring 3.
Note that there is a trick being performed here, as there is only the
one segment. I defined ASMRINGO as SMALL, and when the
ring-0 file, HEAVEN, is linked, there will only be one code
segment. CS is a ring-3, 16-bit descriptor, so that is how the code
is treated when executed using CS. However, the newly created
alias, descriptor_selector, is ring 0, 32 bits, but is referencing the
same segment.
Ca//gate The final step is to create the call gate. Again, an entry is made in
fields the LDT, and it is directly written to, to make it into a call gate.
The selector for this call gate is saved as ring0_cs. The call gate
must contain the offset of the code to be called, which in this case
is ringOfinc, defined as external, at the beginning of the code
listing. You will see that descriptor_selector is also put into the
call gate.
Offset-4 in the call gate, which I have marked in the listing with
three asterisks, is where you can specify how many doublewords
you have passed on the stack: the CPU will copy these from your
ring-3 stack to the ring-0 stack. In this case, no parameters are
copied.
Ring-O Now that the stack has been mentioned, this is an important issue
stack that must be addressed. Windows maintains a separate stack for
ring 0, and the call gate will automatically transfer to it. The CPU
will copy the number of parameters specified from the ring-3 stack
and will put the return address on top of the new stack.
Note that the ring-0 segment has also been defined (in this case) as
a 32-bit segment, which means that the return address is two 32-bit
values for selector:offset.
The default ring-0 stack is very small, which is why this program
executes CL1 (clear interrupt) before calling the ring-0 code. Have
32-Bit Ring 0 299
Finally, you can put a number after RETF to indicate the number
of bytes to pop off the stack. Use this to remove parameters passed
by the call gate, if calling in conformance with the Pascal
convention.
So what can we do in this 32-bit ring-0 procedure?
FLAT Memory
You will find the program discussed so far on the Companion Disk
in directory \ASMRINGO. This chapter also describes an
enhancement to this program that is contained in \FLATASMO.
What you ASMRINGO.EXE, as described so far, demonstrates how a 16-bit
can do in ring-3 program can make the transition to a 32-bit ring-0 code
ring 0 segment and come back. Once in ring 0, you can execute OUT,
IN, CLI, STI, etc., without intervention by the CPU. You can also
use the privileged instructions of the 386 that allow direct
manipulation of LDT, GDT, and page tables.
However, one other thing you might want to do is call the
functions in the Virtual Machine Manager (VMM), which you can
think of as the “core” of Windows, and the functions in the Virtual
Device Drivers (VxDs). Conceptually, you can view Windows as
having two APIs - the ones you know abut and that are
described in all the Windows programming books (and in the
SDK) and another set that can only be called by VxDs.
The latter functions are inside the VMM and the VxDs and are
ring-0 code. The conventional wisdom is that you must write a
VxD to be able to call them, but in fact our RINGOFUNC can do
so. The requirement simply is that you must be in ring 0 and you
must be in the FLAT memory model. The program developed so
far falls down on the latter point.
Getting Back to the central argument. The objective now is for our ring-O,
addressability 32-bit procedure to be able to call VMM and VxD functions, The
to fLATring- following code is a re-do of MAKERINGOSELECTOR, which
code sets up addressability to ring 0:
makeringOselector PROC
;get addressability of ring0, ringofunc.....
invoke GLOBALPAGELOCK,cs
cmp ax,0
je lockfailed
lea si,ms dos str
mov ax,l6FAh - ;get dpmi extensions entry point.
int 2Fh ;-->es:di (undocumented)
; *** cm al,0 ?????
; *** jne extensionsnotfnd
mov WORD PTR[dpmiproc],di ;save entry point
mov W O R D PTR[dpmiproc+2l,es ; /
mov ax,lOOh ;undocumented
call dpmiproc ;--sax=selector to ldt.
jc extensionsnotfnd
mov ldt selector,ax
mov es,Zx
;find the linear address of CS...
mov bx,cs
and bx,OFFFSh ;get offset in ldt
mov ax,es: [bx] ;get size of segment.
mov cssize,ax
mov ax,es:[bx+2] ;get lo-half of lin.addr.
mov WORD PTR flatlin,ax
mov al,es: [bx+41 ;get hi-half of lin. addr.
mov ah,es:[bx+7] ; /
mov WORD PTR flatlin+a,ax
I*calculate FLAT linear address of ringofunc...
mov ax,WORD PTR flatlin
add ax,ringOfunc inote: lwOFFSET1l is optional
jnc moppi
mov bx,WORD PTR flatlin+2
302 Windows Assembly Language & Systems Programming
inc bx
mov WORD PTR flatlin+a,bx
moppi:
mov WORD PTR flatlin,ax
,*create callgate to ringofunc.....
push es
invoke ALLOCSELECTOR,O ; create a descriptor in ldt.
POP es
cmp ax,0
je selectorerror
mov ringO_cs, ax ;save final selector.
and ax,OFFFEh ;get offset of descriptor in ldt.
mov bx,ax
mov ax,WORD PTR flatlin ; my ring0 linear address
mov es: [bxl,ax ; /
mov ax,WORD PTR flatlin+2 I
mov es: [bx+6l,ax ;
mov WORD PTR es:[bx+2],28h;FLAT cod& selectorcin gdt).
mov BYTE PTR es:[bx+41,0 ;04;****?dwords copied to stack
mov BYTE PTR es: [bx+51,11101100b
;present=l,dpl=3,app=OO,type=C (type=C: 386 callgate)
;find the FLAT linear address of this program's data
,*segment...
mov bx,ds
and bx,OFFFEh ;get offset in ldt
mov ax,es: [bx+21 ;get lo-half of lin.addr.
mov WORD PTR flatdatalin,ax
mov al,es:[bx+41 ;get hi-half of lin. addr.
mov ah,es:[bx+71 /
mov WORD PTR flatdatalin+2,ax
qwert:
jmp SHORT qwerty
lockfailed: ; . . . put in handlers . . . .
extensionsnotfnd:
selectorerror:
qwerty:
ret
makerinqOselector ENDP
What you will notice in the above code is that I have not created
code or data descriptors. What you do see above is the use of
selector 28h. I have obtained the base addresses from
ASMRINGO’s DS and CS descriptors, and to obtain the code
FLAT linear address, I have added the offset of RINGOFUNC to
the base address ofCS and savedtheresultinflatlin.
To obtain a FLAT linear address to the data segment, I extracted
the base address from DS and saved it asjhztdutalin.
Caiiing VMM Now, going up to ring 0 HEAVEN, by exactly the same method of
a n d VxD "call RINGOCALLGATE~',~~~~~~~~~ entry to RINGOFUNC with
services CS = 28h, the FLAT selector.
32-Bit Ring 0 303
int 20h
DW GET CUR VM HANDLE ; =1
DW VMM-DEV~CE~ID
- ;= 1
*example of using a '386 privileged instruction...
I
Moving On
To be able to go up to ring 0 from inside a ring-3 application is
“real neat”. This chapter also showed how to go from a 16-bit
segment to a 32-bit segment, and actually have them overlap, that
is, be the same segment, fitting the SMALL memory model.
The work done in this chapter can also be applied to Windows 95
native 32-bit applications, in which case the segment is already 32
bits, but the ring transition is still required.
It may be perverse, but I really like the idea of writing 16-bit
applications that have 32-bit and/or ring 0 functions in them.
These will run fine in both Windows 3.1 and 95.
A 32-bit application will run in Windows if it has the Win32s
library installed, and it will run natively in Windows 95. So, I
guess we need to move ahead into the pure 32-bit world. A lot of
the material earlier in this book has focused on 16-bit code,
although the principles are in most cases applicable to 32-bit code
also.
We need a chapter that elaborates on the differences in coding for
32-bit segments and Win32, the 32-bit Windows API library. We
also need to see a pure 32-bit application. The next chapter does
this.
13
32-Bit Ring 3
Preamble
Other This book has been structured in a quasi-historical sequence,
chapters starting with 16-bit programming in the early chapters, gradually
introducing 32-bit issues in latter chapters. I didn’t want to dump
16-bit, as it is still relevant and will remain an issue for a long
time. Even if a systems programmer wants to program entirely in
32-bit mode, Windows 95 internally is surprisingly 16-bit
oriented. This means that a thorough knowledge of the 16-bit
issues and the interaction between 16- and 32-bit modes is
required. Therefore, the gradual progression of the chapters from
a 16-bit foundation is most relevant.
Of course, many developers are still programming for Windows
3.x, and 16-bit applications run fine on Windows 95 and even have
some advantages with regard to system privileges, compared with
32-bit applications. As described in the last chapter, putting 32-bit
instructions into a 16-bit segment incurs only a small instruction
prefix penalty. Putting 32-bit segments into a 16-bit application
can also be done. Considering these points, many developers do
not feel any urgency to go totally 32-bit.
However, if you want to move ahead and write a true native 32-bit
application, this is the chapter.
307
308 Windows Assembly Language & Systems Programming
TASM 32-bit Both companies have moved toward less printed and more on-line
SUppOrt documentation. My personal viewpoint is that you can’t beat a
good printed manual, which is why the supplementary printed
books business is booming.
TASMS supports 32-bit programming for Windows 95 and NT,
but the documentation, both printed and on-line, is pitiful. The
one example program is also pitiful, as it is written for TASM 4.
Porting code So, I had to figure it out from scratch. I had a 32-bit program
from MASM written for MASM, which I converted. Now, this is an interesting
to TASM story, and there were nights spent working to 3:00 AM trying to
figure it out.
TASMS almost supports all of the features of MASM version 6.1.
Therefore, the example program given in this chapter, though
written for TASMS, should also be very easy to convert for
MASM.
ltemisiig the l TASMS has prototypes for procedures, except they are
differences designated by the “PROCDESC” keyword, not “PROTO”.
between Otherwise, the syntax is the same, and I was able to create an
MASMG and Include file, W32INC, on the Companion Disk in directory
TASM5 \TASM32 that is very easy to convert for MASM.
32-bit Ring 3 309
Installing TASM5
A// the TASMS is designed to work from the command line in a DOS box.
7.wlw too/s There is no editor or IDE. There is, though, the wonderful Turbo
are DOS Debugger. I prefer to use the command line, though an IDE does
programs have advantages, such as seeing where assemble errors occur in
the source code. With the command line approach, the assembler
spews out a list of errors and the developer must then find those
lines in the source code, which is easy enough.
So, I put that in. Then I read that the install process puts these two
lines into the SYSTEMJNI file:
;SYSTEM.INI file
[386Enhl
device=c:\tasm\bin\windpmi.386
device=c:\tasm\bin\tddebug.386
.DAl'A
I._____----______-_______________________--~-~~~---~~~~~--
hInst DD 0
mainhwnd DD 0
Sl WNDCLASS c?>
s2 MSG <?>
s3 PAINTSTRUCT <?>
szTitleName DB "Win32 Assembly Language Demo Program",0
szClassName DB "W32DEMO" 0
sziconname DB "ICON- 1" ,b ;name of icon in .RC file.
g_hmd DWORD 0
g-message DWORD 0
g_wparam DWORD 0
g_lparam DWORD 0
szaboutstr DB "This is an about-box",0 ;messagebox
sztitlestr DB "Barry Kauler 1997",0 ;/
.CODE
;---____----_______-________-_______-___--~------~-------
start:
call GetModuleHandle, NULL
mov hInst,eax
; initialise the WndClass structure
mov sl.w_style, CS HREDRAW + CS VREDRAW + CS_DBLCLKS
mov s1.w IpfnWndProc, offset ASMWNDPROC
mov sl.w-cbClsExtra, 0
mov sl.w-cbWndExtra,
- 0
mov eax, hInst
mov s1.w- hInstance, eax
;call LoadIcon, NULL,IDI_APPLICATION ;loads default icon.
;No, let's load a custom icon....
call LoadIcon, hInst, OFFSET sziconname
mov s1.w- hIcon, eax
call LoadCursor,NULL, IDC_ARROW
mov s1.w- hcursor, eax
mov sl.w_hbrBackground, COLOR_WINDOW + 1
mov sl.w_lpszMenuName, OFFSET szClassName
mov sl.w_lpszClassName, OFFSET szClassName
call RegisterClass, OFFSET sl
call CreateWindowEx, 0,OFFSET szClassName, \
OFFSET szTitleName,WS_OVERLAPPEDWINDOW, \
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,\
0, 0, hInst, 0
312 Windows Assembly Language & Systems Programming
wmcommand PROC
mov ax,WORD PTR g_lparam
.IF ax==0
1 mov ax,WORD PTR g_wparam
.IF ax==IDM_QUIT
call PostQuitMessage,O
.ELSEIF ax==IDM_ABOUT
call MessageBox, g_hwnd, OFFSET szaboutstr, OFFSET
sztitlestr, MB-OK
.ENDIF
.ENDIF
ret
wmcommand ENDP
;_____________________-_----_--_________~~~~~~~~~--------
wmpaint PROC
call BeginPaint, hwnd,OFFSET s3
mov hDC, eax
Support Files
Resource There is nothing much to say about resource tiles. They work the
fies same as before.
32-bit Ring 3 315
//W32DEMO.RC,resource file.
$;;hyee (arbitrary) equates could have been in an include
#dekine*IDM QUIT 100
#define IDMIABOUT 101
!if $d(MAKEDIR)
IMP=$(MAKEDIR)\..\lib\import32
!else
IMP=import32
!endif
$(FN) .EXE: $(OBJS) $(DEF)
tlink32 /Tpe /aa /c $(LNKDBG) $(oBJs),$(FN),,$(IMP),S(DEF),S(FN)
.asm.obj:
tasm32 $(TASMDEBUG) /ml $&.asm
brc32 -r $(FN).rc
Compatibility I have a lot of trouble with Borland Make tiles. Although there is
of Bar/and & a switch for setting compatibility with Microsoft’s NMAKE.EXE,
MicrosotY it is still not compatible. I have never been able to get a Make file
Make files I have created for NMAKE to work with Borland’s MARE.
I have to resort to taking an example Make file provided by
Borland, which is what I have done above. It is not quite
optimum, as the resource compiler executes every time, but at
least it works. I recommend that you use the ” -B" switch to force
everything to build:
NAME W32DEMO
DESCRIPTION 'ASM program'
32-bit Ring 3 317
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE
.TA PRELOAD MOVEABLE MULTIPLE
E HEAPSIZE 8192
ACKSIZE 8192
t EXPORTS ASMWNDPROC
Turbo If after assembling and linking, it doesn’t work, it is time to use the
Debugger debugger. Stay in the DOS box to use it, and type this:
Postamble
Chapter 12 showed how a 16-bit application can move into 32-bit
ring-0 code. What about the 32-bit application of this chapter?
Another question: what if the 32-bit application wanted to call a
function in a 16-bit DLL? Or an interrupt? Or perform an IN or
OUT instruction?
It is a strange fact of the historical evolution of Windows that
16-bit applications have greater freedom getting into the insides of
Windows than 32-bit applications. DOS TSRs also have great
advantages. Because support for legacy applications is going to
continue for the forseeable future, it is sensible to use whatever
easy paths are available.
A 32-bit application cannot use the technique of Chapter 12. The
reason is that the interrupt handlers provided by Windows for
certain interrupts assume that it is 16-bit code executing the
interrupt. The most fundamental problem is that it is only a 16-bit
stack, so the interrupt handler will crash. Nor can a 32-bit
application call a 16-bit function.
The next chapter backtracks somewhat and looks at the transition
between DOS and Windows as Windows loads. Understanding
this can be very useful and will help with the above questions.
14
DOS-Win Transitions
Preamble
Integratiflg This chapter further develops many of the concepts introduced in
the code the previous chapters and also discusses some overall and related
from issues.
previous In this chapter, I have built upon the issues of moving between
Chapters various modes, such as between VMs and between Real and
Protected modes. What happens to registers? What about the
stack? What are the address mappings?
I have further developed the discussion of interrupt handling for
Real and Protected modes.
I have also considered the issue of synchronizing between DOS
and Windows. For example, how does a DOS driver know when
Windows is loading? How do you get a virtual device driver to
cooperate with a DOS device driver? Or to cooperate with a
WinApp?
When writing the first edition of this book, I paid a lot of attention
to Standard mode. In this edition, I have considered it to be
“almost” history, so just about all of the code and description in
this chapter is geared toward Enhanced mode, i.e., requiring at
c
Interrupt Handlers
Chapters 10 and 11 give the elements required for interrupt
handlers, and I have put various example programs on the
Companion Disk. This section develops the topic further.
DOS-Win Transitions 321
push ds
push
;get addressabi?ty of data in code seg...
mov ax,cs:hwndcs ;post message to window
push ax ;
push WM_USER ; $
push 0 ;
push 0 I t
push 0 ;
call POSTMESSAGE ;
mov es,cs:dsselector ; for &iting to data in code seg.
pop es
POP ds
popa
I-for returning to Real mode prog prior to interrupt...
cld ; (de/scribed in Chapter 11)
lodsw ;
mov es:[di].ipl,ax ;
lodsw ;
mov es:[di].csl,ax ;
lodsw I
mov es:[di].flagsl,ax ;
add es:[di].spl,6 ;
ican chain to original vector by putting it into callback
;data structure...
; mov ax,cs:segmentrealint
; mov es:[di].csl,ax
; mov ax,cs:offsetrealint
; mov es:[di].ipl,ax
;
iret
installint ENDP
;........................................................
END
Separate Note that there are two ISRs, one each for interrupts that come via
/SRs for/VT the IDT and those that get reflected up from Real mode via the
and JDJ IVT. With regard to the installation of these ISRs, note that I did
not hook the vectors as soon as the WinApp received the
WMTCREATE message, as this can, under certain circumstances,
impair the display of the window. Instead, I posted a message,
WM_USER+l, which at a later stage calls the install code (see the
complete program on the Companion Disk, in \WIN2REAL and
further development in REAL2WIN).
With regard to exiting from the program, I did of course unhook
the vectors upon receipt of a WM_DESTROY message.
DOS- Win Transitions 323
ISR Another issue with the Protected mode ISRs is reentrancy. This is
reentrancy especially a problem with hardware interrupts that can come in at
any time. Upon entry to the ISR, hardware interrupts are disabled,
but once you put in the ST1 instruction, they can occur. Note that
you would also send an End Of Interrupt (EOI) signal to the
interrupt controller chip to tell it that it is now allowed to send
more interrupts (this is done by the default handler, if you chain to
it). You could argue to avoid the problem by leaving the interrupt
flag clear - but this should not be done for too long. The same
point applies to the EOI signal - I did it by calling the original
handler (via INT-60 in the ISR reached via the IDT).
If you put in an ST1 (and an EOI has been sent in the case of
hardware interrupts), think about reentrancy. You may have to
organize the data used by the ISR to be dynamic (on the stack): I’m
thinking in particular of the data register structure, in which DPMI
passes the Real mode registers to and from the Protected mode
ISR.
Reference The “DPMI Toolkit”, available from Qualitas (see
source http : //www. qualitas . corn/), has mechanisms for this.
The case of In your .DEF file, FIX the code segment in place, and do not mark
the missing it as DISCARDABLE. This will not stop Windows from
code removing the segment from memory, but whenever your program
segment needs to access the segment it will be reloaded into the same place
- well nearly always!
If you get a selector alias to store data into the code segment, such
as a window handle to be used by the ISR, or even the alias itself,
for writing data to the code segment within the ISR, it will work.
The alias will not require updating, because the code segment
marked as FIXED in the .DEF file will remain at the same place in
324 Windows Assembly Language & Systems Programming
Baiting the If you look at the above listing on the Companion Disk, you’ll see
segments that I used GLOBALHANDLE and GLOBALFIXO. The first
down returns a handle for a selector or segment address, while the
second Windows function locks the segment into that linear
address. This is the only sure way to stop Windows from moving
the segment, and it works in both Standard and Enhanced modes.
However, in Enhanced mode you can use GLOBALPAGELOCKO
to prevent paging, and guarantee that the segment is locked into
physical memory. What these functions will do for you is speed
up operation as the ISR’s will be kept in memory (and you won’t
lose what you write to the code segment). They are not essential,
however.
B-nand the What about getting at data in the data segment from inside the
wayward ISR? No problem, because you can store the value of DS in the
data code segment. The data segment doesn’t even have to be FIXED,
segment because its descriptor will be automatically updated, unlike an
alias.
None of this will work under Standard mode. Why am I even
bothering to discuss Standard mode - it’s dead, dead, dead.
Maybe in some remote parts of the world there are still people
running Windows in Standard mode. I promise not to mention it
again.
functions for this (see above) and so does DPMI, apart from the
specifications in the .DEF file.
Put those TSRs in that first 1M and don’t worry about it!
My little DOS TSR hardly impinges on the “valuable” 1M
anyway: it’s under 300 bytes. It hooks INT-9, which is a special
case hardware interrupt. Here it is, somewhat abridged:
;hook keypresses/releases . . .
mov ax,3509h ;get int-9 vector in ivt.
int 21h ; /
326 Windows Assembly Language & Systems Programming
’ This is a very interesting extension to INT-2Fh. Function 1605h is called by Windows when it
first loads. This enables DOS device drivers and TSRs to perform any necessary initialization.
It is important to follow the rules here, by first enabling interrupts and then calling the old
INT-2Fh vector. The latter is because other drivers/TSRs may have hooked the vector. CX
must have zero. If you for any reason decide that Windows should not go ahead and load, then
put a non-zero value into CX and IRET: other drivers have the option of doing this also, which
is why we called the old vector. Windows also supplies ES:BX = 0:O in Standard mode, DS:SI
= 0:O; DX bit-0 = 1 if Windows in Standard mode, =0 if Enhanced mode; and DI contains the
version number = 030Ah for version 3.1.
2 This is the opposite of 1605h, called by Windows when it unloads. Windows supplies DX bit-0
= 1 for Standard mode and = 0 for Enhanced mode.
DOS- Win Transitions 329
The entire program was too much to print, hence the sections in
italics. The complete program is on the Companion Disk;
however, if you know much about TSRs there is sufficient
information in this listing for you to construct it.
Global Another bonus of this TSR is that it establishes a global data area
data and provides a FLAT 32-bit linear address for it that the Windows
application and the device driver can access.
The An interesting aspect to how this TSR works is that neither the
invisible VxD nor the WinApp need to be specified in a IN1 file.
VXD It is usual to put a “DEVICE= ” line inside SYSTEMIN, to cause
a VxD to load, but the TSR will load the VxD without such a line.
Furthermore, although Microsoft recommends that all VxDs
“should” b e i n \WINDOWS\SYSTEM d i r e c t o r y , the
documentation does not say that they “have” to be. Thus, you can
put your VxD anywhere.
TSR Installation
TSRZWltV Essential portions of TSR2WIN are reproduced here, and this is
TSR sufficient for you, without having the original source files from the
Companion Disk, ifyou are familiar with basic TSRs.
Reproduced below is the portion of the installation code that sets
up the data structures required for auto-loading of the VxD and
WinApp.
The TSR is A vital point must be brought out now. I chose to put the TSR,
“insidee” TSR2WINEXE, “inside” the VxD as a DOS stub.
the VxD All Windows programs have a DOS stub, which is a DOS program
that resides inside the Windows program. Should the user execute
the Windows program from the DOS prompt, only the stub will
execute. It is usual for the DOS stub to display a simple message
that you need Windows to run this program, then it terminates.
Putting the TSR inside a VxD is easy. I have placed a typical
.DEF file, used for linking a VxD, in directory \TSR2WIN, and
this file is called VDEMOD.DEF. It shows how easy it is to
specify the TSR as a stub to the VxD.
It is not essential to do this, but it offers a simplification: for the
TSR to automatically load the VxD, the TSR needs to know the
path of the VxD. If the path is fixed, then you can specify it in the
TSR, or maybe you could pass it to the TSR on the DOS command
line-tail (when loading the TSR). Or, by having the TSR inside
the VxD, the TSR need only look at its own path to determine
where the VxD is!
It was an arbitrary choice, but I chose to put the WinApp in
another directory and have specified the path in the TSR, but I
could have also put the WinApp in the same path as the VxD.
332 Windows Assembly Language & Systems Programming
* my 2F handler.
int 2Fh ;multiplex'interrupt (that we will hook)
POP es
or al,al ;AL=non-0 means abort.
jz abba
jmp abort load
abba :
;********************************************************
push cs
POP ds ;note cs: overrides thus not really reqd.
;get a pointer to the name of the load file in the
I-environment seg. entered with es=psp...
mov ax,es
mov bx,cs
mov WORD PTR cs:[TSR_info.TSR PSP Segment], ax
sub bx, ax - size Tin paras) of PSP
mov WORD PTR cs:[PSP_Size]: bx
mov bx,2ch I*environment segment
mov es,es:[bxl
xor di,di
mov cx,-1 ;big number
xor al,al ;search for a null
cld
qq:
repne scasb ;get past one null and stop
cmp byte ptr es:[dil,O ;another null
jnz qq ;no.
add di,3 I*skip the word before the name.
Setting UP the Continuing from above, look now at setting up the data structure
VxD data for the VxD.
structure
Segmeflt This code should be easy to read, but do note that this TSR is
structure .EXE format, which means that the PSP is a separate segment
of TSR from the code segment. I haven’t used the data segment. For the
SMALL model, the code segment and PSP get loaded into
memory contiguously; that is, the code immediately follows the
PSP.
That is why, to get the size of the PSP, I merely subtracted ES
from CS (as ES initially points to the PSP segment).
Setting UP tbe The following code, which is another data structure required for
WinApp data launching the Windows application, continues from above
structure -
mov WORD PTR cs: [TSR_Info.TSR_Exec_Cmdl, \
OFFSET Exec Path-Name
mov WORD PTR cs:[TSR Info.TSR Exec Cmd+21 ,cs
mov WORD PTR cs:[TSR~Info.TSR~Exec~Flagsl, 1
;=TSR_WINEXEC
mov WORD PTR cs:[TSR_Info.TSR_Exec_Cmd_Showl, 4
;=SW_SHOWNOACTIVATE
mov WORD PTR cs: [TSR_Info.TSR_ID_Blockl, \
OFFSET My ID-Block
mov WORD PTR cs:[TSR Info.TSR ID Block+27, cs
mov WORD PTR cs:[TSR-Info.TSR-DaFa Block], 0
mov WORD PTR cs:[TSR~Info.TSR~Data~Block+21, 0
Universal The hooking of IVT vectors in the above code is very ordinary, but
global you will find the creation of the global data and FLAT linear
pointer address to be interesting.
A slightly negative point about this global data is that it is in the
first lM, i.e., conventional memory.
The FLAT address is simple to calculate, because it is just an
addition:
DS* 16 + OFFSET globaldata
It is actually only 16 or 17 bits in size, as this TSR is close to the
start of linear memory, so the higher bits are zero. I stuck this
pointer into the IVT, at entry-60h, which is a convenient place
from which the VxD and WinApp can retrieve it. IVTdOh is thus
not in conventional segment:offset form!
How a WinAppThis FLAT pointer is immediately usable by the VxD. All that the
& VxD can VxD needs to do is get it out of the IVT.
accessglobal 0rd’mary 16-bit or 32-bit Windows applications can also use the
data FLAT pointer, but they do need to obtain a ring-3 FLAT data
selector. The global data can then be accessed in this manner:
The above code sample is not from the TSR - it shows how a
WinApp can access the global data, where GLOBAL is a field of
the global structure.
There are various ways to obtain a FLAT data selector. One thing
that you could do is make an alias of DS and then modify the base
address in the descriptor (if you can locate the LDT! - see
Chapter 12).
Another way is for the WinApp to go to ring 0 and call a VMM
service to create a FLAT ring-3 data selector - the service to call
is GETAPPFLATDSALIASO, described in the DDK. If you have
a VXD as part of your system, you can get it to call this function
and pass the slector back to the WinApp.
A great advantage to using GETAPPFLATDSALIASO is that the
returned selector is in the GDT and will thus continue to work
across VMs. This is not such an issue with Windows 3.x and 95,
because all WinApps run under the same LDT, but beware
Windows NT.
The word of warning here is that if you want your selectors to be
global across 32-bit Windows NT applications, which will run
with private LDTs, then put your selector into the GDT
338 Windows Assembly Language & Systems Programming
Now for the part that actually loads the VxD and WinApp.
TSR Resident Code
/NT-2fh The resident code monitors IVT-2Fh and detects when Windows is
handler loading, as follows:
runtime2F :
;entered when Windows loads, with AX=1605h, and when
;Windows unloads, with AX=1606h....
;detect when Windows loads, and set a flag . . .
sti ;documentation says this reqld.
cmp ax,1608h ;Enhanced mode loaded.
jne nexttry
mov cs:winloaded,l
jmp SHORT go2F
nexttry:
cmp ax,1605h I-test if Win is loading
jne notload
cmp cx,o *this must always be 0, else error.
jne goerror2F ’
mov cs:winmode,dl ;bit-0=0 if Enhanced mode.
test dl,l ;test bit-0
jnz standardload
;..................................................
;inserts our vxd into vxd chain (see my book, Appendix D)
mov word ptr cs:[instdata.SIS Next Ptr],bx
mov word ptr cs: [instdata.SISINextIPtrl [21,es
push cs
POP es ;chain, with es:bx ptg to our instdata
lea bx,InstData ;structure (our VxD data structure).
;...............
standardload:
jmp SHORT go2F
;..........
notload:
cmp ax,1606h I*test if Win is unloading.
jne notunload
mov cs:winloaded,O
mov cs:dpmiloaded,O
jmp SHORT go2F
;.....*....
notunload:
cmp ax,lCOBh ;used for tsr registration with windows.
jne giveitanothergo
jmp dorego
;.....
giveitanothergo:
cmp ax,1687h
je go2F *otherwise will get in endless loop!
cmp cs:dpmiloaded:O
jne go2F ;for all other cases, exit.
; . . . .
DOS-Win Transitions 339
Getting it Together
Testing the \TSR2WIN directory on your version of the Companion Disk may
programs contain the executables, in which case you can run them
immediately. You will need to place WINAPP.EXE into C:\, i.e.,
the root directory of the C: drive. VDEMOD.EXE can be
anywhere. From the DOS prompt, not a DOS box inside
Windows, go to the directory that has VDEMOD.EXE and run it
by typing “VDEMOD”. Then type “WIN” to load Windows.
340 Windows Assembly Language & Systems Programming
Preamble
Other Chapter 12 shows how a 16-bit Windows application (WinApp)
chapters can access 32-bit ring-0 code. Chapter 13 shows how to construct
a “pure” 32-bit ring-3 WinApp. Chapter 14 shows how DOS
applications (DOSApps), Winapps, and Virtual Device Drivers
(VxDs) can communicate. In Chapter 14, the communication is
established by the DOSApp while Windows loads.
Much of this book has described how BIOS and DOS interrupts,
plus the interrupt extensions (31h and 2Fh in particular) provided
by Windows, can be used. However, this is all from the viewpoint
of a 16-bit DOSApp or WinApp.
Execution of an interrupt causes a processor exception, and the
Windows handler is in most cases in a 16-bit segment. Therefore,
a 32-bit WinApp cannot use the interrupt services, even though
they are there and likely to stay there for future versions of
Windows.
Just as Chapter 12 shows an application going from a 16-bit
segment to a 32-bit segment (called ~hunking), it is necessary for a
native 32-bit WinApp to thunk down to 16-bits to use the
interrupts! Thunking is introduced in Chapter 8.
342 Windows Assembly Language h Systems Programming
This chapter This chapter is a mixed bag. I have likened the learning process to
climbing a ladder, as illustrated in Figure 15.1.
This book is intended to satisfy all the alternative needs of the
pondering man, sitting with hand to mouth. It is the nuts and
bolts, not the latest high-level techniques such as programming
using visual 00 components in Borland’s C++Builder. Move onto
those tools if you want, and you may well do, to produce major
applications. You may also move deep into VxDs, and again, I’ve
led you to the point where you can jump in - to the “brink” so to
speak.
I also make no apologies for focusing strongly on 16-bit
programming and the software interrupt services, as the needs of
our pensive man in Figure 15.1 will remain valid for many years to
come.
What I do need to do in this last chapter is fill in a few gaps and
make some suggestions. After that, go where you will . . .
Advanced Systems Programming 343
4.3G
Reference You may well wonder where I got these addresses from - the
sources source is the Soft-ICE/W User’s Manual. The address ranges
quoted here are what Windows 3.x currently uses, or so I’m led to
believe, but they are not guaranteed. Incidentally, Soft-ICE/W is a
specialised Windows debugger, for Enhanced mode only, that is a
resident program and can be popped up at any time. It is about the
only tool available for debugging virtual device drivers and similar
tricky code. It is sold by NuMega Corporation.
Specific information on Windows 95 addressing should be in the
latest Soft-ICE/W manual, and another reference is Inside
Windows 95 by Adrian King, Microsoft Press, USA, 1994.
32-bit The system VM has just one LDT, and all the 16-bit WinApps
WinApps have one set of pages tables. Each 32-bit WinApp has its own set
of page tables. Therefore, each 32-bit WinApp can be mapped to
physical memory totally independently of any other application.
They sit in linear address range 2G to 4G, but of course big chunks
of the linear address range map back to the same physical memory
as other WinApps, DOSApps, and Windows.
Reference The best places to look for extreme detail on this mapping is
books Unauthorized Windows 95 by Andrew Schulman, IDG Books,
USA, 1994, and Windows 95 Systems Programming Secrets by
Matt Pietrek, IDG Books, 1995.
The same selector value is loaded into DS, ES, FS, and GS, and it
is extremely interesting that the type of selector has the expand
down limit of lM, that is, must be greater than 1M. This prevents
data accesses into the first 1M of linear memory, that maps in the
DOS VM.
. . . anda The above description of linear addresses that map to physical are
DPMI not the only method for getting at physical memory from Protected
service mode.
There is a DPMI function that performs mapping between a linear
address and a physical address: function 0800h’ (Physical Address
Mapping). You supply it with a (32-bit) physical address and it
will give you a (32-bit) linear address. You could then use
function 0007h (Set Segment Base Address) to put the linear
address into a descriptor. Of course, the descriptor would have to
have been previously created, for example, by function OOOOh.
Curiously, function 0800h is not recommended for addressing
below physical lM, I presume because there are other DPMI
functions for that purpose.
Mapping Windows does set aside other parts of the linear address space for
between special purposes. For example, the DOS VMs are located at linear
system VM address range:
and DOS
VMs 8100 0000 to FFFF FFFF
If you’ve been following this with an attentive mind, you may see
a problem here - won’t each DOS VM be in its own completely
isolated virtual address space? Yes, but this is an example of
where Windows maps different virtual addresses to the same
physical place. Thus, from the system VM you can use the above
address range to access the DOS VMs, just as though they exist
within the system VM.
Another reserved area in the VM is the range:
8000 0000 to 803F FFFF
where the virtual device drivers are kept.
We lump a DOS V86 VM and its Protected mode together as one
VM, so each VM has its own attached Protected mode and hence
The Windows/DOS/DPMI
Relationship
This section develops further the relationship between DOS and
windows. What are the extensions to DOS provided by Windows?
Just what is the relationship between the Windows kernel and the
DPMI host?
/NT-2//r You should remember that the DPMI INT-3 1 h functions are only
versus available when the CPU is in Protected mode, not while it is in
INT-3lb V86 mode. However, the INT-2Fh extensions are available in
V86 (Enhanced) Real mode and Standard Real mode. Also don’t
forget that V86 and Protected mode overlap, so you can readily
address all of the first 1M of the VM from Protected mode (though
to execute Real mode code you must perform the necessary DPMI
function to transfer the CPU to Real mode, and vice versa).
Anatomy of a VxD
Changing the subject somewhat, VxDs are Microsoft’s preferred
direction for access to the hardware. Chapter 14 introduces the
VxD, and gives references. I recommend that if you want to delve
deeply into VxDs, you purchase a specialised book. However, it is
appropriate for me to explain a little more about how the example
VxD in directory \TSR2WIN on the Companion Disk works.
The VxD is VDEMOD.ASM, and it assembles and links to
VDEMOD.EXE. Note the extension .EXE, rather than .386 (that
most VxDs use). This relates back to how the VxD is used in
Chapter 14; that is, it is invoked from the DOS prompt prior to
Windows loading, which executes the stub TSR2WIN.EXE. Note
that TSR2WIN.EXE was inserted into VDEMOD.EXE by the
linker, so it is not a separate program. Note that I have created a
Make tile to rebuild everything, called BUILDALL.MAK. This is
designed for NMAKE.EXE and uses the /A switch.
For now, however, I am interested only in the VxD.
VXD tools To assemble and link the example VxD requires the DDK or
VxD-Lite. More specifically, the following files are required:
DEBUGJNC, SHELLJNC, VMM.INC, LINK386.EXE,
LINK4.EXE, MAPSYM32.EXE, MASMS.EXE, ADDHDR.EXE
These are not the standard LINK and MASM version 5 - they are
special versions.
Make file
I’ll start by looking at the Make file:
VDEMOD.MAK
This Make file build VDEMOD.ASM into VDEMOD.EXE, and puts
in the dos stub TSR2WIN.EXE....
Note that VDEMOD.EXE does not have to be in the windows
SYSTEM directory.
The dos stub is to be executed from the autoexec.bat file.
Note that the Include files are in the path shown below...
change if necessary. masm5.exe, link386.exe, addhdr.exe,
mapsym32.exe must
all be in the search path . . . or put in current directory.
to run: NMAKE /A VDEMOD.MAK
(there is also a BUILDALL.MAK)
I have put a path of c:\vxd for the .INC files, but
replace as necessary.
comment this definition out with a "#", if building a
non-debugging version
Debug=-DDEBUG
352 Windows Assembly Language & Systems Programming
all : VDEMOD.exe
.asm.obj:
masm5 -p -w2 -Mx $(Debug) -Ic:\vxd $*;
.asm.lst:
masm5 -1 -p -w2 -Mx $(Debug) -Ic:\vxd $*;
VDEMOD.obj : VDEMOD.asm c:\vxd\debug.inc c:\vxd\vmm.inc
OBJS = VDEMOD.obj
VDEMOD.exe: VDEMOD.def $(OBJS)
link386 @VDEMOD.lnk
addhdr VDEMOD.exe
mapsym32 VDEMOD
LIBRARY VDEMOD
DESCRIPTION 'Barry Kauler VxD for Microsoft Windows'
STUB 'TSR2WIN.EXE'
EXETYPE DEV386
SEGMENTS
LTEXT PRELOAD NONDISCARDABLE
-LDATA PRELOAD NONDISCARDABLE
-1TEXT CLASS 'ICODE' DISCARDABLE
-1DATA CLASS 'ICODE' DISCARDABLE
-TEXT CLASS 'PCODE' NONDISCARDABLE
IDATA CLASS 'PCODE' NONDISCARDABLE
EXPORTS
VDEMOD-DDB 631
The above two files are the standard red tape, adaptable to other
VxDs. However, one special difference is the TSR2WIN.EXE
stub. For other VxDs you can replace this with a dummy
do-nothing stub, and also you may prefer to generate a VxD with a
.386 extension.
The listing of VDEMOD.ASM follows, broken up with comments:
TITLE VDEMOD -
*example skeleton VxD, adapted from a skeleton in
IMicrosoft's DDK.
; It is possible to monitor any I/O port, and allow or
;disallow it's use. Install_IO_Handler achieves this.
.386p
.XLIST
INCLUDE VMM. Inc ; supplied with DDK (or VxD-Lite)
Advanced Systems Programming 353
Debug .Inc ;/
Shell .inc ;I
;...........................................................
Declare Virtual Device VDEMOD, 3, 0, VDEMOD Control, \
Bndefinea-Device ID,
- VMM -INIT -ORDER,,
;..,........................................................
;local d a t a . . .
VxD LOCKED DATA_SEG
Device-Name D B “VDEMOD ”,0
354 Windows Assembly Language & Systems Programming
VDEMOD Owner DD ?
VxD - LO&ZD_DATA_ENDS
;...........................................,..........
iinitilisation code...
VxD - ICODE_SEG
BeginProc VDEMOD_Device_Init
beep:
push eax
mov al,OB6h I*turn on loudspeaker
out 43h,al
mov bx,OSCSh
mov al,bl
out 42h,al
mov al,bh
out 42h,al
in a1,61h
or al,3
out 61h,al
POP eax
I-firstly, let's hook anything (port 200h) and show a
;generalised handler...
mov edx, 2oOh
mov esi, OFFSET32 My_VDEMOD_Hook
VMMCall Install_IO_Handler
;now, let's prevent printing...
mov edx, 3BDh *lpt status
mov esi, OFFSET32 My_VDEMOD_lpt_Hook'
VMMCall Install_IO_Handler
mov edx, 379h elpt status
mov esi, OFFSET32 My_VDEMOD_lpt_Hook ’
VMMCall Install_IO_Handler
mov edx, 27911 I*lpt status
mov esi, OFFSET32 My_VDEMOD_lpt_Hook
VMMCall Install_IO_Handler
, . . . . .
mov edx, 3BEh -1pt control
mov esi, OFFSET32 ctrl_VDEMOD_lpt_Hook ’
VMMCall Install_IO_Handler
mov edx, 37Ah ,elpt control
mov esi, OFFSET32 ctrl_VDEMOD_lpt_Hook
VMMCall Install_IO_Handler
mov edx, 27Ah Imlpt control
mov esi, OFFSET32 ctrl VDEMOD_lpt_Hook
VMMCall Install_IO_HandTer
xor eax, eax
mov VDEMOD_Owner, eax ; no current owner
EndProc VDEMOD_Device_Init
VxD_ICODE_ENDS
VxD_LOCKED_CODE_ENDS
;........................~..................................
;the hooked ports get redirected here...
VxD_CODE_SEG
BeginProc My_VDEMOD_Hook
*firstly, resolve contention...
’ pushad ; save regs
mov eax, VDEMOD_Owner ; get previous owner
cw eax, ebx ; same as current owner?
jz short process_io ; yes, just handle it
or eax, eax ; was there an old owner?
jz short new-owner ; no
mov esi, OFFSET32 DeviceName
_ .
VxDCall Shell_Resolve_Contentlon
jc short dont_process ; hmmm, couldn't resolve
ENDIF
ret
EndProc My_VDEMOD_Hook
;.........................................................
BeginProc My_VDEMOD_lpt Hook
;we come here if the printer status ports are trapped...
;set bit-3,7, clear bit-4...
pushad
popad
Dispatch Byte IO Fall-Through, <SHORT VDEMOD lpt Out>
in &,dx- * do-rear in
mov a1,10101000b *this should stuif-up printing!
ret 1 (busy,out-Of-paper,offline,error)
VDEMOD_lpt_Out:
out dx,al ; do real out
ret
EndProc My_VDEMOD_lpt_Hook
;...........................................................
BeginProc ctrl VDEMOD_lpt_Hook
;we come here if the printer control ports are trapped...
pushad
popad
Dispatch-Byte-10 Fall-Through, <SHORT ctrl lpt Out>
in al,dx ; 30 real in
; mov al,0 ;
ret
ctrl lpt_Out:
out dx,al * do real out
’ mov a1,00001000b ;bit-3=l,printer nbt selected.
out dx,al
ret
EndProc ctrl_VDEMOD_lpt_Hook
;...........................................................
VxD CODE ENDS
E-JD -
To take this further, you need a good book with all the details on
the VMM services. Install_IO_Handler for example, hooks a port
and redirects to a callback routine. The routine is entered with
EBX = current VM, ECX = type of I/O, EDX = port number, and
EAX = output data (if type of I/O is output). When the callback
exits, if the type of I/O is input, the value placed in EAX is the
input value.
The book should also explain the VMM.INC macros and data
structures, such as the Dispatch-Byte-10 macro used above.
358 Windows Assembly Language & Systems Programming
Maximum Productivity
Now for something completely different . . .
True visual The main point is that the most productive programming language
programming is not C or C++, nor is it some augmentation using class libraries
and front-end code generators. Nor is it Pascal, Basic, Fortran,
Cobol, etc.
The way to go is visual programming, and that does not mean
Visual Basic or Visual C++. These two products are not visual
programming languages (VPLs), as they are still text-based
languages. Borland’s Delphi and C++Builder move slightly further
toward true visual programming, but it is still mostly the user
interface only that is developed visually.
If you want a definition of VPLs, and you have Internet access,
l o o k a t t h e f r e q u e n t l y - a s k e d - q u e s t i o n s ( F A Q ) file on
COMP.LANG.VISUAL, a newsgroup. Much to the dismay of the
people who started this newsgroup, very few programmers know
what a VPL is, and COMP.LANG.VISUAL is bombarded with
mail about Visual C++ and Visual Basic.
VPLs potentially can increase your programming productivity ten
times or more, and probably the best of all is LabView, developed
by National Instruments Corp.
Advanced Systems Programming 359
LabView Programming
LabView is at least as powerful as any text-based language and
has evolved, since 1986, into a very mature and sophisticated
product. In a nutshell, LabView is an environment in which you
can very rapidly develop applications with a single line of coding.
LabView applications can be recompiled, unchanged, to run on
Macintosh, PC with Windows, Windows NT, and Sun
workstations, with more to come.
The speed with which you can put programs together has to be
seen to be believed, and phenomenal productivity improvements
360 Windows Assembly Language h Systems Programming
Dataflow LabView is based upon a datafow model, but there are enough
visual control-flow mechanisms built-in to get around the limitations of
programming the pure dataflow concept. Dataflow means that you construct
programs by drawing data paths between icons on the screen, and
execution follows the data paths.
Figure 15.3 shows this. Look at the diagram at the bottom of the
figure, and you will see how “icons” have been interconnected, to
form the program, or “diagram”. Control structures are also
shown, such as a while-loop and a case-structure. Note that the
case-structure is very efficient in its use of screen real-estate; cases
are superimposed, like a deck of cards, with a simple selection-box
on top, for flicking through them.
This leads to an interesting consideration - a classical problem
with visual programming environments is that they tend to very
rapidly run out of screen, but LabView is the most compact and
“screen efficient” of any VPL that I have encountered.
Just imagine being able to rapidly wire-up a program, without
having to remember any text-based syntax, or even how to use
pointers.
Disadvantages of LabView
Speed & size Ok, ok, the negative points. The power comes at a price, which is
inefficiency of the generated code. LabView programs are HUGE,
and they don’t “run at the speed of compiled C”, as National
Instruments publicity would have you believe. Maybe you can
contrive such a situation, but practical programs would be lucky to
achieve half the speed of an equivalent C program.
However, “equivalent” is a difficult issue, because LabView
applications have so much extra stuff built-in. Compared with one
of LabView’s main competitors, HP-VEE developed by Hewlett
Packard, the former is much faster and, in my personal opinion, far
more sophisticated and mature.
Advanced Systems Programming 361
Valve Deadband
Lag (min]
Update w (T) Process Variable [X
Manipulated Variable [Outpu...
Process Gain
Process Load
Process Deadtime
Noise (%)
362 Windows Assembly Language & Systems Programming
Narrow target The next negative noint is that LabView is optimised for use by
1
market engineers and scientists, and this is reflected throughout its design.
It is absolutely superb for data acquisition, control, mathematic
processing, and image work. It was never really intended to be a
commercial high-volume product, so you don’t use it to develop
high-volume applications.
You use LabView for custom, one-off, or low-quantity jobs.
However, since people have discovered that LabView is good for
just about everything and is very easy to use (once you get used to
the dataflow concept), the original market domain has tended to
diversify. People are now selling stand-alone applications, to a
larger market - in particular, this has happened since LabView
was released on the PC in 1993.
Integration with Assembly
Since LabView is optimised for engineering and scientific
applications, its strengths are on the I/O side, which ties in
strongly with the kind of things you normally use assembly for.
So if you use LabView to control all the machinery in your
factory, and you also want to do some low-level optimisation, how
can you integrate assembly into the picture?
Front panel Have another look at Figure 15.3. Each diagram, or code-module,
& top-icon has a front panel, which is a window through which all inputs and
outputs travel. Note, however, that this front panel may or may
not appear at execution-time. Think of it as a handy development
aid, since it gives you total control over the diagram for testing
purposes and interactive monitoring while executing.
Look higher, and you will see that there is an icon, that has input
and output terminals, which all go to and from the diagram, via the
front panel. This icon makes the diagram into the equivalent of a
subroutine or procedure and is a software object that can be reused
with the greatest of ease.
An important point to note about Figure 15.3 is that you are seeing
it in black and white, when in fact, it is in full color, and all wires
clearly show the data types they carry. Furthermore, LabView will
not allow a connection if the data types are incompatible - also
note that most built-in LabView icons are polymorphic, meaning
that they will accept almost any data type.
Any one of these icons could be a program that has been written in
another language.
Note also, that a small help window automatically shows you the
meaning of each input and output terminal on an icon as you wire
it up.
Dynamic What if you want use your own brand of assembler, or any
link assembler for that matter? There is a way. LabView can call any
libraries dynamic link library (DLL) function - Sun workstations and
Macintoshes also have the equivalent to the DLL mechanism.
This is great, as you can put all your hardware-dependent
low-level code into a DLL and write a version for each platform
- the rest of LabView will automatically work on any of the
platforms.
A standard skeleton DLL, written in assembly language, is on the
Companion disk in directory \LV-DLL.
Reference To find out more about LabView, National Instruments has a Web
source page at:
http://www.natinst.com/
Layout Programming
There is another, easy, way to generate a DLL - its name is
Layout.
Layout is another VPL. Comparing Layout with LabView is like
comparing chalk and cheese, but there is a method in my madness.
Layout had its origins on DOS, and Layout for DOS still exists.
Layout for Windows made its appearance in the world in
November 1994.
I 45.9 1
II
I
X
I
H
El t
II=1 ctitor
Start Of Program
0
Assembly Each of the boxes in Figure 15.4 is called a blackbox, and, as with
DLL LabView, they can be developed in another language. In fact,
Layout blackboxes are simply DLLs!
That’s it: write a DLL, register it with Layout, and it becomes a
blackbox that you can put into your flowchart, just like any other
blackbox.
Figure 15.5 gives an overall picture. Look at the figure, and you
can see how assembly language fits in with both Layout and
LabView.
One very interesting possibility that you can see in Figure 15.5 is
that Layout can generate DLL output. That is, any program that
you construct inside Layout can, simply by selecting “Make
blackbox” from the menu, generate a DLL.
DLLs are mighty handy, because they can readily be used by
various development environments, including LabView. Because
Layout is a totally open environment (with no run-time
distribution licencing required, unlike LabView), you might find it
useful as an adjunct for use with LabView.
369
370 Windows Assembly Language h Systems Programming
LOOP
None . . .
LOOPE/LOOPZ short
Loop equal/zoom
None . .
LOOPNE/LOOPNZ short
Loop while not equal/not zero
None ...
LSL
Load segment limit
LEA 16 reg, 16 mem ZF ..
Load effective address to
register LSS
None ..
Load pointer to SS
None
LEAVE
Leave procedure LTR
None ..
Load task register
None
LES 16 reg, 32 mem
Load pointer to ES:reg MOV 16 reg, segreg
None .. MOV accum,mem
MOV mem,accum
LFS MOV mem, imm
Load pointer to FS:reg MOV mem, reg
None MOV mem, segreg
MOV reg, imm
LGDT MOV reg, mem
Load global descriptor table MOV reg, reg
None ..
MOV segreg, 16 mem
MOV segreg, 16 reg
LGS Move
Load pointer to GS None ...
None
MOVS (repeat)deststr,sourcstr
LIDT MOVS deststr,sourcestr
Load IF descriptor table Move string
None .. ...
None
LLDT MOVSX
Load local descriptor table Move with sign extension
None ..
None
LMSW MOVZX
Load machine status word Move with zero extension
None ..
None
LOCK MUL 16 mem
Bus lock prefix MUL 16 reg
None ... MDL 8 mem
MUL 8 reg
LODS (rep) sourcestr Multiply
Repeat load byte/word/dword OF, CF
None ...
NEG mem
LODS sourcestr NED reg
Load byte/word/dword Change sign
None ... OF,SF,ZF,AF,PF,CF . . .
LOOP short NOP
Instruction Summary 373
RCL/RCR/ROL/ROR mem,l
RCL/RCR/ROL/ROR reg, 1
rotate thru CF left/CF
right/rot. left/right
OF, CF
374 Windows Assembly Language & Systems Programming
STC/STD/STI
Set'CF/DF/IF
CF = I/DF = I/IF = I ...
STR
Store task register
None
SUB accum, imm
SUB mem, imm
SUB mem, reg
Instruction Summary 37.5
FLDLN2
Load log (e^2)
Format IE
Instruction FLDPI
Description Load pi
IE ...
Flags affected 8712871387
FLDZ
Load +O.O
IE
FINCSTP
Increment stack pointer FMUL dest,source
None Multiply real
IE,DE,ZE,OE,UE,PE
FINIT/FNINIT
Initialise processor FMULP dest,source
None Multiply real & pop
IE,DE,ZE,OE,UE,PE
FIST dest
Integer store FNOP
IE,PE ...
No operation
None . . .
FISTP dest
Integer store & pop FPATAN
IE,PE Partial arctangent
UE,PE
FISDB source
Integer subtract FPREM
IE,DE,OE,PE ... Partial remainder
IE,DE,UE . . .
FISUBR source
Integer subtract reversed FPREMl
IE,DE,OE,PE ...
Partial remainder (IEEE)
FLD source
Load real FPTAN
IE,DE ...
Partial tangent
IE,PE
FLDl
Load +l.O FRNDINT
IE ...
Round to integer
IE,PE ...
FLDCW source
Load control word FRSTOR source
None ...
Restore saved state
None ...
FLDENV source
Load environment FSAVE/FNSAVE dest
None ...
Save state
None . . .
FLDL2E
Load log (2*e) FSCALE
IE ...
Scale
IE,OE,UE ...
FLDLZT
Load log (2^10) FSETPM
IE . . . Enter Protected mode
None
FLDLG2
Load loq (lOA FSIN
IE - Sine
Instruction Summary 3 77
FWAIT
Wait while 8087 is busy
FSINCOS None . . .
Sine & cosine
FXAM
Examine stack top
FSQRT None . . .
Square root
IE,DE,PE FXCH dest
Exchange registers
FST dest IE . . .
Store real
IE,OE,UE,PE FXTRACT
Extract exponent & Significand
FSTCW/FNSTCW dest IE . . .
Store control word
None FYL2X
ST(l)*log,(ST(O))
FSTENV/FNSTENV dest PE ...
Store environment
None FYL2XPl
ST(l)*log,(ST(O)+l)
FSTP dest PE ...
Store real & pop
IE,OE,UE,PE
FSTSW/FNSTSW dest
Store status word
None ...
FSUB dest,source
Subtract real
IE,DE,OE,UE,PE .
FSUBP dest,source
Subtract real & pop
IE,DE,OE,UE,PE ...
FSUBR dest,source
Subtract real reversed
IE,DE,OE,UE,PE ...
FSUBRP dest,source
Subtract real reversed & pop
IE,DE,OE,UE,PE ...
FTST
Test stack top against +O.O
IE,DE ..
FUCOM
Unordered compare
FUCOMP
Unordered compare & pop
FUCOMPP
Unordered camp. & pop twice
Keyboard Tables
Virtual Keys
379
380 Windows Assembly Language & Systems Programming
:::::::::::::::::::::::::::::::::::::::
,_, ,, ::: ::::::::::::. . . . . . . . . . . . . . . . . . . . . . . . . .,
:,:,:,:,:,:,:,:,:.:,:,::::::::
,A....:.:.:.:.:.:.:.:.:.:.,.:.
::::::::::y:::::::::::::: :: :.:.: .,.,.,.,.,.....,...,..... _,
~~~
1A Unassigned
(K-ESCAPE LB ESCAPE key
lC-1F Reserved for Kanji
VK_SPACE 20 SPACEBAR
VK_PRXOR 21 PAGE UP key
VK_NEXT 22 PAGE DOWN key
a-END 23 END key
VK_HOME 24 HOME key
VK_LEFT 25 LEFT ARROW key
VK_UP 26 UP ARROW key
VK_RIGHT 27 RIGHT ARROW key
VK_DOWN 28 DOWN ARROW key
VK_SELECT 29 SELECT key
VK_PRTNT 2A OEM specific
VK_EXECUTE 2B EXECUTE key
VK_SNAPSHOT 2c PRINTSCREEN key
VK_INSERT 2D INSERT key
m-DELETE 2E DELETE key
VK_HELP 2F HELP key
VK_o to w-9 30-39 O-9 (same as ASCII)
3A-40 Unassigned
VK_A to VK_Z 41-5A A-Z (same as ASCII)
SB-5F Unassigned
VK_NUMPADO 60-69 Numeric keypad
to VKJUMPAD9
VK_MULTIPLY 6A Multiply key
VI-ADD 6B Add key
VK_SEPARATER 6C Separater key
VK_SUBTRACT 6D Subtract key
VK_DECIMAL 6E Decimal point key (.)
VK_DIVIDE 6F Divide key
VK_Fl to VK_16 70-7F Function keys O-16
80-87 OEM specific
88-8F Unassigned
VK_NUMLOCK 90 NUM LOCK key
Keyboard Tables 381
~~~~
. . . . . . . . .
VK_OEM_SCROLL
92-B9 Unassigned
VK_OEM_l BA Keyboard specific
W-OEM-PLUS BB Plus key
VK_OEM_COMMA BC Comma key
VK_OEM_MINUS BD Minus key
VK_OEM_PERIOD BE Period key
VK_OEM-2 BF Keyboard specific
VK_OEM-3 co Keyboard specific
C 1 -DA Unassigned
W-OEM-4 DB-DF Keyboard specific
to VK_OEM-8
EO-El OEM specific
VK_OEM_I 02 E2 <> or \ (non-USA kbrd)
E3-E4 OEM specific
E5 Unassigned
E6 OEM specific
E7-E8 Unassigned
E9-F5 OEM specific
F6-FE Unassigned
382 Windows Assembly Language & Systems Programming
Extended ANSI
0 1 2 3 4 5 6 7 8 9
301 ! ,, # $ % & 1
SP
* + 0
40 ( > per ! 1
50 2 3 4 5 6’ 7 8 9 9
60 < = > ? @ A B C D E
70 F G H I J K L M N 0
80 p Q R S T U V w X Y
/\ .
90 z 1 \ 1 a b C
100 d e f g h j k 1 m
110 n 0 P 9 r t U V W
120 X Y Z { I
130
‘ “ 3,
140
150 - - TM )
..
160 # E Y 9
- 2
170 --I f
180 I ‘/4
1
190 A A A L AZ
..
200 B E i I D
210 6 8 6 0 ti
220 P I3 6 ?i ii
230 ae c 2. c c i i
240 6 ii b 6 6 A 0
2501 il ii ii 9 Y
Cctdes itTe in decimal. Example: 162 = f, sp = space. per = period.
Keyboard Tables 383
IBM ASCII
Dec.' 0 1 2 3 4 5 6 7 8 !
30 SP ! II # $ % & '
40 ( 1 * +, - per / 0 1
50 2 3 4 5 6 7 8 9 : .
60 < = > ? @ A B C D ;
70 F G H I J K L M N C
80 P Q R S T U V W X E
90 z A \ b c
r \ 1 - a
100 d e f g h i j k 1 n
110 n 0 p q r s t u v u
120 x y z { 1 ) -
130
140 a 1E 6 ij cj
150 Q lj y ij 0 f$! f f
160 sp. i 6 ti fi R 2 Q L r
385
386 Windows Assembly Language & Systems Programming
TRANSLATION SERVICES
0300h Simulate Real Mode Interrupt * *
0301h Call Real Mode Procedure with Far Return Frame * *
0302h Call Real Mode Procedure with Int. Return Frame * *
0303h Allocate Real Mode Callback Address * *
0304h Free Real Mode Callback Address * *
0305h Get State Save/Restore Addresses * *
0306h Get Raw CPU Mode Switch Addresses * *
cause the descriptor to reference a linear address address range outside that
range outside that allowed for DPMI clients.) allowed for DPMI clients)
Int 31h Function OOOAh Int 31h Function OOODh
Create Alias Descriptor Allocate specific LDT Descriptor
Creates a new LDT data descriptor that has the Allocates a specific LDT descriptor.
same base and limit as the specified descriptor. Call with.
Call with: AX = OOODh
= OOOAh BX = selector
g = selector Returns:
Returns: if function successful
if function successul CF = clear
CF = clear and descriptor has been allocated
AX = date selector (alias) if function unsuccessful
if function unsuccessful CF = set
CF = set AX = error code
AX = error code 8011 h descriptor unavailable
801 I h descriptor unavailable (descriptor is in use)
8022h invalid selector 8012h invalid selector
(references GDT or beyond the
Int 31h Function OOOBh LDT limit)
Get Descriptor
Copies the local descriptor table (LDT) entry Int 31h Function OlOOh
for the specified selector into an S-byte buffer. Allocate DOS Memory Block
Call with: Allocates a block of memory from the DOS
= OOOBh memory pool, i.e. memory below the 1 MB
:: = selector boundary that is controlled by DOS. Such
ES:(E)DI = selector:offset of 8 byte buffer memory blocks are typically used to exchange
Returns: data with Real mode programs, TSRs, or device
if function successful drivers. The function returns both the Real
CF = clear mode segement base address of the block and
and buffer pointed to by ES:(E)DI contains one or more descriptors that can be used by
descriotor if function unsuccessful
s
Protected mode applications to access the block.
zz
set Call with:
= error code = OlOOh
802231 invalid selector E = number of (16-byte)
paragraphs desired
Int 31h Function OOOCh Returns:
Set Descriptor if function successful
Copies the contents of an 8-byte buffer into the CF = clear
LDT descriptor for the specified selector. AX = Real mode segment base
Call with: address of allocated block
= OOOCh DX = selector for allocated block
;; = selector if function unsuccessul
EX:(E)DI = selector:offset of CF = set
8-byte buffer containing AX = error code
descriptor 0007h memory control blocks
Returns: damaged (also returned by
if function successful DPMI 0.9 hosts)
CF = clear 0008h insufficient memory
if function unsucessful (also returned by DPMI 0.9
= set hosts).
g = error code BX = size of largest available block
8021 h invalid value (access in paragraphs
rights/types byte invalid)
8022h invalid selector
802511 invalid linear address
(descriptor references a linear
392 Windows Assembly Language & Systems Programming
L-!
Heading
Description
Call with
RetWiZS
Returns:
if function successful
CF = clear
AX = watchpoint status
Bit Significance
0 0 = watchpoint has not
been encountered
I = watchpoint has been
encountered
1 - 15 reserved
if function unsuccessful
CF = set
AX = error code
8023h invalid handle
Function Description
1600h Get Enhanced-Mode Windows Installed State
1602h Get Enhanced-Mode Windows Entry-Point Address
1605h Windows Initialization Notification
1606h Windows Termination Notification
1607h Device Call Out
1608h Windows Initialization Complete Notification
609h Windows Begin Exit
680h Release Current VM Time-Slice
681h Begin Critical Section
68211 End Critical Section
683h Get Current Virtual Machine ID
The specific reference for Appendix D is the Device Driver Adaptation Guide, Microsoft
Device Development Kit, version 3.1, 0 1992 Microsoft Corporation. All rights reserved.
Reprinted with permission from Microsoft Corporation.
401
402 Windows Assembly Language & Systems Programming
Function Description
r
The device driver or TSR must preserve all
registers and restore the original values before
returning. The only exceptions to this rule are
Heading changes made to the BX, CX, DS, ES, and SI
registers as a result of following the previous
Description procedure.
call with Enable/Disable Virtual 8086 Mode Callback
ReCurns Function
[Cornmen fsl Some device drivers and TSRs, such as
expanded memory emulators, switch the
processor to virtual 8086 mode. Because 386
Termination Notification (Interrupt 2Fh Enhanced-mode Windows cannot start
Function 1606h) and returns to MS-DOS. successfully while the processor is in this mode,
If the device driver or TSR can run in the any device driver or TSR that switches to
Windows environment, it should do the virtual 8086 mode must either switch back to
following: Real mode or supply the address of a callback
1 Call the next device driver or TSR in function that can switch between real and
the Interrupt 2Fh chain to allow all virtual 8086 modes.
device drivers and TSRs in the chain Windows uses the callback function to disable
an opportunity to respond to this virtual 8086 mode before Windows itself enters
function. Protected mode. Windows calls the callback
2 Upon return from the interrupt chain, function again to enable virtual 8086 mode after
carry out the following actions: Windows exits Protected mode. Windows calls
a Free any extended memory. The the callback function using a far call instruction,
device driver or TSR takes this and it specifies which action to take by setting
action only if it has previously the AX register to 0 or I.
allocated extended memory To disable virtual 8086 mode. Windows sets the
using the Extended Memory AX register to 0, disables interrupts, and calls
Soecitication (XMSI interface. the callback function. The function should
b Switch the processor to real switch the processor to Real mode, clear the
mode, or set the DS:SI register carry flag to indicate success, and return. If an
pair to the address of an error occurs, the function sets the carry flag and
Enable/Disable Virtual 8086 returns. Windows checks the carry flag and
Mode callback function. The terminates if it is set.
device driver or TSR takes this To enable virtual 8086 mode, Windows set the
action only if it has previously AX register to I, disables interrupts, and calls
switched the processor to virtual the callback function. The function should
8086 mode. If standard-mode switch the processor to virtual 8086 mode, clear
Windows is starting, the device the carry flag, and return. If an error occurs, the
driver or TSR must switch the function sets the carry flag and returns.
processor to real mode. The However, Windows ignores the carry flag, so if
callback function is permitted an error occurs no action is taken and the
for 386 Enhanced-mode nrocessor is left in Real mode.
Windows only. Whether an error occurs when enabling or
c Initialise a Win386_Startup_ disabling virtual 8086 mode. it is UD to the
Info Strut structure, and copy callback”function to display any error ‘message
the address of the structure to to the user. Also, the callback function must not
the ES:BX register pair. The enable interrupts unless an error occurs, and the
device driver or TSR carries out function sets the carry flag.
this action only if 386 A device driver or TSR supplies a callback
Enhanced-mode Windows is function by copying the address of the function
starting. to the DS:SI register nair when it orocesses the
3 Return (using the iret instruction) but Windows Initi&zati& Notification (Interrupt
without changing the CX register. 2Fh Function 1605h). Windows permits only
one callback function, so the device driver or
For more information about these procedures, TSR should first check to make sure that the DS
see the following comments: and SI registers are both zero. If they are
-
nonzero, the device driver or TSR should set the Interrupt 2Fh Function 1606h
CX register to a nonzero value and return, Windows Termination Notification
directing Windows to terminate without Notifies MS-DOS device drivers and TSRs that
starting. standard- or 386 Enhanced-mode Windows is
terminating. Windows calls this function as it
Initializing a Win386_Startup_Info_Struc terminates- allowing MS-DOS device drivers
Structure and TSRs that monitor Interruot 2Fh the
An MS-DOS device driver or TSR initializes a opportunity to prepare for leaving ihe Windows
Win3 86_Startup_Info_Struc structure to direct environment.
386 Enhanced-mode Windows to load the Call with:
virtual device and to reserve the instance data AX = 1606h
the device driver or TSR needs to operate in the DX = Specifies whether standard- or
Windows environment. The device driver or 386 Enhanced-mode Windows is
TSR is also responsible for establishing a chain terminating. 386 Enhanced-
of startup structures by copying the contents of mode Windows sets bit 0 to 0:
the ES:BX register pair to the Next_Dev Ptr standard-mode Windows sets.
member. It is assumed that any other device bit 0 to 1. Onlv bit 0 is used: all
driver or TSR in the Interrupt 2Fh chain will other bits resehed and
have set the ES:BX register pair to the address undefined.
of its own startup structure prior to returning. Return value:
Any device driver or TSR can use a Windows This function has no return value.
virt-ual device to help support its operation in Comments
the 386 Enhanced-mode Windows environment. Windows calls this function when the processor
To specify a virtual device, the device driver or is in Real mode.
TSR-sets ihe SIS Virt De; File Ptr member to
the address of th<vi&al device’; filename. The Interrupt 2Fh Function 1607h
device file is assumed to be in the Windows Device Call OUI
SYSTEM directory. The device driver or TSR Directs an MS-DOS device driver or TSR to
can also set the SIS_Reference_Data member to provide information to the calling virtual
specify additional data to be passed to the device. Although the BX register soecifies a
virtual device when loaded. device identifie;, other registe;s may be used to
Any device driver or TSR can reserve instance specify the action to take.
data for itself. Instance data is one or more Called with:
blocks of memory used by the device or TSR, = 1607h
and managed by-windows For device drivers i% = device identifier for a virtual
or TSRs loaded before 386 Enhanced-mode device
Windows starts, reserving instance data allows Return value:
the device drivdr or TSfto keep separate data The return value depends on the specific action
for each virtual machine. Whenever Windows requested.
switches virtual machines, it saves the previous Comments
VM’s instance data and loads the current VM’s this function typically is used by a virtual
instance data. If a device driver or TSR does not device to communicate with the driver or TSR
specify instance data, the same data is used for that explicitly loaded the virtual device. It is up
all virtual machines. to the virtual device to supply a correct device
A device driver or TSR reserves instance data identifier and any other parameters that specify
by appending an array of Instance_Item_Struc what action to take. It is UD to the driver or TSR
structures to the Win386 Startup_Info_Struc to monitor Interrupt 2Fh’ and respond to the
structure. The last structure% the array must be function appropriately.
set to zero. Each Instance_Item_Struc structure A virtual device can call this function at anv
specifies the starting address and size (in bytes) time, either in Real mode or after 386
of an instance data block. Enhanced-mode Windows has started.
The device driver or TSR must copy the address
of its startup structure to the ES:BX register pair tnterrupt 2Fh Function 1608h
before returning. Windows Initialization Complete Notification
Notifies MS-DOS device drivers and TSRs that
See also at end of this appendix: 386 Enhanced-mode Windows has completed
Win386_Startup_Info_Struc, its initialization. Windows calls this function
Instance_Item_Struc after it has installed and initialized all virtual
406 Windows Assembly Language & Systems Programming
The return value is OOh in the AL register if the other programs use this function to determine
function is supported. Otherwise, AL is which virtual machine is running. This is
unchanged (contains 80h). especially important for programs that
Comments independently manage separate data or
Only non-Windows programs should use execution contexts for separate virtual
Release Current VM Time-Slice; Windows machines.
applications should yield by calling the Call with:
WaitMessage function. A program can call this AX = 1683h
function at any time, even when running in Return value:
environments other than 386 Enhanced-mode The return value is the current virtual-machine
Windows environment. If the current identifier in the BX register.
environment does not support the function, the Comments
function returns and the program continues Each virtual machine has a unique, nonzero
execution. identifier. Although Windows currently runs in
Windows suspends the current VM only if there virtual machine 1, programs should not rely on
is another VM scheduled to run. If no other VM this. Windows assigns the identifier when it
is ready, the function returns to the program and creates the virtual machine, and releases the
execution continues. A program should call the identifier when it destroys the virtual machine.
function frequently (for example, once during Since Windows may reuse identifiers from
each pass of the program’s idle loop) to give previous, destroyed virtual machines, programs
Windows ample opportunity to check for other should monitor changes to virtual machines to
VMs that are ready for execution. ensure no mismatches.
Before calling this function, a program should
check that the Interrupt 2Fh address is not zero. interrupt 2Fh Function 1684h
Get Device Entrv Point Address
Interrupt 2Fh Function 1681h Retrieves the entry point address for a virtual
Begin Critical Section device’s service functions. MS-DOS device
Prevents Windows from switching execution drivers or TSRs typically use this function to
from the current VM to some other. MS-DOS communicate with virtual devices they have
device drivers and TSRs use this function to explicitly loaded.
prevent a task-switch from occurring. Call with.
Call with: AX = 1684h
AX = 1680h BX = device identifier (ID).
Return value. Return value:
This function has no return value. Ihe return value is the entry-point address
Comments :ontained in the ES:DI register pair if the
When a virtual machine is in a critical section, Function is supported. Otherwise, ES:DI contain
no other task will be allowed to run except to rero.
service hardware interrupts. For this reason, the Zomments
critical section should be released using End 4ny virtual device can provide service
Critical Section (Interrupt 2Fh Function 1682h) ‘unctions to be used by MS-DOS programs. For
as soon as possible. :xample, the virtual-display device provides
retvices that the Windows old application
Interrupt 2Fh Function 168211 )rogram uses to display MS-DOS programs in a
End Critical Section Nindow.
Releases the critical section previously started :t is the responsibility of the MS-DOS program
using Begin Critical Section (Interrupt 2Fh o provide the appropriate virtual-device
Function 168 lh). Every call to Begin Critical dentifier. The function returns a valid address if
Section must be followed by a matching call to he virtual device supports the entry point.
End Critical Section. MS-DOS programs call the entry point using a
Call with. ‘ar call instruction. The services provided by the
AX = 1682h virtual device depend on the device. It is the
Return value. .esponsibility of the MS-DOS program to set
This function has no return value. .egisters to values that are appropriate to the
;pecrtic virtual device.
Interrupt 2Fh Function 1683h :or versions of Windows prior to version 3.0,
Get Current Virtual Machine ID he program must set the ES:DI register pair to
Returns the identifier for the current virtual rero before calling this function.
machine. MS-DOS device drivers, TSRs, and
408 Windows Assembly Language & Systems Programming
which registers may be reset is undefined, but Interrupt 2Fh chain. A driver or TSR calls the
the set is restricted to those Sequencer and next handler, then sets this member to the
Graphics Controller registers that do not affect address returned by the handler in the ES:BX
the display. register pair. _
SIS Virt Dev File Ptr: Points to a
Interrupt 2Fh Function 4007h nulkerm%ated stri& that contains the name of
Disable VM-Assisted Save/Restore a 386 Enhanced-mode Windows virtual device
Directs the virtual-display device (VDD) to file. MS-DOS devices such as networks use this
discontinue notifying the VM application when to force a special 386 Enhanced-mode Windows
it needs access to video registers. virtual device to be loaded. If this member is
VM applications call this function when they zero, no device is loaded.
terminate. SIS Reference-Data: Specifies reference data
Call with: for ihe virtual device. This member, used only
Ax = 4007h when SIS Virt Dev File Ptr is nonzero, is
Return value: passed to the v&al-devke when it is
This function has no return value. initialized. The member can contain any value
Comments and often contains a pointer to some
This function directs the VDD to restore I/O device-specific structure.
trapping of unreadable registers and to SIS Instance Data Ptr: Points to a list of data
discontinue calling Save Video Register State to I% instanced, th% is, allocated uniquely for
and Restore Video Register State (Interrupt 2Fh each new virtual machine. If the member is
Functions 4005h and 4006h) when it needs zero, then no data is instanced. Each entry in the
access to the registers. Furthermore, the VDD list is an Instance Item Strut structure. The list
ignores any subsequent calls to Enter Critical is terminated with-a 32&t zero.
Section and Exit Critical Section (Interrupt 2Fh SIS_Opt_Instance_Data_Ptr: Points to a list of
Functions 4003h and 4004h). data to be optionally instanced. Available only
This function does not disable Notify if SIS_Version is 4.0.
Background Switch and Notify Foreground
Switch (Interrupt 2Fh Functions 400lh and Instance_Item_Struc
4002h).
Instance Item_Struc STRUC
DATA STRUCTURES 11s Ptr dd ?
IIS-Size dw ?
Win386_Startup_Info_Struc Instan~e_Item_Struc ENDS
A C
Access field 28,253,289 C++ binding 142
Accessing 32-bit data in halves 23,130 C++. interfacing with 147
Accessing data CALL/RET ” 38,40
Accessing physical memory in a VM 3:: Call gate 292
ADC instruction 53 Call gate, creation of 296
ADD instruction Callback function 79, 102
ADDR 1:; CALLMSGFILTERO 208
Address bus 9 CallProce32W() .’ 220
Address decoder I82 CATCH0 208
Addressing below I M from prot. mode 228 CHANGESELECTOR 208
414 Windows Assembly Language & Systems Programming