Assembly Programming Journal 5
Assembly Programming Journal 5
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::. July-Sep 99
:::\_____\::::::::::. Issue 5
::::::::::::::::::::::.........................................................
A S S E M B L Y P R O G R A M M I N G J O U R N A L
http://asmjournal.freeservers.com
[email protected]
T A B L E O F C O N T E N T S
----------------------------------------------------------------------
Introduction...................................................mammon_
----------------------------------------------------------------------
++++++++++++++++++Issue Challenge+++++++++++++++++
Convert a bit value to ACIII less than 10 bytes
----------------------------------------------------------------------
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::..............................................INTRODUCTION
by mammon_
I suppose I should start with the good news. A week or so ago Hiroshimator
emailed me for the nth time asking if I needed help with the journal as I have
yet to get one out on time. I relented and asked if he knew any listservers;
one hour later he had an account for APJ set up at e-groups, specifically:
http://www.egroups.com/group/apj-announce
One of the greatest obstacles to putting out these issues -- processing the
300 or so subscription requests that rack up between issues -- is now out of
the way for good.
The articles this month have somewhat of a high-level focus; with the COM and
Direct Draw by Bill Tyler and X-Caliber, respectively, as well as Chris
Dragan's classic work on exception handling and Jeremy Gordon's treatment of
windows callbacks, this issue is heavily weighed towards high-level win32
coding. Add to this Iczelion's two tutorials and my own win32-biased
linked list example, and it appears the DOS/Unix camp is losing ground.
To shore up the Unix front line, Jan Wagemakers has provided a port of last
month's fire demo to linux [GAS]. In addition, there are A86 articles by Jan
Verhoeven and a general assembly routine by Laura Fairhead to prove that not
all assembly has to be 32-bit.
_m
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
COM in Assembly Part II
by Bill Tyler
This article will describe implementing COM Objects, using MASM syntax. TASM
or NASM assemblers will not be considered, however the methods can be easily
applied to any assembler.
This article will also not describe some of the more advanced features of COM
such as reuse, threading, servers/clients, and so on. These will presented
in future articles.
COM Interfaces Review
------------------------------------------------------------------------------
An interface definition specifies the interface's methods, their return types,
the number and types of their parameters, and what the methods must do. Here
is a sample interface definition:
IInterface struct
lpVtbl dd ?
IInterface ends
IInterfaceVtbl struct
; IUnknown methods
STDMETHOD QueryInterface, :DWORD, :DWORD, :DWORD
STDMETHOD AddRef, :DWORD
STDMETHOD Release, :DWORD
; IInterface methods
STDMETHOD Method1, :DWORD
STDMETHOD Method2, :DWORD
IInterfaceVtbl ends
This macro is used to greatly simplify interface declarations, and so that the
MASM invoke syntax can be used. (Macro originally by Ewald :)
Two different styles of addressing the members are shown. Both notations
produce equivalent code, so the method used is a matter of personal
preference.
All interfaces must inherit from the IUnknown interface. This means that the
first 3 methods of the vtable must be QueryInterface, AddRef, and Release.
The purpose and implementation of these methods will be discussed later.
GUIDS
------------------------------------------------------------------------------
A GUID is a Globally Unique ID. A GUID is a 16-byte number, that is unique
to an interface. COM uses GUID's to identify different interfaces from one
another. Using this method prevents name clashing as well as version
clashing. To get a GUID, you use a generator utility that is included with
most win32 development packages.
A GUID is represented by the following structure:
GUID STRUCT
Data1 dd ?
Data2 dw ?
Data3 dw ?
Data4 db 8 dup(?)
GUID ENDS
COM Objects
------------------------------------------------------------------------------
A COM object is simply an implementation of an interface. Implementation
details are not covered by the COM standard, so we are free to implement our
objects as we choose, so long as they satisfy all the requirements of the
interface definition.
Object struct
interface IInterface <?> ; pointer to an IInterface
nRefCount dd ? ; reference count
nValue dd ? ; private object data
Object ends
We also have to define the vtable's we are going to be using. These tables
must be static, and cannot change during run-time. Each member of the vtable
is a pointer to a method. Following is a method for defining the vtable.
Reference Counting
------------------------------------------------------------------------------
COM object manage their lifetimes through reference counting. Each object
maintains a reference count that keeps track of how many instances of the
interface pointer have been created. The object is required to keep a
counter that supports 2^32 instances, meaning the reference count must be a
DWORD.
When the reference count drops to zero, the object is no longer in use, and
it destroys itself. The 2 IUnknown methods AddRef and Release handle the
reference counting for a COM object.
QueryInterface
------------------------------------------------------------------------------
The QueryInterface method is used by a COM object to determine if the object
supports a given interface, and then if supported, to get the interface
pointer. There are 3 rules to implementing the QueryInterface method:
@1:
; GETOBJECTPOINTER is a macro that will put the object pointer into eax,
; when given the name of the object, the name of the interface, and the
; interface pointer.
GETOBJECTPOINTER Object, interface, pif
; return S_OK
mov eax, S_OK
jmp return
@NoInterface:
; interface not supported, so set *ppv to zero
mov eax, [ppv]
mov dword ptr [eax], 0
; return E_NOINTERFACE
mov eax, E_NOINTERFACE
return:
ret
IInterface@QueryInterface endp
AddRef
------------------------------------------------------------------------------
The AddRef method is used to increment the reference count for an interface
of an object. It should be called for every new copy of an interface pointer
to an object.
AddRef takes no parameters, other than the interface pointer required for all
methods. AddRef should return the new reference count. However, this value
is to be used by callers only for testing purposes, as it may be unstable in
certain situations.
Release
------------------------------------------------------------------------------
Release decrements the reference count for the calling interface on a object.
If the reference count on the object is decrememnted to 0, then the object is
freed from memory. This function should be called when you no longer need to
use an interface pointer
Like AddRef, Release takes only one parameter - the interface pointer. It
also returns the current value of the reference count, which, similarly, is to
be used for testing purposess only
; free the object: here we have assumed the object was allocated with
; LocalAlloc and with LMEM_FIXED option
GETOBJECTPOINTER Object, interface, pif
invoke LocalFree, eax
@1:
ret
IInterface@Release endp
Other methods exist for creating objects, such as using CoCreateInstance, and
using class factories. These methods will not be discussed, and may be a
topic for a future article.
.386
.model flat,stdcall
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
includelib uuid.lib
;-----------------------------------------------------------------------------
; Macro to simply interface declarations
; Borrowed from Ewald, http://here.is/diamond/
STDMETHOD MACRO name, argl :VARARG
LOCAL @tmp_a
LOCAL @tmp_b
@tmp_a TYPEDEF PROTO argl
@tmp_b TYPEDEF PTR @tmp_a
name @tmp_b ?
ENDM
;-----------------------------------------------------------------------------
IInterface@QueryInterface proto :DWORD, :DWORD, :DWORD
IInterface@AddRef proto :DWORD
IInterface@Release proto :DWORD
IInterface@Get proto :DWORD
IInterface@Set proto :DWORD, :DWORD
externdef IID_IUnknown:GUID
;-----------------------------------------------------------------------------
; declare the interface prototype
IInterface struct
lpVtbl dd ?
IInterface ends
IInterfaceVtbl struct
; IUnknown methods
STDMETHOD QueryInterface, pif:DWORD, riid:DWORD, ppv:DWORD
STDMETHOD AddRef, pif:DWORD
STDMETHOD Release, pif:DWORD
; IInterface methods
STDMETHOD GetValue, pif:DWORD
STDMETHOD SetValue, pif:DWORD, val:DWORD
IInterfaceVtbl ends
; object data
nRefCount dd ?
nValue dd ?
Object ends
;-----------------------------------------------------------------------------
.data
; define the vtable
@@IInterface segment dword
vtblIInterface:
dd offset IInterface@QueryInterface
dd offset IInterface@AddRef
dd offset IInterface@Release
dd offset IInterface@GetValue
dd offset IInterface@SetValue
@@IInterface ends
;-----------------------------------------------------------------------------
.code
start:
StartProc proc
LOCAL pif:DWORD ; interface pointer
exit:
ret
StartProc endp
;-----------------------------------------------------------------------------
IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
invoke IsEqualGUID, [riid], addr IID_IInterface
test eax,eax
jnz @F
invoke IsEqualGUID, [riid], addr IID_IUnknown
test eax,eax
jnz @F
jmp @Error
@@:
GETOBJECTPOINTER Object, interface, pif
lea eax, (Object ptr [eax]).interface
; set *ppv
mov ebx, [ppv]
mov dword ptr [ebx], eax
; return S_OK
mov eax, S_OK
jmp return
@Error:
; error, interface not supported
mov eax, [ppv]
mov dword ptr [eax], 0
mov eax, E_NOINTERFACE
return:
ret
IInterface@QueryInterface endp
;-----------------------------------------------------------------------------
CreateObject proc uses ebx ecx pobj:DWORD
; set *ppv to 0
mov eax, pobj
mov dword ptr [eax], 0
; allocate object
invoke LocalAlloc, LMEM_FIXED, sizeof Object
or eax, eax
jnz @1
; alloc failed, so return
mov eax, E_OUTOFMEMORY
jmp return
@1:
return:
ret
CreateObject endp
;-----------------------------------------------------------------------------
IsEqualGUID proc rguid1:DWORD, rguid2:DWORD
cld
mov esi, [rguid1]
mov edi, [rguid2]
mov ecx, sizeof GUID / 4
repe cmpsd
xor eax, eax
or ecx, ecx
setz al
ret
IsEqualGUID endp
end start
Conclusion
------------------------------------------------------------------------------
We have (hopefully) seen how to implement a COM object. We can see that it
is a bit messy to do, and adds quite some overhead to our programs. However,
it can also add great flexibility and power to our programs.
Remember that COM defines only interfaces, and implementation is left to the
programmer. This article presents only one possible implementation. This is
not the only method, nor is it the best one. The reader should feel free to
experiment with other methods.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::..........................................FEATURE.ARTICLE
How to use DirectDraw in ASM
by X-Calibre [Diamond]
Well, there has been quite a large demand for this essay, so I finally started
writing it. This essay will show you how to use C++ objects and COM interface
in Win32ASM, using DirectDraw as an example.
Well, in this part of the Win32 API, you will soon find out how important it
is to know C and C++ when you want to use an API written in these languages.
Judging from the demand for this essay, I think it will be necessary to
explain a bit of how objects work in C++. I will not go too deep, but only
show the things you need to know in Win32ASM.
Actually a structure is an object of which all fields are public. We will look
at it the other way around. So the public fields in an object make up a
structure. The other fields in an object are private and are not reachable
from the outside. So they are not interesting to us.
A special thing about objects is that they can contain pointers to functions.
Normally, when using C or ASM, this would be possible, but a bit error-prone.
It can be seen as 'dirty' programming. That's why you probably haven't seen it
before.
When using C++ with a compiler, there will be no errors, as long as the
compiler does its job. So here you can use this technique with no chance of
errors, and it gives you some nice new programming options.
C++ goes even further with this 'structure of functions' idea. With
inheritance, you can also overwrite functions of the base class in the
inherited class. You can also create 'virtual' functions, which are defined in
the base class, but the actual code is only in inherited classes.
This is of course interesting for DirectX, where you want to have standard
functions, but with different code, depending on the hardware on which it is
running. So in DirectX, all functions are defined as virtual, and the base
class is inherited by hardware-specific drivers which supply hardware-specific
code. And the beauty of this is, that it's all transparent to the programmer.
The function pointers can change at runtime because of this system, so the C++
designers had to think of a way to keep the pointers to the functions
available to the program at all time.
What this all boils down to is that there is a table with pointers to the
functions. It's called the Virtual Function Table. I will call this the
vtable from now on.
So we need to get this table, in order to call functions from our object.
Lucky for you, Z-Nith has already made a C program to 'capture' the table,
and converted the resulting header file to an include file for use with MASM.
So I'll just explain how you should use this table, and you can get going
soon.
Well, actually it's quite simple. The DirectX objects are defined like this:
IDirectDraw STRUC
lpVtbl DWORD ?
IDirectDraw ENDS
IDirectDrawPalette STRUC
lpVtbl DWORD ?
IDirectDrawPalette ENDS
IDirectDrawClipper STRUC
lpVtbl DWORD ?
IDirectDrawClipper ENDS
IDirectDrawSurface STRUC
lpVtbl DWORD ?
IDirectDrawSurface ENDS
So these structs are actually just a pointer to the vtables, and don't contain
any other values. Well, this makes it all very easy for us then.
I'll give you a small example:
Say we have an IDirectDraw object called lpDD. And we want to call the
RestoreDisplayMode function.
Then we need to do 2 things:
The first part is simple. All the struct contains, is the pointer to the
vtable. So we can just do this:
Simple, isn't it? And the next part isn't really much harder. The vtable is
put into a structure called IDirectDrawVtbl in DDRAW.INC. We now have the
address of the structure in eax. All we have to do now, is get the correct
member of that structure, to get the address of the function we want to call.
You would have guessed by now, that this will do the trick:
call [IDirectDrawVtbl.RestoreDisplayMode][eax]
push [lpDD]
call [IDirectDrawVtbl.RestoreDisplayMode][eax]
Simple, was it not? And calls with arguments are not much harder.
Let's set the displaymode to 320x200 in 32 bits next.
This call requires 3 arguments:
Well, the extra arguments work just like normal API calls: just push them onto
the stack in backward order.
So it will look like this:
push 32
push 200
push 320
mov eax, [lpDD]
push eax
mov eax, [eax]
call [IDirectDrawVtbl.SetDisplayMode][eax]
To make life easier, we have included some MASM macros in DDRAW.INC, for use
with the IDirectDraw and IDirectDrawSurface objects:
IFB <arglist>
INVOKE [IDirectDrawVtbl. func][eax], this
ELSE
INVOKE [IDirectDrawVtbl. func][eax], this, arglist
ENDIF
ENDM
IFB <arglist>
INVOKE [IDirectDrawSurfaceVtbl. func][eax], this
ELSE
INVOKE [IDirectDrawSurfaceVtbl. func][eax], this, arglist
ENDIF
ENDM
With these macros, our 2 example calls will look as simple as this:
Well, that's basically all there is to know about using objects, COM and
DirectX in Win32ASM. Have fun with it!
And remember:
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Writing Boot Sectors To Disk
by Jan Verhoeven
Introduction.
-------------
In my previous article I showed how to make a private non-bootable
bootsector for 1.44 Mb floppy disks. Unfortunately, there was no way yet to
write that non-bootsector to a floppy disk....
Enter this code. It is the accompanying bootsector writer for floppy disks.
It assumes that your A: drive is the 1.44 Mb floppy disk drive and I dare
say that this will be true in the majority of cases.
A86 is particularly useful for people that make syntax errors. It will
insert the errormessages into the sourcefile so that you can easily find
them back. In the next assembler run the error messages are removed again.
To fully use this aspect of A86 programming, I made a small batchfile that
will let me choose between several options while writing the code. Below
you can see the file. After an error, I choose to go back into the editor.
When there are no errors, I might decided to do a trial run. Or to quit to
DOS.
This is all done by means of the WACHT command which waits for a keypress.
It returns (in errorlevel) the indexed position in the command tail table
of th key which was pressed.
:start
ed %1.a86
a86 %1.a86 %2 %3 %4 %5 %6
:menu
Echo *
Echo Options:
Echo *Escape = stop
Echo * L = LIST
echo * ;-() = back to the editor
echo * space = test-run of %1.com
echo *Period = debugger-run with %1.com/sym
wacht # .\=-[]';-()/":?><{}|+_LCE
:execute
%1
if errorlevel 9 echo Errorlevel = 9+
if errorlevel 8 echo Errorlevel = 8
if errorlevel 7 echo Errorlevel = 7
if errorlevel 6 echo Errorlevel = 6
if errorlevel 5 echo Errorlevel = 5
if errorlevel 4 echo Errorlevel = 4
if errorlevel 3 echo Errorlevel = 3
if errorlevel 2 echo Errorlevel = 2
if errorlevel 1 echo Errorlevel = 1
goto menu
:debugger
vgamode 3
d86 %1
goto menu
:list
list
goto menu
:screen
vgamode 3
goto menu
:leave
echo No file specified
----------- Run.Bat ---------------------------------------- End ----------
This BAT file relies heavily on my computer system. For one, I use DR-DOS 6
which means that I can use the EXIT word to get out of a Batchfile.
Also, I switch videomodes back to Mode 3 with "Vgamode 3" and you will have
to use another command for that, like "Mode co80" or using the utillity
that came with your videocard.
The program "List" is Vernon Buerg's file lister which I use to track down
errors in all kinds of files.
I chose for the BIOS method. For non-partitioned diskstructures this is the
easiest way. Just select track, head and side and write data to the sectors
on that disk.
The bootsector is the very first sector on a disk. For a floppy disk this
boils down to track 0, head 0 and sector 1 (sectors are counted from 1, not
from 0!).
The Source.
-----------
Below is the sourcecode for this short utillity. I have commented just
about any line I thought fit for it.
db 'VeRsIoN=0.2', 0
db 'CoPyRiGhT=CopyLeft 1999, Jan Verhoeven, '
db '[email protected]', 0
; ----------------------
filename db 'BootLoad.bin', 0 ; name of file to send to disk
Have fun experimenting with bootsectors. But take care that this will NOT
work on a hard disk.
The MBR contains the partition table, indicating where partitions start and
end and whether they are bootable or not. Plus some code to interpret that
table and to find the bootsector that was selected.
If you write a floppy disk bootsector to the very first sector of a HDD,
you wipe out the MBR and hence make inaccesable all data on that disk. The
data will still be there, but the system will not be able anymore to find
or use it.
So please take care that this software is NOT used for drive (DL=) 080.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Dumping Memory To Disk
by Jan Verhoeven
This piece of code allows you to make a memory dump of any region of
conventional memory (i.e. below 1 Mb) to a diskfile.
So just read the source and read the remarks. This way, the text is
where the code is, and you don't need to go back and forth in the
text. I think this will easier to read than explanation afterwards.
name mem2file
title Send an area of memory to a diskfile.
page 80, 120
An A86 enhancement: if you need 16K elements of data, just ask for it.
No need to remember that 16 Kb is 16.384 bytes. The "K" will do.
Also, if you need to process large binary numbers you may group them
into sub-units separated by underscores. So the number:
1000100100111101
1000_1001_0011_1101
jmp main
HexTable db '0123456789ABCDEF', 0
db 'VeRsIoN=Mem2File 1.3', 0
db 'CoPyRiGhT=CopyLeft Jan Verhoeven, [email protected]', 0
I use volatile data to store data that does not need initialising. This
saves a lot of diskspace and it loads a lot faster. Drawback of volatile
data can be that any rubbish left there by other programs can make your
software go berzerk if you yourself forget to initialise the data.
Therefore I -always- prime the volatile data memory with zero's. Just to
have a well defined starting position.
It should not be necessary, but, on the other hand, how much overhead
and extra execution time is such an initialisation routine?
GetArg: push ax, si, di ; get next argument from command-line in ASCIIZ
format
mov si, [OldClp] ; now, where did we leave last time?
cmp si, 0 ; Have we ever used this routine?
IF E mov si, 081 ; if not, prime SI, ...
mov di, offset Argument ; ... DI and ...
mov [ArgNum], 0 ; ... nr of chars in argument.
L1: lodsb ; get byte
cmp al, ' ' ; skip over spaces, ...
je L1
cmp al, tab ; ... and tabs.
je L1
cmp al, 1 ; ONLY if AL is 0, we get a carry
jc L3 ; if CARRY, we're done
cmp al, 0
jz L3
but L3 is the error-exit and needs the carrybit to be set as an error
flag. Normally you would enter a
stc
AL can have any value between 020 and 0FF, plus 00, tab, lf and cr. 01
is not an option. So the sequence
will send us to the errorexit WITH the carryflag set, all in one,
without explicitly having to set the carry flag.
Ok, ok, ok. I was influenced to make this function by a compiler. No, it
wasn't C. It was Modula-2.
GetArg (if necessary A86 can operate in a case sensitive mode!) extracts
the next argument from the command tail. It puts it in a seperate buffer
at address "Arument" which can hold 80 bytes. Shoyuld be more than
enough for one word or expression.
;------------------------
L1: stc ; byte not in table!
pop dx ; we came here with carry set!
ret ; exit
This is done to have the local labels declared for when they are needed
in the main functionbody. All jumps are "backward". For the CPU there's
no big influence, but for the assembler there is. No guessing about
labels.
So you have to put a conditional jump before the call, and introduce yet
another silly labelname for the next instruction.
IF cc Ret
A86 is very programmer-oriented and allows us to write down what and how
we think. So if I need to set bit 0 of register Ax, I will simply write
or ax, bit 0
ret
;------------------------
BadNumber: ; hey typo, you made a dumbo!
mov dx, offset Mess004
mov cx, Len004
mov bx, StdOut
mov ah, 040
int 021
This is another A86 goody. I coded a "SHL DX, 4" instruction, although
I do not know what the target processor will be.
No problem with A86. It will find out with which CPU you are assembling
and use that. If your CPU supports this function, it is implemented as
such. If it doesn't this instruction is expanded as a macro into the
following:
shl dx, 1
shl dx, 1
shl dx, 1
shl dx, 1
If on a modern CPU, you can force A86 to act as if the CPU were a
vintage 88 with the commandline switch +P65.
call GetArg
IF C jmp SyntErr
call GetArg
jnc jmp_01F
jmp SyntErr
jmp_01F: ...
IF C jmp SyntErr
See how powerful the IF construct can be? It is a very convenient way to
circumvent the foolish conditional instructions of the x86 architecture.
mov [Handle], ax
A86 likes to have as much as possible labels declared before they are
referenced. That's why many times there is code "before" the subroutine
name is declared.
For local labels (i.e. labels that consist of 1 letter and the rest
decimal digits) it is a MUST that they are defined before being
referenced.
If, for some reason, you do not want to put a label backwards in memory,
you can forward reference a local label by prefixing it with a ">" sign.
A86 now knows that the local label still has to come. Not a luxury since
many A86 programmers can do with 4 or 5 local labels in over 2000 lines
of code.... Especially L0 is always very well available.
shl ax, 1
rcl dx, 1
shl ax, 1
rcl dx, 1 ; dx = nr of 16 Kb blocks to move
mov [Blocks], dx ; store it
shr ax, 2 ; ax = remainder to move
mov [Rest], ax ; save it
Normally this instruction would require a secretary with 100 letters per
minute typing rate:
But the "ptr" argument is always the same, so it is only there to please
the assembler and humiliate the programmer: entering data that nobody
needs.
Therefore A86 only needs the first letter of such prose. In our case:
the "dword ptr" is abbreviated to a "d". "Byte ptr" is a "b". "Word Ptr"
is a "w". Simple as that.
So if your coding skills outweigh your typing speed, you should consider
switching to the superior assembler. :)
For people who still remember that "Seg ES" is a legal instruction
(used for a segment override) this might bring back memories.
A86 allows the user to put the segmentation override before the actual
instruction. This way, the operand field looks neater. And it is also
the way in which D86 shows segmentation overrides.
je >S0
mov cx, 16K
L0: mov ah, 040
int 021
mov ax, ds ; store ds into ax
add dx, 04000 ; next buffer to load data from
IF C add ax, 01000 ; if carry, inc ds
mov ds, ax ; ds:dx now ready for next bufferfull of data
es dec [Blocks]
jnz L0
That's it.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Formatted Numeric Output
by Laura Fairhead
Here I am going to present you with a very useful routine for numeric
output. I have been using it myself for sometime and now I think it is
almost perfect.
It consists of 2 basic API's. The first (nuconvs), you call when you
want to change the parameters of the main routine (nuconv). You simply call
it with one DWORD in EAX, this specifies the following:
Once you've set the control parameters you can call the main routine
(nuconv) freely to do the work. You call the main routine with ES:DI set
to where the output is to be stored, and the value to be output in AL or
AX or EAX (depending on what data size you set).
I use the word 'output' here which might conjure up images of the screen,
but in fact what we are doing here is writing all the ASCII to memory.
This is much more powerful than incorporating all that OS/application
specific nonsense, and it really doesn't cost much overhead at all (in
fact this is the way C does it and even though I **HATE** C, here it is
right on the mark;)
================START OF CODE==============================================
;
;nuconvs- set control parameters for 'nuconv'
;
; !! this must be called at least once before calling nuconv
;
;entry: EAX=SSFFPPRR (hex digits)
;
; where: SS=data size (0=byte,1=word,2=dword)
; FF=field size (0=none)
; PP=pad char
; RR=radix (2-16)
;
; !! these parameters must be set correctly by the application
; !! they are not validated in anyway and invalid parameters
; !! will cause undefined operation
;
;exit: (all registers preserved)
;
;
;control parameters
;
; !! these absolutely must be in the below order due to the way the above
; routine works
;
;
;nuconv- output value in accumalator -> ES:DI
;
; !! see 'nuconvs' header for more information
;
;entry: AL|AX|EAX=value to output
; ES:DI=address to write output data
;
; size of accumulator that is used depends on what the current data
; size is ( as specified by a previous call to 'nuconvs' )
;
;
;exit: DI=updated to offset of last character + 1
;
; (all other registers preserved)
;
nulop1: POP AX
XLAT
STOSB
LOOP nulop1
;
; restore all registers and exit (in case it wasn't obvious!)
;
POP EDX
POP CX
POP EBX
POP EAX
POP DS
RET
;
nudat DB "0123456789ABCDEF"
;
nuconv ENDP
==================END OF CODE==============================================
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Linked Lists in ASM
by mammon_
One of the areas in which assembly language is lacking is the use of dynamic
structures. Pointer manipulation in asm is simple and clear for up to one
level of redirection; further redirection causes the code to quickly become a
confusion of register juggling and indirect addressing. As a result,
implementing even a simple linked list in assembly language can be tedious
enough to make one rewrite the project in C.
To begin with, one must define the memory allocation routines for use in the
application; I have chosen Win32 for convenience. The routines defined below
are for local heap allocation and for the Win32 console interface to allow the
use of STDOUT on the console.
;=========================================================Win32 API Definitions
STD_INPUT_HANDLE EQU -10 ;nStdHandle types
STD_OUTPUT_HANDLE EQU -11
STD_ERROR_HANDLE EQU -12
GetConsole:
;GetConsole()
[section data]
hConsole DD 0
[section code]
call AllocConsole
push dword STD_OUTPUT_HANDLE
call GetStdHandle
mov [hConsole], eax
xor eax, eax
ret
puts:
;puts( ptrString, NumBytes )
[section data]
NumWrote DD 0
[section code]
%define _ptrString ebp + 8
%define _strlen ebp + 12
push ebp
mov ebp,esp
push eax
push dword 0
push dword NumWrote
mov eax, [ _strlen ]
push dword eax
mov eax, [ _ptrString ]
push dword eax
push dword [hConsole]
call WriteConsoleA
pop eax
mov esp, ebp
pop ebp
ret 8
;==========================================================End Utility Routines
The STRING macro is particular interesting; it allows one to define a string
in the data segment as
STRING label, 'contents of string',0Dh,0Ah
while defining the constant label.length as the total length of the string.
This will come in handy during the many calls to puts, which is used to write
to the Win32 console. Puts has the syntax
puts( lpString, strLength )
and returns the result of WriteConsole, a BOOL value. GetConsole is a routine
provided to move the Win32 console allocation code out of the main program; it
takes no parameters and defines the hConsole handle.
The linked list implementation has been designed to be extendable; the routine
names are prefaced with underscores to avoid filling up the namespace of the
linked list application, and the routines themselves are generic enough to be
called from higher-level Stack, Queue, and List implementations. The Linked
List interface is as follows:
ptrHead _create_list( hHeap, NodeSize )
void _delete_list( hHeap, ptrHead)
ptrNode _add_node( hHeap, ptrPrev, NodeSize )
void _delete_node( hHeap, ptrPrev, ptrNode )
void _set_node_data( ptrNode, NodeOffset, data )
DWORD data _get_node_data( ptrNode, NodeOffset )
The names of the routines should make their intent apparent; note however that
NodeSize is assumed to be the size of a LISTSTRUCT structure.
;====================================================Linked List Implementation
[section data]
;Define .next as offset Zero for use in generic functions
struc _llist
.next: resd 1 ;this is basically a constant
endstruc
_delete_list:
; _delete_list( hHeap, ptrHead)
%define _hHeap ebp + 8
%define _ptrHead ebp + 12
ENTER 0, 0
push eax
push ebx ;save registers
mov eax, [_ptrHead] ;eax = addr of list head node
.DelNode:
mov ebx, [eax + _llist.next] ;ebx = [eax].next
push eax ;free addr in eax
push dword 0 ;FLAG
push dword [_hHeap] ;local heap
call HeapFree
test ebx, ebx ;is [eax].next == NULL?
jz .Exit ;if yes then done
mov eax, ebx ;loop until done
jmp .DelNode
.Exit: pop ebx
pop eax
LEAVE
ret 8
_add_node:
; ptrNode _add_node( hHeap, ptrPrev, NodeSize )
%define _hHeap ebp + 8
%define _ptrPrev ebp + 12
%define _ListSize ebp + 16
ENTER 0, 0
push edx ;HeapAlloc kills edx!!
push ebx
push ecx ;save registers
mov ebx, [_ptrPrev] ;ebx = node to add after
push dword [_ListSize] ;size of node
push dword HEAP_ZERO_MEMORY ;FLAG
push dword [_hHeap] ;local heap
call HeapAlloc
test eax, eax
jz .Error ;alloc failed!
mov ecx, eax ;note -- eax = ptrNew
add ecx, _llist.next ;ecx = ptrNew.next
mov [ecx], ebx ;ptrNew.next = ptrPrev.next
add ebx, _llist.next ;note -- ebx = ptrPrev
mov [ebx], eax ;ptrPrev.next = ptrNew
.Exit: pop ecx
pop ebx
pop edx
LEAVE
ret 12
.Error: xor eax, eax ;return NULL on failure
jmp .Exit
_delete_node:
; _delete_node( hHeap, ptrPrev, ptrNode )
%define _hHeap ebp + 8
%define _ptrPrev ebp + 12
%define _ptrNode ebp + 16
ENTER 0, 0
push ebx
mov eax, [_ptrNode + _llist.next] ;eax = ptrNode.next
mov ebx, [_ptrPrev] ;
mov [ebx + _llist.next], eax ;ptrPrev.next = ptrNode.next
push dword [_ptrNode] ;free ptrNode
push dword 0 ;FLAG
push dword [_hHeap] ;local heap
call HeapFree
pop ebx
LEAVE
ret 12
_set_node_data:
; _set_node_data( ptrNode, NodeOffset, data )
%define _ptrNode ebp + 8
%define _off ebp + 12
%define _data ebp + 16
ENTER 0, 0
push eax
push ebx
mov eax, [_ptrNode] ;eax = ptrNode
add eax, [ _off ] ;eax = ptrNode.offset
mov ebx, [_data] ;ebd = data
mov [eax], ebx ;ptrNode.offset = data
pop ebx
pop eax
LEAVE
ret 12
_get_node_data:
; DWORD data _get_node_data( ptrNode, NodeOffset )
%define _ptrNode ebp + 8
%define _off ebp + 12
ENTER 0, 0
mov eax, [_ptrNode] ;eax = ptrNode
add eax, [_off] ;eax = ptrNode.offset
mov eax, [eax] ;return [ptrNode.offset]
LEAVE
ret 8
;===============================================================End Linked List
The LISTSTRUCT structure is perhaps the most crucial part of this implemen-
tation. In NASM, a structure is simply a starting address with local labels
defined as constants which equal the offset of the local label from the start
of the structure. Thus, in the structure
struc MyStruc
.MyVar resd 1
.MyVar2 resd 1
.MyVar3 resd 1
.MyByte resb 1
endstruc
the constant MyStruc.MyVar has a value of 0 [0 bytes from the start of the
structure], MyStruc.MyVar2 has a value of 4, MyStruc.MyVar3 has a value of 8,
MyStruc.MyByte has a value of 12, and MyStruc_size [defined as the offset of
the "endstruc" directive] has a value of 13. Note that in NASM, the name of a
structure instance determines the address in memory of the instance [i.e., it
is a simple code label], while the constants defined in the structure
definition allow access to offsets from that address.
What this means is that structures in NASM can be defined and never instant-
iated, allowing the convenient use of the structure constants for dynamic
memory structures such as classes and linked list nodes. The above code uses
the LISTSTRUCT macro to force all linked list nodes to have a ".next" member;
this also allows the use of the constant "_llist.next" in the linked list
routines to avoid having to pass the offset of the ".next" member for a node.
Both the _set_node_data and the _get_node_data routines are basic pointer
manipulations added for code clarity. Each could be rewritten inline; for
example, the _get_node_data routine can be implemented as
add ebx, offset
mov eax, [ebx]
assuming ebx holds the node to be accessed and "offset" is the offset [or
constant] of the node member to be accessed.
Below is a simple program which makes a four-node linked list of the format
.next Node1 --> .next Node2 --> .next Node3 --> .next NULL
.prev NULL <-- .prev Head <-- .prev Node1 <-- .prev Node2
.data NULL .data 'node1' .data 'node2' .data 'node3'
Note the use of the NewNode routine, which provides a front-end to _add_node
which sets the .prev member for the new node. One brief caveat, the example
does not delete the list, as the Win32 heap is deallocated on program
termination; neither is there any substantial error checking in the sample.
LISTSTRUCT llist
.prev resd 0
.data resd 0
END_LISTSTRUCT
[section code]
Error:
push dword strErr.length
push dword strErr
call puts
jmp Exit
..start:
call GetProcessHeap
mov [hHeap], eax
call GetConsole
CreateList:
push dword llist_size
push dword [hHeap]
call _create_list
test eax, eax
jz Error
mov [ptrHead], eax
push dword 0
push dword llist.data
push eax
call _set_node_data ;set ptrHead.data to NULL
push dword 0
push dword llist.prev
push eax
call _set_node_data ;set ptrHead.prev to NULL
ListDone:
push dword strDone.length
push dword strDone
call puts
Exit:
push dword 0
call ExitProcess
NewNode:
ENTER 0, 0
push edx
mov edx, eax ;save previous node
push dword llist_size
push dword eax
push dword [hHeap]
call _add_node
test eax, eax
jz .Done
push dword eax
push dword llist.next
push dword edx
call _set_node_data ;set ptrPrev.next to ptrNew
push edx
push dword llist.prev
push eax
call _set_node_data ;set ptrNew.prev to ptrPrev
push dword 0
push dword llist.next
push eax
call _set_node_data ;set PtrNew.next to NULL
.Done pop edx
LEAVE ;eax is still set to ptrNew
ret
;==========================================================================EOF
As mentioned earlier, this is a generic implementation of dynamic structures
designed with linked lists in mind. The macros and routines may be included in
a header file such as llist.h and used to automate the creation of dynamic
memory structures in future projects. In addition, further macros and routines
can be added to provide specific implementations of Single Linked Lists,
Double Linked Lists, Circular Lists, Stacks, Queues, and Deques.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Structured Exception Handling under Win32
by Chris Dragan
The starting point for Structured Exception Handling, SEH, is the Thread
Info Block. TIB, as almost all the other structures, is described in winnt.h
file that comes with PlatformSDK.
struc NT_TIB
ExceptionList dd ? ; Used by SEH
StackBase dd ? ; Used by functions to check for
StackLimit dd ? ; stack overflow
SubSystemTib dd ? ; ?
FiberDataOrVersion dd ? ; ?
ArbitraryUserPointer dd ? ; ?
Self dd ? ; Linear address of the TIB
ends
struc E_L_ENTRY
Next dd ? ; Points to next entry in the list
ExceptionHandler dd ? ; User callback - exception hook
Optional db X dup (?) ; Exception Handler data
EntryTerminator dd -1 ; Optional
ends
The exception handler uses C-style calling convention, it does not release
arguments while returning. The most important parameters are ExceptionRecord
and ContextRecord, described at the end of this text, that point to the pushed
corresponding structures. I do not have yet any idea what is the purpose of
EstablisherFrame and DispatcherContext.
struc EXCEPTION_RECORD
ExceptionCode dd ? ; See at the end of this text
ExceptionFlags dd ?
ExceptionRecord dd ? ; ?
ExceptionAddress dd ? ; Linear address of faulty instruction
NumberParameters dd ? ; Corresponds to the field below
ExceptionInformation dd 15 dup (?) ; ?
ends
The exception handler has two possible ways of proceeding. It can return to
the exception manager, or it can unwind the stack and continue the program. In
the first case it has to return one of the following values:
enum EXCEPTION_DISPOSITION \
ExceptionContinueExecution = 0,\
ExceptionContinueSearch = 1,\
ExceptionNestedException = 2,\
ExceptionCollidedUnwind = 3
The value of zero forces the exception manager to continue the program
at saved in context cs:eip, which may be altered by the exception handler. The
value of 1 causes the exception manager to call another exception handler in
the exception list. Values 2 and 3 inform the exception manager that an error
occured - an exception-in-exception happened, or the handler wanted to unwind
the stack during another handler of higher instance was doing this already.
The other case can be determined if one of .ExceptionFlags is
EXCEPTION_UNWINDING or EXCEPTION_UNWINDING_FOR_EXIT.
----Start-of-file-------------------------------------------------------------
ideal
p686n
model flat, stdcall
O equ <offset>
struc EXCEPTION_RECORD
ExceptionCode dd ?
ExceptionFlags dd ?
ExceptionRecord dd ?
ExceptionAddress dd ?
NumberParameters dd ?
ExceptionInformation dd 15 dup (?)
ends
udataseg
ExCode dd ?
szCode db 12 dup (?)
dataseg
szWindowTitle db 'Exception code', 0
szFormat db '%0X', 0
codeseg
proc main
; Install exception handler
push O ExceptionHandler
push [dword fs:0] ; E_L_ENTRY.Next
mov [fs:0], esp ; Append new E_L_ENTRY
end main
----End-of-file---------------------------------------------------------------
The above source should be compiled with TASM 5.0r or later like this:
tasm32 /ml except.asm
tlink32 /x /Tpe /aa /c /V4.0 except.obj,,, LIBPATH\import32.lib
And here are other important constants and structures, all defined in
winnt.h PlatformSDK file.
Exception codes:
----------------
STATUS_SEGMENT_NOTIFICATION = 040000005h
STATUS_GUARD_PAGE_VIOLATION = 080000001h
STATUS_DATATYPE_MISALIGNMENT = 080000002h
STATUS_BREAKPOINT = 080000003h
STATUS_SINGLE_STEP = 080000004h
STATUS_ACCESS_VIOLATION = 0C0000005h
STATUS_IN_PAGE_ERROR = 0C0000006h
STATUS_INVALID_HANDLE = 0C0000008h
STATUS_NO_MEMORY = 0C0000017h
STATUS_ILLEGAL_INSTRUCTION = 0C000001Dh
STATUS_NONCONTINUABLE_EXCEPTION = 0C0000025h
STATUS_INVALID_DISPOSITION = 0C0000026h
STATUS_ARRAY_BOUNDS_EXCEEDED = 0C000008Ch
STATUS_FLOAT_DENORMAL_OPERAND = 0C000008Dh
STATUS_FLOAT_DIVIDE_BY_ZERO = 0C000008Eh
STATUS_FLOAT_INEXACT_RESULT = 0C000008Fh
STATUS_FLOAT_INVALID_OPERATION = 0C0000090h
STATUS_FLOAT_OVERFLOW = 0C0000091h
STATUS_FLOAT_STACK_CHECK = 0C0000092h
STATUS_FLOAT_UNDERFLOW = 0C0000093h
STATUS_INTEGER_DIVIDE_BY_ZERO = 0C0000094h
STATUS_INTEGER_OVERFLOW = 0C0000095h
STATUS_PRIVILEGED_INSTRUCTION = 0C0000096h
STATUS_STACK_OVERFLOW = 0C00000FDh
STATUS_CONTROL_C_EXIT = 0C000013Ah
STATUS_FLOAT_MULTIPLE_FAULTS = 0C00002B4h
STATUS_FLOAT_MULTIPLE_TRAPS = 0C00002B5h
STATUS_ILLEGAL_VLM_REFERENCE = 0C00002C0h
Context flags:
--------------
CONTEXT_i386 = 000010000h
CONTEXT_i486 = 000010000h
Context structure:
------------------
struc CONTEXT
ContextFlags dd ? ; CONTEXT_??? flags
Additional word
---------------
This article was posted on comp.lang.asm.x86.
Especially thanks to Michael Tippach for pointing out some exception flags.
My web page is at http://ams.ampr.org/cdragan/
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Child Window Controls
by Iczelion
In this tutorial, we will explore child window controls which are very
important input and output devices of our programs.
Theory
------
Windows provides several predefined window classes which we can readily use
in our own programs. Most of the time we use them as components of a dialog
box so they're usually called child window controls. The child window
controls process their own mouse and keyboard messages and notify the
parent window when their states have changed. They relieve the burden from
programmers enormously so you should use them as much as possible. In this
tutorial, I put them on a normal window just to demonstrate how you can
create and use them but in reality you should put them in a dialog box.
Examples of predefined window classes are button, listbox, checkbox, radio
button,edit etc.
After the control was created, it will send messages notifying the parent
window when its state has changed. Normally, you create the child windows
during WM_CREATE message of the parent window. The child window sends
WM_COMMAND messages to the parent window with its control ID in the low
word of wParam, the notification code in the high word of wParam, and its
window handle in lParam. Each child window control has different
notification codes, refer to your Win32 API reference for more information.
The parent window can send commands to the child windows too, by calling
SendMessage function. SendMessage function sends the specified message with
accompanying values in wParam and lParam to the window specified by the
window handle. It's an extremely useful function since it can send messages
to any window provided you know its window handle.
So, after creating the child windows, the parent window must process
WM_COMMAND messages to be able to receive notification codes from the child
windows.
Application
-----------
We will create a window which contains an edit control and a pushbutton.
When you click the button, a message box will appear showing the text you
typed in the edit box. There is also a menu with 4 menu items:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; buffer to store the text
retrieved from the edit box
.const
ButtonID equ 1 ; The control ID of the
button control
EditID equ 2 ; The control ID of the
edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc
hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,NULL,NULL, hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analysis:
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or
ES_LEFT\
or ES_AUTOHSCROLL,\
50,35,200,25,hWnd,EditID,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
After creating each control, we keep its handle in a variable for future
use.
SetFocus is called to give input focus to the edit box so the user can type
the text into it immediately.
Now comes the really exciting part. Every child window control sends
notification to its parent window with WM_COMMAND.
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
Recall that a menu also sends WM_COMMAND messages to notify the window
about its state too. How can you differentiate between WM_COMMAND messages
originated from a menu or a control? Below is the answer
You can see that you should check lParam. If it's zero, the current
WM_COMMAND message is from a menu. You cannot use wParam to differentiate
between a menu and a control since the menu ID and control ID may be
identical and the notification code may be zero.
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
You can put a text string into an edit box by calling SetWindowText. You
clear the content of an edit box by calling SetWindowText with NULL.
SetWindowText is a general purpose API function. You can use SetWindowText
to change the caption of a window or the text on a button.
To get the text in an edit box, you use GetWindowText.
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
The above code snippet deals with the condition when the user presses the
button. First, it checks the low word of wParam to see if the control ID
matches that of the button. If it is, it checks the high word of wParam to
see if it is the notification code BN_CLICKED which is sent when the button
is clicked.
The interesting part is after it's certain that the notification code is
BN_CLICKED. We want to get the text from the edit box and display it in a
message box. We can duplicate the code in the IDM_GETTEXT section above but
it doesn't make sense. If we can somehow send a WM_COMMAND message with the
low word of wParam containing the value IDM_GETTEXT to our own window
procedure, we can avoid code duplication and simplify our program.
SendMessage function is the answer. This function sends any message to any
window with any wParam and lParam we want. So instead of duplicating the
code, we call SendMessage with the parent window handle, WM_COMMAND,
IDM_GETTEXT, and 0. This has identical effect to selecting "Get Text" menu
item from the menu. The window procedure doesn't perceive any difference
between the two.
You should use this technique as much as possible to make your code more
organized.
Last but not least, do not forget the TranslateMessage function in the
message loop. Since you must type in some text into the edit box, your
program must translate raw keyboard input into readable text. If you omit
this function, you will not be able to type anything into your edit box.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Dialog Box as Main Window
by Iczelion
Now comes the really interesting part about GUI, the dialog box. In this
tutorial (and the next), we will learn how to use a dialog box as our main
window.
Theory
------
If you play with the examples in the previous tutorial long enough, you 'll
find out that you cannot change input focus from one child window control
to another with Tab key. The only way you can do that is by clicking the
control you want it to gain input focus. This situation is rather
cumbersome. Another thing you might notice is that I changed the background
color of the parent window to gray instead of normal white as in previous
examples. This is done so that the color of the child window controls can
blend seamlessly with the color of the client area of the parent window.
There is a way to get around this problem but it's not easy. You have to
subclass all child window controls in your parent window.
The reason why such inconvenience exists is that child window controls are
originally designed to work with a dialog box, not a normal window. The
default color of child window controls such as a button is gray because the
client area of a dialog box is normally gray so they blend into each other
without any sweat on the programmer's part.
Before we get deep into the detail, we should know first what a dialog box
is. A dialog box is nothing more than a normal window which is designed to
work with child window controls. Windows also provides internal "dialog box
manager" which is responsible for most of the keyboard logic such as
shifting input focus when the user presses Tab, pressing the default
pushbutton if Enter key is pressed, etc so programmers can deal with higher
level tasks. Dialog boxes are primarily used as input/output devices. As
such a dialog box can be considered as an input/output "black box" meaning
that you don't have to know how a dialog box works internally in order to
be able to use it, you only have to know how to interact with it. That's a
principle of object oriented programming (OOP) called information hiding.
If the black box is *perfectly* designed, the user can make use of it
without any knowledge on how it operates. The catch is that the black box
must be perfect, that's hard to achieve in the real world. Win32 API is
also designed as a black box too.
Well, it seems we stray from our path. Let's get back to our subject.
Dialog boxes are designed to reduce workload of a programmer. Normally if
you put child window controls on a normal window, you have to subclass them
and write keyboard logic yourself. But if you put them on a dialog box, it
will handle the logic for you. You only have to know how to get the user
input from the dialog box or how to send commands to it.
A dialog box is defined as a resource much the same way as a menu. You
write a dialog box template describing the characteristics of the dialog
box and its controls and then compile the resource script with a resource
editor.
Note that all resources are put together in the same resource script file.
You can use any text editor to write a dialog box template but I don't
recommend it. You should use a resource editor to do the job visually since
arranging child window controls on a dialog box is hard to do manually.
Several excellent resource editors are available. Most of the major
compiler suites include their own resource editors. You can use them to
create a resource script for your program and then cut out irrelevant lines
such as those related to MFC.
There are two main types of dialog box: modal and modeless. A modeless
dialog box lets you change input focus to other window. The example is the
Find dialog of MS Word. There are two subtypes of modal dialog box:
application modal and system modal. An application modal dialog box doesn't
let you change input focus to other window in the same application but you
can change the input focus to the window of OTHER application. A system
modal dialog box doesn't allow you to change input focus to any other
window until you respond to it first.
You can communicate with any child window control on a dialog box by using
SendDlgItemMessage function. Its syntax is like this:
This API call is immensely useful for interacting with a child window
control. For example, if you want to get the text from an edit control, you
can do this:
In order to know which message to send, you should consult your Win32 API
reference.
Windows also provides several control-specific API functions to get and set
data quickly, for example, GetDlgItemText, CheckDlgButton etc. These
control-specific functions are provided for programmer's convenience so he
doesn't have to look up the meanings of wParam and lParam for each message.
Normally, you should use control-specific API calls when they're available
since they make source code maintenance easier. Resort to
SendDlgItemMessage only if no control-specific API calls are available.
The Windows dialog box manager sends some messages to a specialized
callback function called a dialog box procedure which has the following
format:
The dialog box procedure is very similar to a window procedure except for
the type of return value which is TRUE/FALSE instead of LRESULT. The
internal dialog box manager inside Windows IS the true window procedure for
the dialog box. It calls our dialog box procedure with some messages that
it received. So the general rule of thumb is that: if our dialog box
procedure processes a message,it MUST return TRUE in eax and if it does not
process the message, it must return FALSE in eax. Note that a dialog box
procedure doesn't pass the messages it does not process to the
DefWindowProc call since it's not a real window procedure.
There are two distinct uses of a dialog box. You can use it as the main
window of your application or use it as an input device. We 'll examine the
first approach in this tutorial.
1. You can use the dialog box template as a class template which you
register with RegisterClassEx call. In this case, the dialog box
behaves like a "normal" window: it receives messages via a window
procedure referred to by lpfnWndProc member of the window class, not
via a dialog box procedure. The benefit of this approach is that you
don't have to create child window controls yourself, Windows creates
them for you when the dialog box is created. Also Windows handles the
keyboard logic for you such as Tab order etc. Plus you can specify the
cursor and icon of your window in the window class structure.
Your program just creates the dialog box without creating any parent
window. This approach makes a message loop unnecessary since the
messages are sent directly to the dialog box procedure. You don't even
have to register a window class!
This tutorial is going to be a long one. I'll present the first approach
followed by the second.
Application
-----------
------------------------------------------------------------------------
dialog.asm
------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
ClassName db "DLGCLASS",0
MenuName db "MyMenu",0
DlgName db "MyDialog",0
AppName db "Our First Dialog Box",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)
.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc
hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hDlg:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,DLGWINDOWEXTRA
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
mov hDlg,eax
invoke ShowWindow, hDlg,SW_SHOWNORMAL
invoke UpdateWindow, hDlg
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
.ENDW
mov eax,msg.wParam
ret
WinMain endp
#include "resource.h"
MyMenu MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
Analysis
--------
This is the text that will appear in the dialog box's title bar.
CLASS "DLGCLASS"
This line is crucial. It's this CLASS keyword that allows us to use the
dialog box template as a window class. Following the keyword is the name of
the "window class"
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END
The above block defines the child window controls in the dialog box.
They're defined between BEGIN and END keywords. Generally the syntax is as
follows:
mov wc.cbWndExtra,DLGWINDOWEXTRA
mov wc.lpszClassName,OFFSET ClassName
Normally, this member is left NULL, but if we want to register a dialog box
template as a window class, we must set this member to the value
DLGWINDOWEXTRA. Note that the name of the class must be identical to the
one following the CLASS keyword in the dialog box template. The remaining
members are initialized as usual. After you fill the window class
structure, register it with RegisterClassEx. Seems familiar? This is the
same routine you have to do in order to register a normal window class.
After registering the "window class", we create our dialog box. In this
example, I create it as a modeless dialog box with CreateDialogParam
function. This function takes 5 parameters but you only have to fill in the
first two: the instance handle and the pointer to the name of the dialog
box template. Note that the 2nd parameter is not a pointer to the class
name.
At this point, the dialog box and its child window controls are created by
Windows. Your window procedure will receive WM_CREATE message as usual.
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
After the dialog box is created, I want to set the input focus to the edit
control. If I put these codes in WM_CREATE section, GetDlgItem call will
fail since at that time, the child window controls are not created yet. The
only way you can do this is to call it after the dialog box and all its
child window controls are created. So I put these two lines after the
UpdateWindow call. GetDlgItem function gets the control ID and returns the
associated control's window handle. This is how you can get a window handle
if you know its control ID.
The program enters the message loop and before we translate and dispatch
messages, we call IsDialogMessage function to let the dialog box manager
handles the keyboard logic of our dialog box for us. If this function
returns TRUE , it means the message is intended for the dialog box and is
processed by the dialog box manager. Note another difference from the
previous tutorial. When the window procedure wants to get the text from the
edit control, it calls GetDlgItemText function instead of GetWindowText.
GetDlgItemText accepts a control ID instead of a window handle. That makes
the call easier in the case you use a dialog box.
------------------------------------------------------------------------
Now let's go to the second approach to using a dialog box as a main window.
In the next example, I 'll create an application modal dialog box. You'll
not find a message loop or a window procedure because they're not
necessary!
------------------------------------------------------------------------
dialog.asm (part 2)
------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)
.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
invoke ExitProcess,eax
#include "resource.h"
IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
------------------------------------------------------------------------
The above line calls DialogBoxParam function which takes 5 parameters: the
instance handle, the name of the dialog box template, the parent window
handle, the address of the dialog box procedure, and the dialog-specific
data. DialogBoxParam creates a modal dialog box. It will not return until
the dialog box is destroyed.
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
The dialog box procedure looks like a window procedure except that it
doesn't receive WM_CREATE message. The first message it receives is
WM_INITDIALOG. Normally you can put the initialization code here. Note that
you must return the value TRUE in eax if you process the message.
The internal dialog box manager doesn't send our dialog box procedure the
WM_DESTROY message by default when WM_CLOSE is sent to our dialog box. So
if we want to react when the user presses the close button on our dialog
box, we must process WM_CLOSE message. In our example, we send WM_COMMAND
message with the value IDM_EXIT in wParam. This has the same effect as when
the user selects Exit menu item. EndDialog is called in response to
IDM_EXIT.
Now let's examine the resource file. The notable change is that instead of
using a text string as menu name we use a value, IDR_MENU1. This is
necessary if you want to attach a menu to a dialog box created with
DialogBoxParam. Note that in the dialog box template, you have to add the
keyword MENU followed by the menu resource ID.
A difference between the two examples in this tutorial that you can readily
observe is the lack of an icon in the latter example. However, you can set
the icon by sending the message WM_SETICON to the dialog box during
WM_INITDIALOG.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Standardizing Win32 Callback Procedures
by Jeremy Gordon
This short article describes my preferred method for coding CALLBACK procedures
in a large assembler program for Windows 32. First I describe what Win32
callback procedures are, and then get down to some code.
At run time the Win32 system will call your program on a regular and frequent
basis. The procedures you supply for the system to call are called CALLBACK
procedures. Here are examples of when these are used:-
1. To manage a window you created. In this case the system will send many
messages to the Window Procedure for the window. The Window Procedure is
the code label you provide when you register your window class (by calling
RegisterClass). For example the message WM_SIZE is sent by the system
when the window is resized.
2. To inform the owner of a child window of events in the child window. For
example WM_PARENTNOTIFY (with a notify code) is sent to the Window
Procedure of the owner of a window when the child window is being created
or destroyed, or if the user clicks a mouse button while the cursor is
over the child window.
3. To inform the owner of a common control of events in the control. For
example if you create a button owned by your window the Window Procedure
for that window receives BN_CLICKED messages if the button is clicked.
4. Messages sent to a dialog you have created. These are messages relating
to the creation of the dialog and of the various controls. The dialog
procedure is informed of events in the controls.
5. If you "Superclass" or "Subclass" a common control, you receive messages
for that common control like a hook procedure but your window procedure
has the responsibility of passing them on to the control.
6. If you create "Hook" procedures you can intercept messages about to be sent
to other windows. The system will call your hook procedure and will pass
the message on only when your hook procedure returns.
7. You can ask the system to provide your program with information to be sent
to a CALLBACK procedure. Examples are EnumWindows (enumerate all top-level
windows) or EnumFonts (enumerate all available fonts).
In cases 1 to 5 above, just before the system calls the CALLBACK procedure,
it PUSHES 4 dwords on the stack (ie. 4 "parameters"). Traditionally the
names given to these parameters are:-
hWnd = handle of window being called
uMsg = message number
wParam = a parameter sent with the message
lParam = another parameter sent with the message.
Since your Window (or Dialog) procedure will need to react in a certain
way depending on the message being sent, your code will need to divert
execution to the correct place for a particular message.
"C" programmers have the advantage of being able to code this simply,
using "switch" and "case".
Assembler programmers use various techniques. Perhaps the worst if there are
a lot of messages to handle is the chain of compares, eg. (in A386 format):-
MOV EAX,[EBP+0Ch] ;get message number
CMP EAX,1h ;see if WM_CREATE
JNZ >L2 ;no
XOR EAX,EAX ;ensure eax is zero on exit
JMP >L32 ;finish
L2:
CMP EAX,116h ;see if WM_INITMENU
JNZ >L4 ;no
CALL INITIALISE_MENU
JMP >L30 ;correct exit code
L4:
CMP EAX,47h ;see if WM_WINDOWPOSCHANGED
JNZ >L8
and so on ........
WndProc:
MOV EDX,OFFSET MAINMESSAGES
CALL GENERAL_WNDPROC
RET 10h
;----------------------------------------------------------
DATA SEGMENT FLAT ;assembler to put following in data section
;--------------------------- WNDPROC message functions
MAINMESSAGES DD ENDOF_MAINMESSAGES-$ ;=number to be done
DD 312h,HOTKEY,116h,INITMENU,117h,INITMENUPOPUP,11Fh,MENUSELECT
DD 1h,CREATE,2h,DESTROY, 410h,OWN410,411h,OWN411
DD 231h,ENTERSIZEMOVE,47h,WINDOWPOSCHANGED,24h,GETMINMAXINFO
DD 1Ah,SETTINGCHANGE,214h,SIZING,46h,WINDOWPOSCHANGING
DD 2Bh,DRAWITEM,0Fh,PAINT,113h,TIMER,111h,COMMAND
DD 104h,SYSKEYDOWN,100h,KEYDOWN,112h,SYSCOMMAND
DD 201h,LBUTTONDOWN,202h,LBUTTONUP,115h,SCROLLMESS
DD 204h,RBUTTONDOWNUP,205h,RBUTTONDOWNUP
DD 200h,MOUSEMOVE,0A0h,NCMOUSEMOVE,20h,SETCURSORM
DD 4Eh,NOTIFY,210h,PARENTNOTIFY,86h,NCACTIVATE,6h,ACTIVATE
DD 1Ch,ACTIVATEAPP
ENDOF_MAINMESSAGES: ;label used to work out how many messages
;----------------------------------------------------------
_TEXT SEGMENT FLAT ;assembler to put following in code section
;----------------------------------------------------------
and where each of the functions here are procedures, for example:-
CREATE:
XOR EAX,EAX ;ensure zero and nc return
RET
GENERAL_WNDPROC:
PUSH EBP
MOV EBP,[ESP+10h] ;get uMsg in ebp
MOV ECX,[EDX] ;get number of messages to do * 8 (+4)
SHR ECX,3 ;get number of messages to do
ADD EDX,4 ;jump over size dword
L33:
DEC ECX
JS >L46 ;s=message not found
CMP [EDX+ECX*8],EBP ;see if its the correct message
JNZ L33 ;no
MOV EBP,ESP
PUSH ESP,EBX,EDI,ESI ;save registers as required by Windows
ADD EBP,4 ;allow for the extra call to here
;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam
CALL [EDX+ECX*8+4] ;call correct procedure for the message
POP ESI,EDI,EBX,ESP
JNC >L48 ;nc=don't call DefWindowProc eax=exit code
L46:
PUSH [ESP+18h],[ESP+18h],[ESP+18h],[ESP+18h] ;ESP changes on push
CALL DefWindowProcA
L48:
POP EBP
RET
NOTES:
-------------------------------------------------------------------------------
1. Instead of giving the actual message value, you can, of course, give
the name of an EQUATE. For example
WM_CREATE EQU 1h
enables you to use WM_CREATE,CREATE instead of 1h,CREATE if you wish.
2. It is tempting to keep the message table in the CODE SECTION. This is
perfectly possible because the only difference to the Win32 system between
the code section and the data section is that the code section area of
memory is marked read only, whereas the data section is read/write.
However, you may well get some loss of performance if you do this because
most processors will read data more quickly from the data section.
I performed some tests on this and found that having the table in the code
section rather than the data section could slow the code considerably:-
486 processor - 22% to 36% slower
Pentium processor - 94% to 161% slower
AMD-K6-3D processor - 78% to 193% slower
(but Pentium Pro - from 7% faster to 9% slower)
(and Pentium II - from 29% faster to 5% slower)
These tests were carried out on a table of 60 messages and the range of
results is because tests were carried out varying the number of scans
required before a find and also testing a no-find.
3. The procedure names must not be the names of API imports to avoid
confusion! For example change SETCURSOR slightly to avoid confusion
with the API SetCursor.
4. If a function returns c (carry flag set) the window procedure will call
DefWindowProc. An nc return (carry flag not set) will merely return to
the system with the return code in eax. (Some messages must be dealt with
in this way).
5. You can send a parameter of your own to GENERAL_WNDPROC using EAX.
This is useful if you wish to identify a particular window.
For example:-
SpecialWndProc:
MOV EAX,OFFSET hSpecialWnd
MOV EDX,OFFSET SPECIALWND_MESSAGES
CALL GENERAL_WNDPROC
RET 10h
6. The ADD EBP,4 just before the call to the function is to ensure that
EBP points to the parameters the stack in the same way as if the window
procedure had been entered normally. This is intended to ensure that
the function will be compatible if called by an ordinary window procedure
written in assembler, for example:-
WndProc:
PUSH EBP
MOV EBP,ESP
;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam
7. A standardized procedure for dealing with messages to a dialog procedure
can also be created in the same way, except that it should return TRUE
(eax=1) if the message is processed and FALSE (eax=0) if it is not, without
calling DefWindowProc. The same coding method can be applied to hooks and
to enumerator CALLBACKS although these will vary.
[email protected]
http://ourworld.compuserve.com/homepages/jorgon
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD
Fire Demo ported to Linux SVGAlib
by Jan Wagemakers
In APJ4 there was a little nice fire demo written in DOS assembly language.
I have ported this program to Linux assembly language. It is written in the
AT&T-syntax (GNU assembler) and makes use of SVGAlib.
My main goal of porting this program to Linux was to show that it can be
done. So, I have not optimized this program. For example, things like 'call
ioperm' can also be done by making use of int 0x80; quite possibly making use
of int 0x80 will make the program smaller. More information about int 0x80 is
available at Konstantin Boldyshev's webpage [http://lightning.voshod.com/asm].
With SVGALib you can access the screen memory directly, just like you
would write to A000:0000 in a DOS asm-program.
I like to thank 'paranoya' for his explanation about how to make use of
SVGAlib. Anyway, enough blablabla, here is the source ;-)
movw $0x3c8,%dx
movw $0,%ax
outb %al,%dx
incw %dx
lus:
outb %al,%dx
outb %al,%dx
outb %al,%dx
incw %ax
jnz lus
movl graph_mem,%ebx
Mainloop:
movl $1280,%esi # mov si,1280 ;
movl $0x5d00,%ecx # mov ch,5dh ; y-pos, the less the faster demo
pushl %esi # push si
pushl %ecx # push cx
Sloop:
movb (%ebx,%esi),%al # lodsb
incl %esi #
loop Sloop
Randoml:
mulw 1(%ebx,%edi) # mul word ptr [di+1] ; 'random' routine.
incw %ax
movw %ax,(%ebx,%edi) #stosw
incl %edi
incl %edi
loop Randoml
inb $0x60,%al
cmpb key,%al
jz Mainloop
pushl $0
call exit
addl $4,%esp
movl %ebp,%esp
popl %ebp
ret
.data
key:
.byte 0
# =============================================================================
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................ASSEMBLY.LANGUAGE.SNIPPETS
Abs
by Chris Dragan
; For comparison, the standard way (2-8 clocks on P5 and 1-17 on P6):
; or eax, eax
; js @@1
; neg eax
;@@1:
Min
by Chris Dragan
;Summary: eax = min (eax, ecx) (both eax and ecx unsigned)
;Compatibility: 386+
;Notes: 8 bytes, 4 clocks (P5), destroys ecx and edx
sub ecx, eax ; ecx = n2 - n1
sbb edx, edx ; edx = (n1 > n2) ? -1 : 0
and ecx, edx ; ecx = (n1 > n2) ? (n2 - n1) : 0
add eax, ecx ; eax += (n1 > n2) ? (n2 - n1) : 0
; Standard cmp/jbe/mov takes 2-8 clocks on P5 and 1-17 on P6
Max
by Chris Dragan
;Summary: eax = max (eax, ecx) (both eax and ecx unsigned)
;Compatibility: 386+
;Notes: 9 bytes, 5 clocks (P5), destroys ecx and edx
sub ecx, eax ; ecx = n2 - n1
cmc ; cf = n1 <= n2
sbb edx, edx ; edx = (n1 > n2) ? 0 : -1
and ecx, edx ; ecx = (n1 > n2) ? 0 : (n2 - n1)
add eax, ecx ; eax += (n1 > n2) ? 0 : (n2 - n1)
; Standard cmp/jae/mov takes 2-8 clocks on P5 and 1-17 on P6
OBJECT
by mammon_
;Summary: Primitive for defining dynamic objects
;Compatibility: NASM
;Notes: The basic building block for classes in NASM; part of
; an ongoing project of mine. Note that .this can be
; filled with the instance pointer, and additional
; routines such as .%1 [constructor] and .~ can be added.
%macro OBJECT 1
struc %1
.this: resd 1
%endmacro
%macro END_OBJECT 0
endstruc
%endmacro
;_Sample:________________________________________________________________
;OBJECT MSGBOX
; .hWnd: resd 1
; .lpText: resd 1
; .lpCapt: resd 1
; .uInt: resd 1
; .show: resd 1
;END_OBJECT
;;MyMBox is a pointer to a location in memory or in an istruc; its members
;;are filled in an init routine ['new'] with "show" being "DD _show"
;_show: ;MSGBOX class display routine
; push dword [MyMbox + MSGBOX.uInt]
; push dword [MyMbox + MSGBOX.lpCapt]
; push dword [MyMbox + MSGBOX.lpText]
; push dword [MyMbox + MSGBOX.hWnd]
; call MessageBoxA
; ret
;..start:
; call [MyMbox + MSGBOX.show]
; ret
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................ISSUE.CHALLENGE
Binary-to-ASCII
by Jan Verhoeven
The Challenge
-------------
Write a routine to convert the value of a bit to ASCII in under 10 bytes, with
no conditional jumps.
The Solution
------------
Load the number into the AX register and shift through the bits. If a bit is
cleared [0], you want to print a "0" character; if a bit is set [1], you want
to print a "1".
Prime the BL register with the ASCII character "0"; if the next bit in AX is
set, carry will be set after the SHL and BL will thus be incremented to an
ASCII "1". The key, as you will see, is the ADC [AddWithCarry] instruction:
7 bytes all told; with a loop and mov instruction for storing each value in
BL to the location of your choice, you will have a full-fledged binary-to-
ascii converter in a handful of bytes.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::.......................................................FIN