::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. 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 asmjournal@mailcity.com T A B L E O F C O N T E N T S ---------------------------------------------------------------------- Introduction...................................................mammon_ "COM in Assembly Part II"...................................Bill.Tyler "How to use DirectDraw in ASM"...............................X-Calibre "Writing Boot Sectors To Disk"...........................Jan.Verhoeven "Dumping Memory to Disk".................................Jan.Verhoeven "Formatted Numeric Output"..............................Laura.Fairhead "Linked Lists in ASM"..........................................mammon_ Column: Win32 Assembly Programming "Structured Exception Handling under Win32"...........Chris.Dragan "Child Window Controls"...................................Iczelion "Dialog Box as Main Window"...............................Iczelion "Standardizing Win32 Callback Procedures"............Jeremy.Gordon Column: The Unix World "Fire Demo ported to Linux SVGAlib".................Jan.Wagemakers Column: Assembly Language Snippets "Abs".................................................Chris.Dragan "Min".................................................Chris.Dragan "Max".................................................Chris.Dragan "OBJECT"...................................................mammon_ Column: Issue Solution "Binary to ASCII"....................................Jan.Verhoeven ---------------------------------------------------------------------- ++++++++++++++++++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. And, finally, I am looking for a good 'challenge' columnist: someone to write the monthly APJ challenges [and their solutions] so that I can start announcing next month's challenge sooner than next month... Now at last I can sleep ;) _m ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................FEATURE.ARTICLE COM in Assembly Part II by Bill Tyler My previous atricle described how to use COM objects in your assembly language programs. It described only how to call COM methods, but not how to create your own COM objects. This article will describe how to do that. 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 STDMETHOD is used to simplify the interface declaration, and is defined as: 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 This macro is used to greatly simplify interface declarations, and so that the MASM invoke syntax can be used. (Macro originally by Ewald :) Access to the interface's methods occurs through a pointer. This pointer points to a table of function pointers, called a vtable. Here is a sample method call: mov eax, [lpif] ; lpif is the interface pointer mov eax, [eax] ; get the address of the vtable invoke (IInterfaceVtbl [eax]).Method1, [lpif] ; indirect call to the function - or - invoke [eax][IInterfaceVtbl.Method2], [lpif] ; alternate notation 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 A GUID is then defined in the data section: MyGUID GUID <3F2504E0h, 4f89h, 11D3h, <9Ah, 0C3h, 0h, 0h, 0E8h, 2Ch, 3h, 1h>> Once a GUID is assigned to an interface and published, no furthur changes to the interface definition are allowed. Note, that this does mean that the interface implementation may not change, only the definition. For changes to the interface definition, a new GUID must be assigned. 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. A typical object will contain pointers to the various interfaces it supports, a reference count, and any other data that the object needs. Here is a sample object definition, implemented as a structure: 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. @@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 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. Objects must have an identity - a call to QueryInterface must always return the same pointer value. 2. The set of interfaces of an object must never change - for example, if a call to QueryInterface with on IID succeeds once, it must succeed always. Likewise, if it fails once, it must fail always. 3. It must be possible to successfully query an interface of an object from any other interface. QueryInterface returns a pointer to a specified interface on an object to which a client currently holds an interface pointer. This function must call the AddRef method on the pointer it returns. Following are the QueryInterface parameters: pif : [in] a pointer to the calling interface riid : [in] pointer to the IID of the interface being queried ppv : [out] pointer to the pointer of the interface that is to be set. If the interface is not supported, the pointed to value is set to 0 QueryInterface returns the following: S_OK if the interface is supported E_NOINTERFACE if not supported Here is a simple assembly implementation of QueryInterface: IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD ; The following compares the requested IID with the available ones. ; In this case, because IInterface inherits from IUnknown, the IInterface ; interface is prefixed with the IUnknown methods, and these 2 interfaces ; share the same interface pointer. invoke IsEqualGUID, [riid], addr IID_IInterface or eax,eax jnz @1 invoke IsEqualGUID, [riid], addr IID_IUnknown or eax,eax jnz @1 jmp @NoInterface @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 ; now get the pointer to the requested interface lea eax, (Object ptr [eax]).interface ; set *ppv with this interface pointer mov ebx, [ppv] mov dword ptr [ebx], eax ; increment the reference count by calling AddRef GETOBJECTPOINTER Object, interface, pif mov eax, (Object ptr [eax]).interface invoke (IInterfaceVtbl ptr [eax]).AddRef, 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. Following is a simple implementation of the AddRef method: IInterface@AddRef proc pif:DWORD GETOBJECTPOINTER Object, interface, pif ; increment the reference count inc [(Object ptr [eax]).nRefCount] ; now return the count mov eax, [(Object ptr [eax]).nRefCount] ret IInterface@AddRef endp 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 Here is a simple implementation of Release: IInterface@Release proc pif:DWORD GETOBJECTPOINTER Object, interface, pif ; decrement the reference count dec [(Object ptr [eax]).nRefCount] ; check to see if the reference count is zero. If it is, then destroy ; the object. mov eax, [(Object ptr [eax]).nRefCount] or eax, eax jnz @1 ; 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 Creating a COM object ------------------------------------------------------------------------------ Creating an object consists basically of allocating the memory for the object, and then initializing its data members. Typically, the vtable pointer is initialized and the reference count is zeroed. QueryInterface could then be called to get the interface pointer. 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. COM implementatiion sample application ------------------------------------------------------------------------------ Here follows a sample implementation and usage of a COM object. It shows how to create the object, call its methods, then free it. It would probably be very educational to assemble this and run it through a debugger. This and other examples can be found at http://asm.tsx.org. .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 ; Macro that takes an interface pointer and returns the implementation ; pointer in eax GETOBJECTPOINTER MACRO Object, Interface, pif mov eax, pif IF (Object.Interface) sub eax, Object.Interface ENDIF ENDM ;----------------------------------------------------------------------------- IInterface@QueryInterface proto :DWORD, :DWORD, :DWORD IInterface@AddRef proto :DWORD IInterface@Release proto :DWORD IInterface@Get proto :DWORD IInterface@Set proto :DWORD, :DWORD CreateObject proto :DWORD IsEqualGUID 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 ; declare the object structure Object struct ; interface object interface IInterface ; 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 ; define the interface's IID ; {CF2504E0-4F89-11d3-9AC3-0000E82C0301} IID_IInterface GUID <0cf2504e0h, 04f89h, 011d3h, <09ah, 0c3h, 00h, 00h, 0e8h, 02ch, 03h, 01h>> ;----------------------------------------------------------------------------- .code start: StartProc proc LOCAL pif:DWORD ; interface pointer ; create the object invoke CreateObject, addr [pif] or eax,eax js exit ; call the SetValue method mov eax, [pif] mov eax, [eax] invoke (IInterfaceVtbl ptr [eax]).SetValue, [pif], 12345h ; call the GetValue method mov eax, [pif] mov eax, [eax] invoke (IInterfaceVtbl ptr [eax]).GetValue, [pif] ; release the object mov eax, [pif] mov eax, [eax] invoke (IInterfaceVtbl ptr [eax]).Release, [pif] 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 ; increment the reference count GETOBJECTPOINTER Object, interface, pif mov eax, (Object ptr [eax]).interface invoke (IInterfaceVtbl ptr [eax]).AddRef, [pif] ; 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 IInterface@AddRef proc pif:DWORD GETOBJECTPOINTER Object, interface, pif inc [(Object ptr [eax]).nRefCount] mov eax, [(Object ptr [eax]).nRefCount] ret IInterface@AddRef endp IInterface@Release proc pif:DWORD GETOBJECTPOINTER Object, interface, pif dec [(Object ptr [eax]).nRefCount] mov eax, [(Object ptr [eax]).nRefCount] or eax, eax jnz @1 ; free object mov eax, [pif] mov eax, [eax] invoke LocalFree, eax @1: ret IInterface@Release endp IInterface@GetValue proc pif:DWORD GETOBJECTPOINTER Object, interface, pif mov eax, (Object ptr [eax]).nValue ret IInterface@GetValue endp IInterface@SetValue proc uses ebx pif:DWORD, val:DWORD GETOBJECTPOINTER Object, interface, pif mov ebx, eax mov eax, [val] mov (Object ptr [ebx]).nValue, eax ret IInterface@SetValue 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: mov ebx, eax mov (Object ptr [ebx]).interface.lpVtbl, offset vtblIInterface mov (Object ptr [ebx]).nRefCount, 0 mov (Object ptr [ebx]).nValue, 0 ; Query the interface lea ecx, (Object ptr [ebx]).interface mov eax, (Object ptr [ebx]).interface.lpVtbl invoke (IInterfaceVtbl ptr [eax]).QueryInterface, ecx, addr IID_IInterface, [pobj] cmp eax, S_OK je return ; error in QueryInterface, so free memory push eax invoke LocalFree, ebx pop eax 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. Copyright (C) 1999 Bill Tyler (billasm@usa.net) ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::..........................................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. What are objects really? 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: 1. Get the vtable. 2. Get the address of the function, using the vtable. The first part is simple. All the struct contains, is the pointer to the vtable. So we can just do this: mov eax, [lpDD] mov eax, [eax] 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] That is not a bad guess... But there's one more thing, which is very important: this function needs to be invoked on the IDirectDraw object. We may only see the vtable in the structure, but there are also private members inside the object. So there's more than meets the eye here. What it comes down to is that the call needs the object as an argument. And this will be done by stack as always. So we just need to push lpDD before we call. The complete call will look like this: 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: SetDisplayMode( width, height, bpp ); 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] And that's all there is to it. To make life easier, we have included some MASM macros in DDRAW.INC, for use with the IDirectDraw and IDirectDrawSurface objects: DDINVOKE MACRO func, this, arglist :VARARG mov eax, [this] mov eax, [eax] IFB INVOKE [IDirectDrawVtbl. func][eax], this ELSE INVOKE [IDirectDrawVtbl. func][eax], this, arglist ENDIF ENDM DDSINVOKE MACRO func, this, arglist :VARARG mov eax, [this] mov eax, [eax] IFB 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: DDINVOKE RestoreDisplayMode, lpDD DDINVOKE SetDisplayMode, lpDD, 320, 200, 32 Well, that's basically all there is to know about using objects, COM and DirectX in Win32ASM. Have fun with it! And remember: C and C++ knowledge is power! ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................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. The assembler used ------------------ As usual, I have written this code in A86 format. Until now, not many aspects of the A86 extensions have been used, but, believe me, in future articles this will be done. 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. Rapid assembly prototyping. --------------------------- For easy processing and running sourcefiles I use a small batchfile, which looks like: ----------- Run.Bat --------------------------------------- Start --------- @echo off if "%1" == "" goto leave :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 if errorlevel 27 goto start if errorlevel 26 goto screen if errorlevel 25 goto list if errorlevel 4 goto start if errorlevel 3 goto debugger if errorlevel 2 goto execute if errorlevel 1 exit goto menu :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. How to write a sector to disk. ------------------------------ Globally there are three methods. The first would be to program the floppy disk controller, but that is just downright difficult. A second approach would be to use INT 026, the way DOS does things. 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 code is very straightforward. What it does is: - reset disk drive controller - open the file to transfer to the bootsector - read file into internal buffer - close the file - repeat 5 times: - try to tranfer buffer to bootsector of drive A: - shut down and return to DOS. - if an error occurs, the user is informed about it. That's all there's to it. The Source. ----------- Below is the sourcecode for this short utillity. I have commented just about any line I thought fit for it. ----------- Wrs.A86 --------------------------------------- Start --------- name wrs title WRite Sector page 80, 120 stdout = 1 ; the "standard" equates lf = 10 cr = 13 DATA segment ; define the volatile data area buffer db 512 dup (?) ; this is enough for one sector EVEN ; make sure WORD starts at an even address Handle dw ? ; handle number of file to write ; ---------------------- CODE segment ; start of the actualk code ; no ORG, so we start at offset 0100 jmp main ; jump forward to entry point db 'VeRsIoN=0.2', 0 db 'CoPyRiGhT=CopyLeft 1999, Jan Verhoeven, ' db 'jverhoeven@bigfoot.com', 0 ; ---------------------- filename db 'BootLoad.bin', 0 ; name of file to send to disk Mess001 db 'Cannot open file BootLoad.bin. ' db 'Operation aborted.', cr, lf Len001 = $ - Mess001 Mess002 db 'Something went wrong while writing to disk.', cr, lf Len002 = $ - Mess002 Mess003 db 'The floppy disk subsytem reported an error. ' db 'Trying once more.', cr, lf Len003 = $ - Mess003 Mess004 db cr, lf, 'Bootsector written. ' db 'Thank you for using this software.' db cr, lf, 'This program is GNU GPL free software and you use ' db 'it at your won risk.' db cr, lf, 'Please study the GNU ' db 'General Public License for more details.', cr, lf Len004 = $ - Mess004 ; ---------------------- Error1: mov dx, offset mess001 ; process "cannot open file" mov cx, len001 mov bx, stdout mov ah, 040 int 021 ; print via DOS mov ax, 04C01 ; exit with errorcode = 1 int 021 ; ---------------------- Error2: mov dx, offset mess002 ; process "disk error" mov cx, len002 mov bx, stdout mov ah, 040 int 021 ; via DOS mov ax, 04C02 ; exit with errorcode = 2 int 021 ; ---------------------- Error2a: push ax, bx, cx, dx ; process "Disk not ready" mov dx, offset mess003 ; point to message mov cx, len003 ; this many bytes mov bx, stdout ; to the console mov ah, 040 ; do a write int 021 ; via DOS pop dx, cx, bx, ax ; restore state of machine ret ; and return to caller ; ---------------------- main: mov dl, 0 ; choose drive A: mov ah, 0 ; select funtion 0 ... int 013 ; ... reset diskdrives mov dx, offset filename ; point to name of file mov ax, 03D00 ; to open int 021 ; via DOS jc Error1 ; if error, take action mov [Handle], ax ; no error, => ax = handle mov dx, offset buffer ; setup pointer, ... mov cx, 512 ; ... byte count, ... mov bx, ax ; ... and handle mov ah, 03F ; to read data from file int 021 ; via DOS mov bx, [Handle] mov ah, 03E int 021 ; close this file mov cx, 5 ; prepare for a five times LOOP L0: push cx mov bx, offset buffer mov es, ds ; es:bx = buffer to read from mov dx, 0000 ; drive A:, head 0 mov cx, 0001 ; Track 0, Sector 1 mov ax, 0301 ; Write sectors, 1 sector int 013 ; via BIOS jnc >L1 ; if no error, jump forward pop cx ; Houston, we have an error! call error2a ; inform the user loop L0 ; and try again jc error2 ; after five times still no go.... L1: mov dx, offset Mess004 ; Signal that we're successful mov cx, Len004 mov bx, stdout ; to the console mov ah, 040 int 021 ; via DOS (so it can be redirected) mov ax, 04C00 ; mention that there were no errors int 021 ; and return to DOS ----------- Wrs.A86 ---------------------------------------- End ---------- Have fun experimenting with bootsectors. But take care that this will NOT work on a hard disk. Hard disk structure. -------------------- A hard disk uses another layout for it's structure. The very first sector of a HDD is the MBR (Master BootRecord). It is the only sensible sector in the first track of a normal HDD. The rest is just empty. Each partition starts at a cylinder boundary, so the first one starts at cylinder 1 (track 1, side 0, sector 1). The very first sector of a bootable partition is the bootsector. 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. jverhoeven@bigfoot.com ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................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. The program itself. ------------------- The source is documented so it speaks for itself. At points of interest I have insterted "break" and "restart" lines with space for additional remarks. 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. --- Mem2File ------------------------------------------------- Start --- name mem2file title Send an area of memory to a diskfile. page 80, 120 ; version 1.0 : Had to be compiled for each area/filename OK: 01-01-1991 ; version 1.1 : Same as above, for A86 format OK: 01-01-1999 ; version 1.2 : Make it commandline driven OK: 01-02-1999 ; version 1.3 : Make it reliable OK: 02-02-1999 ; ---------------------- stdout = 1 tab = 9 lf = 10 cr = 13 clr MACRO ; macro called CLeaR mov #1, 0 ; move it with zero #EM ; and get outa here Dum1 STRUC ; a structure definition OffVal dw ? SegVal dw ? ENDS ; ---------------------- DATA segment ; this is where the volatile data lives ByteF = $ dummy db ? ; just to fool D86.... ; if this dummy variable is not here, D86 will ; reference variable "Start" as "ByteF". even Start dw ?, ? ; segment:address to start Stop dw ?, ? ; segment:address to stop Blocks dw ? ; number of 16K chucks to save Rest dw ? ; remaining part to save ArgNum dw ? ; nr of bytes in this argument OldClp dw ? ; current pointer into command line Handle dw ? Length dw ?, ? FileName: ; this storage is used twice.... Argument db 80 dup (?) ; storage for next argument from command-line Output db 16K dup (?) ; buffered output --- Mem2File ------------------------------------------------- Break --- 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. No big deal, just a nice feature. Also, if you need to process large binary numbers you may group them into sub-units separated by underscores. So the number: 1000100100111101 is hard to read back. But if we insert "_" markers like: 1000_1001_0011_1101 the grouping of bits makes them easier to understand. Not that it is a matter of life and death, but it can come in handy once in a while. --- Mem2File ------------------------------------------------ Restart -- Bytes = $ - ByteF ; number of volatile databytes ; ---------------------- CODE segment ; no ORG, so we start at 0100 jmp main HexTable db '0123456789ABCDEF', 0 db 'VeRsIoN=Mem2File 1.3', 0 db 'CoPyRiGhT=CopyLeft Jan Verhoeven, jverhoeven@bigfoot.com', 0 Mess001 db 'Mem2File collects a part of conventional memory and ' db 'sends it to a file.', cr, lf, lf db 'The syntax is:', cr, lf, lf db tab, 'Mem2File segm1:offs1 [-] segm2:offs2 ' db 'file.ext.', cr, lf, lf db 'Mem2File is GNU GPL style FREE software. ', cr, lf db 'Please read the GNU GPL if you are in doubt.', cr, lf, lf Mess002 db 'Mem2File was made by Jan Verhoeven, NL-5012 GH 272, ' db 'The Netherlands', cr, lf db 'E-mail address : jverhoeven@bigfoot.com', cr, lf, lf Len001 = $ - Mess001 Len002 = $ - Mess002 Mess004 db 7, 'Error! All numbers are expected to be hexadecimal.' db cr, lf Len004 = $ - Mess004 ;------------------------ InitMem: mov di, ByteF ; Init volatile memory with zero's. mov cx, Bytes ; saves a lot of strange problems. mov al, 0 rep stosb ret ;------------------------ --- Mem2File ------------------------------------------------- Break --- 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? --- Mem2File ------------------------------------------------ Restart -- L0: mov b [di], 0 ; terminate argument string mov [OldClp], si ; done, => clean up. clc ; indicate "No Error" L3: pop di, si, ax ; restore registers, ... ret ; ... and leave. 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 --- Mem2File ------------------------------------------------- Break --- This construction is what I particularly like. I want to check if AL is Zero. Normally you can code 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 instruction to fullfill the specification. But that is poor programming. It is better to let the software do this for us. AL can have any value between 020 and 0FF, plus 00, tab, lf and cr. 01 is not an option. So the sequence cmp al, 1 ; ONLY if AL is 0, we get a carry jc L3 ; if CARRY, we're done will send us to the errorexit WITH the carryflag set, all in one, without explicitly having to set the carry flag. --- Mem2File ------------------------------------------------ Restart -- L2: stosb ; else store char in Arguments array inc [ArgNum] ; adjust counter lodsb ; and get next char cmp al, ' ' ; is it a delimiting space? je L0 cmp al, tab ; or a tab? je L0 cmp al, ':' ; or a colon? je L0 cmp al, 0 ; or an end-of-line? jne L2 ; if not, loop back, mov si, 0FFFF ; else make SI ridiculously high, ... stc ; ... set carry flag, ... jmp L0 ; and get out. --- Mem2File ------------------------------------------------- Break --- 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. --- Mem2File ------------------------------------------------ Restart -- ;------------------------ L1: stc ; byte not in table! pop dx ; we came here with carry set! ret ; exit L2: sub bx, dx ; calculate position in table pop dx clc ; make sure carry is cleared ret --- Mem2File ------------------------------------------------- Break --- This is a typical A86 construction. The subroutine is called TableFind and it starts in the next line and ends in the previous one! 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. --- Mem2File ------------------------------------------------ Restart -- TableFind: ; find AL in ASCIIZ table [BX] ... push dx ; ... and report position mov dx, bx ; keep value of SI L0: cmp b [bx], 0 ; is it end of table? je L1 ; if so, jump out cmp al, [bx] ; compare byte with table je L2 ; if same, jump out inc bx ; else increment pointer jmp L0 ; and loop back ;------------------------ MakeUpper: cmp al, 'a' ; too low? jb ret --- Mem2File ------------------------------------------------- Break --- An A86 enhancement: a conditional return instruction. All sensible CPU's have conditional CALL and RET instructions. Not the 80x86 line. This CPU was meant to be structured. So you have to put a conditional jump before the call, and introduce yet another silly labelname for the next instruction. The "Jcc ret" is a good way to circumvent this ommission. What it does is the same as what, on a Z-80, would be done with a "RET cc" instruction. There is one catch, however: there must be a RET instruction within reach PRIOR to the "Jcc Ret" (internal) macro. If that is a problem, you could also use the line: IF cc Ret So either way you, the programmer, win. --- Mem2File ------------------------------------------------ Restart -- cmp al, 'z' ; if in range, ... ja ret and al, not bit 5 ; ... make uppercase --- Mem2File ------------------------------------------------- Break --- 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 Any value between 0 and 15 is valid in A86 (0 - 31 for A386) to refer to the respective bit in the respective source. --- Mem2File ------------------------------------------------ Restart -- ret ;------------------------ BadNumber: ; hey typo, you made a dumbo! mov dx, offset Mess004 mov cx, Len004 mov bx, StdOut mov ah, 040 int 021 mov ax, 04C02 ; and exit with errorcode 2 int 021 ;------------------------ SyntErr: mov dx, offset Mess001 mov cx, Len001 mov bx, StdOut mov ah, 040 ; print out "help" screen and ... int 021 mov ax, 04C01 ; ... exit with errorcode 1 int 021 ;------------------------ L8: mov ax, dx ; Convert has result in DX, that's why. pop dx, bx ret Convert: push bx, dx ; convert ASCII to Hex. mov si, offset Argument clr dx ; dx will contain result --- Mem2File ------------------------------------------------- Break --- Here the macro is invoked. It is used to load the DX register with zero. If later you decide to change the way in which you want to clear registers, just change the macro. In LST files (the assembler listings) the expansions are controlled by means of the +L switch. If you issue the option "+L35" macro's will not be expanded in the listings file. --- Mem2File ------------------------------------------------ Restart -- L1: lodsb ; get first character cmp al, 0 ; end of string? je L8 call MakeUpper ; if not, make uppercase mov bx, offset HexTable call TableFind ; and lookup in table jc BadNumber shl dx, 4 ; multiply DX by 16 --- Mem2File ------------------------------------------------- Break --- 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 More code in the executable, but it makes programming easier. If on a modern CPU, you can force A86 to act as if the CPU were a vintage 88 with the commandline switch +P65. --- Mem2File ------------------------------------------------ Restart -- or dl, bl ; bx = index into table jmp L1 ; repeat until done ;------------------------ Credits: mov dx, offset Mess002 mov cx, Len002 mov bx, stdout mov ah, 040 int 021 ; print some egotripping data ret ;------------------------ main: call InitMem ; prime volatile data mov al, [080] ; get tail length cbw ; make 16 bits long mov si, 081 ; point to start of tail add si, ax ; point to end of tail mov [si], ah ; make commandtail ASCIIZ call GetArg ; get argument from command tail jc SyntErr ; if error, get out call Convert ; convert text to hex mov [Start.SegVal], ax ; store it call GetArg ; etcetera jc SyntErr call Convert mov [Start.OffVal], ax L0: call GetArg jc SyntErr cmp b [Argument], '-' ; single '-' character? je L0 ; if so, ignore it call Convert mov [Stop.SegVal], ax call GetArg jc SyntErr call Convert mov [Stop.OffVal], ax call GetArg IF C jmp SyntErr --- Mem2File ------------------------------------------------- Break --- This is one of the A86 enhancements. This IF construct prevents that you have to make up all kinds of ridiculous labelnames like jmp_001F in a construct as follows: call GetArg jnc jmp_01F jmp SyntErr jmp_01F: ... The IF construct (not to be confused with the "#IF" construct which is for conditional assemblies) enables you to just make a fast jump by stating the reverse condition in the IF statement and acting further like a high level language: IF C jmp SyntErr Neat, isn't it? --- Mem2File ------------------------------------------------ Restart -- mov si, offset Argument add si, [ArgNum] mov b [si], 0 ; make it ASCIIZ mov dx, offset FileName ; same as Argument buffer.... mov cx, 0 mov ah, 03C int 021 ; create the file IF C jmp SyntErr --- Mem2File ------------------------------------------------- Break --- See how powerful the IF construct can be? It is a very convenient way to circumvent the foolish conditional instructions of the x86 architecture. --- Mem2File ------------------------------------------------ Restart -- mov [Handle], ax mov ax, [Stop.SegVal] mov dx, 0 ; prime DX mov cx, 4 L0: shl ax, 1 rcl dx, 1 loop L0 ; shift upper 4 bits of address into DX add ax, [Stop.OffVal] adc dx, 0 ; now, dx:ax = linear address to stop at mov [Stop.SegVal], dx mov [Stop.OffVal], ax ; store linear address STOP mov ax, [Start.SegVal] mov dx, 0 ; prime DX mov cx, 4 L0: shl ax, 1 rcl dx, 1 loop L0 ; shift upper 4 bits of address into DX add ax, [Start.OffVal] adc dx, 0 ; now, dx:ax = linear address to start from mov [Start.SegVal], dx mov [Start.OffVal], ax ; store linear address START cmp dx, [Stop.SegVal] ; start > stop? ja >L1 ; fix it! jb >L2 --- Mem2File ------------------------------------------------- Break --- 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. --- Mem2File ------------------------------------------------ Restart -- cmp ax, [Stop.OffVal] ; start > stop? jbe >L2 ; if not, OK L1: push [Stop.SegVal] push [Stop.OffVal] ; swap start and stop addresses mov [Stop.SegVal], dx mov [Stop.OffVal], ax pop [Start.OffVal] pop [Start.SegVal] L2: mov dx, [Stop.SegVal] mov ax, [Stop.OffVal] add ax, 1 adc dx, 0 ; limits are INCLUSIVE sub ax, [Start.OffVal] sbb dx, [Start.SegVal] ; dx:ax = bytes to move 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 mov ax, [Start.SegVal] ; end of linear addressing, we're going to DOS! mov cl, 12 shl ax, cl mov [Start.SegVal], ax mov es, ds ; use es to refer to data mov bx, [Handle] lds dx, d [Start] --- Mem2File ------------------------------------------------- Break --- Normally this instruction would require a secretary with 100 letters per minute typing rate: lds dx, dword ptr [Start] 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. :) --- Mem2File ------------------------------------------------ Restart -- es cmp [Blocks], 0 ; if less than 16 Kb, skip this one. --- Mem2File ------------------------------------------------- Break --- 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. --- Mem2File ------------------------------------------------ Restart -- 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 S0: es cmp [Rest], 0 je >L1 mov ah, 040 es mov cx, [Rest] int 021 L1: mov ds, es mov bx, [Handle] mov ah, 03E int 021 ; close file call Credits ; show my ego mov ax, 04C00 int 021 ; exit to DOS --- Mem2File -------------------------------------------------- End ---- That's it. jverhoeven@bigfoot.com ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................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: EAX = SSFFPPRR (hexadecimal value of course) SS size size of the datum you will be calling the main routine with, only 3 values are valid: 0=byte 1=word 2=dword FF field size of a field in which to right-justify the number, if this is = 0 then there is no right-justification you only get the number PP pad the ASCII value of the character to use to right-justify the number in the field of output RR radix the radix to output the number in 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;) Here is the code: ================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) ; nuconvs PROC NEAR MOV DWORD PTR CS:[nuradix],EAX RET nuconvs ENDP ; ;control parameters ; ; !! these absolutely must be in the below order due to the way the above ; routine works ; nuradix DB ? ;output radix nupad DB ? ;pad character nufld DB ? ;field size nudsiz DB ? ;data size ; ;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) ; nuconv PROC NEAR ; ;all registers are going to be preserved ; PUSH DS PUSH EAX PUSH EBX PUSH CX PUSH EDX ; ;save some CS: overrides ; PUSH CS POP DS ; ;initialise ; ; set EBX =radix ; CX =fieldsize ; ; also we zero pad out the datum passed so it fills EAX ; XOR EBX,EBX CMP BL,BYTE PTR DS:[nudsiz] JNP SHORT ko1 JS SHORT ko0 MOV AH,0 ko0: DEC BX AND EAX,EBX INC BX ko1: MOV BL,BYTE PTR DS:[nuradix] MOV CH,0 MOV CL,BYTE PTR DS:[nufld] ; ;calculate digits and push to stack ; ; EAX is divided and modulus taken which is the standard way, ; loop exits when it reaches 0 or the field size is hit ; notice that if CX=0 on entry to this then the field size ; will be effectively unbounded ; nulop0: XOR EDX,EDX DIV EBX PUSH DX AND EAX,EAX LOOPNZ nulop0 ; ;'output' the field padding ; ; the number of padding characters is normally the value ; now in CX (ie: fieldsize - digits ). however no pad chars ; should be output if field size = 0. i think the check here ; for this is nice and tight (read the code...) ; MOV BX,CX NEG BX JNS SHORT ko MOV AL,BYTE PTR DS:[nupad] REP STOSB ko: ; ;'output' all the digits ; ; CX is set to the number of digits on the stack we have to output ; ie: fieldsize - ( fieldsize - digits ) ; MOV CH,0 MOV CL,BYTE PTR DS:[nufld] ADD CX,BX ; ; now we pop off those #CX digits translating into ASCII using a nice ; variation of the traditional speed method ; MOV BX,OFFSET nudat 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_ Assembly language is notorious for being low-level; to wit, it lacks many of the features in higher-level languages which make programming easier. In the course of my work in the visasm project I have put quite a bit of time into working on exactly which higher language features are important and which, in a nutshell, are swill. 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. In this article I have undertaken an implementation of a linked list in NASM; the implementation is generic enough to support more complex data structures, and should port to other assemblers with few changes. 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 EXTERN AllocConsole ;BOOL AllocConsole() EXTERN GetStdHandle ;HANDLE GetStdHandle( nStdHandle ) EXTERN WriteConsoleA ;HANDLE hConsole, lpBuffer, Num2Write, lpWritten,NULL EXTERN ExitProcess ;UINT ExitCode EXTERN GetProcessHeap ; EXTERN HeapAlloc ;HANDLE hHeap,DWORD dwFlags, DWORD dwBytes:ret ptr EXTERN HeapFree ;HANDLE hHeap,DWORD dwFlags, LPVOID lpMem EXTERN HeapReAlloc ;HANDLE hHeap,DWORD dwFlags,LPVOID lpMem,DWORD dwBytes EXTERN HeapDestroy ;HANDLE hHeap %define HEAP_NO_SERIALIZE 0x00000001 %define HEAP_GROWABLE 0x00000002 %define HEAP_GENERATE_EXCEPTIONS 0x00000004 %define HEAP_ZERO_MEMORY 0x00000008 %define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010 %define HEAP_TAIL_CHECKING_ENABLED 0x00000020 %define HEAP_FREE_CHECKING_ENABLED 0x00000040 %define HEAP_DISABLE_COALESCE_ON_FREE 0x00000080 %define HEAP_CREATE_ALIGN_16 0x00010000 %define HEAP_CREATE_ENABLE_TRACING 0x00020000 %define HEAP_MAXIMUM_TAG 0x0FFF %define HEAP_PSEUDO_TAG_FLAG 0x8000 %define HEAP_TAG_SHIFT 16 ;===========================================================End API Definitions In addition, it is useful to define a few common routines for use later: ;==============================================================Utility Routines [section data class=DATA use32] ;set up the segments early %macro STRING 2+ %1: db %2 .end: %define %1.length %1.end - %1 %endmacro [section code class=CODE use32] 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 ;Macro to ensure that .next is always at offset zero in user-defined lists %macro LISTSTRUCT 1 struc %1 .next: resd 1 %endmacro %macro END_LISTSTRUCT 0 endstruc %endmacro [section code] ;Note that these assume an LISTSTRUCT base type _create_list: ; ptrHead_create_list( hHeap, NodeSize ) %define _hHeap ebp + 8 %define _ListSize ebp + 12 ENTER 0 , 0 push dword [_ListSize] ;size of LISTSTRUCT push dword HEAP_ZERO_MEMORY ;FLAG for HeapAlloc push dword [_hHeap] ;Heap being used call HeapAlloc test eax, eax jz .Error ;Alloc failed! mov [eax + _llist.next], dword 0 ;.next pointer = NULL .Exit: LEAVE ;eax = ptrHead ret 8 .Error: xor eax, eax ;error = return NULL jmp .Exit _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. The implementation routines should be pretty straight forward. _create_list allocates memory from the local heap of the size of one list node [determined by the parameter NodeSize passed to _create_list] and returns the address of the allocated memory; since this node is assumed to be the list "head", the .next member is set to NULL. _delete_list is passed the address of the head node of the list; it saves the address in the .next member of the node and then frees the memory allocated to the node, repeating this with each .next link until the .next member is NULL [indicating an end of list]. _add_node is used to insert a node into an existing list; it is passed the address of the node after which the new node is to be inserted. The .next member of this node is moved into the .next member of the new node, and replaced with the address of the new node. Thus, if before insertion the list had the structure .next [Node1] --> .next [Node2] --> .next [NULL] .data NULL .data Node1 .data Node2 then it would have the following structure after insertion following Node1: .next [Node1] --> .next [NewNode] --> .next [Node2] --> .next [NULL] .data NULL .data Node1 .data NewNode .data Node2 _del_node does the opposite of _add_node; it moves the .next member of the node to be deleted into the .next member of the preceding node, then frees the specified node. Note that both _del_node and _add_node are designed to be as generic as possible and make no assumptions regarding the linked list structure; thus in a double linked list of the format struc DLLIST .next .prev .data endstruc one could front-end the Delete function as follows: DelNode: push dword eax ;eax = Node to delete push dword [eax + DLLIST.prev] push hHeap call _del_node ret 8 The other linked list routines can be provided with similar front-ends to take care of common heap handles, list sizes, and member assignments. 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. ;=======================================================Linked List Application [section data] hHeap dd 0 ptrHead dd 0 STRING strData1, 'node 1',0Dh,0Ah STRING strData2, 'node 2',0Dh,0Ah STRING strData3, 'node 3',0Dh,0Ah STRING strStart, 'Creating List',0Dh,0Ah STRING strDone, 'Finished!',0Dh,0AH,'Printing Data...',0Dh,0Ah STRING strErr, 'Error!',0Dh, 0AH 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 push dword strStart.length push dword strStart call puts 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 call NewNode ;create Node1 test eax, eax jz ListDone push dword strData1 push dword llist.data push eax call _set_node_data ;set Node1.data to 'node1' call NewNode ;create Node2 test eax, eax jz ListDone push dword strData2 push dword llist.data push eax call _set_node_data ;set Node2.data to 'node2' call NewNode ;create Node3 test eax, eax jz ListDone push dword strData3 push dword llist.data push eax call _set_node_data ;set Node3.data to 'node3' ListDone: push dword strDone.length push dword strDone call puts mov ebx, [ptrHead] PrintList: push dword _llist.next push ebx call _get_node_data ;could have been mov eax,[ebx] test eax, eax ;if ptrCurrent.next == NULL exit jz Exit ; [end of list] mov ebx, eax ;save ptrNode push dword strData1.length ;push length for call to puts push dword llist.data push ebx call _get_node_data ;get ptrNode.data push dword eax ;push string for call to puts call puts jmp PrintList ;loop 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 Structured Exception Handling is a powerful feature of all Win32 platforms that allows a program to recover from any critical errors like BOUND, divide overflow, page missing or general protection fault. It is documented only for C-level usage (try-except/finally syntax), and no documentation for low level languages exists. Therefore I will try to show how to use it. 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 TIB is accessible at address fs:0. NT_TIB.Self contains linear address of TIB, base of FS segment. When an exception occurs, the system uses (dword)fs:0, NT_TIB.ExceptionList to find an exception handler and execute it. The exception list entry is very simple: 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 C compilers usually keep some additional information in E_L_ENTRY.Optional field of varying size and usually terminated with (dword)-1. Both .Optional and .EntryTerminator fields are not required. Before calling an exception handler, the exception manager pushes ExceptionRecord and ContextRecord onto the stack. These structures identify an exception and processor state before it. The exception manager adds also its own entry to the exception list. Exception handler is in fact a typical callback. It is not however installed by any API function, but appended in E_L_ENTRY into the exception list. EXCEPTION_DISPOSITION __cdecl _except_handler ( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ); 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 Exception flags are: EXCEPTION_NONCONTINUABLE = 1 EXCEPTION_UNWINDING = 2 EXCEPTION_UNWINDING_FOR_EXIT = 4 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. While appending a new exception handler to the exception list, a common practice is to push new E_L_ENTRY onto the stack. This way unwinding the stack can be done simply by skipping the exception manager's entry and restoring the stack pointer. Here is an example of exception handling. ----Start-of-file------------------------------------------------------------- ideal p686n model flat, stdcall O equ struc EXCEPTION_RECORD ExceptionCode dd ? ExceptionFlags dd ? ExceptionRecord dd ? ExceptionAddress dd ? NumberParameters dd ? ExceptionInformation dd 15 dup (?) ends procdesc wsprintfA c :dword, :dword, :dword:? procdesc MessageBoxA :dword, :dword, :dword, :dword procdesc ExitProcess :dword 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 ; Cause Invalid Opcode exception ud2 ; Display exception code and quit _Continue: call wsprintfA, O szCode, O szFormat, [ExCode] call MessageBoxA, 0, O szCode, O szWindowTitle, 0 call ExitProcess, 0 endp proc ExceptionHandler c ExceptionRecord, EF, ContextRecord, DC ; Save exception code mov eax, [ExceptionRecord] mov ecx, [(EXCEPTION_RECORD eax).ExceptionCode] mov [ExCode], ecx ; Unwind the stack mov eax, [fs:0] ; Exception Manager's entry mov esp, [eax] ; Our entry pop [dword fs:0] ; Restore fs:0 add esp, 4 ; Skip ExHandler address jmp _Continue endp 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_CONTROL = (CONTEXT_i386 or 1) ; SS:ESP, CS:EIP, EFLAGS, EBP CONTEXT_INTEGER = (CONTEXT_i386 or 2) ; EAX, EBX,..., ESI, EDI CONTEXT_SEGMENTS = (CONTEXT_i386 or 4) ; DS, ES, FS, GS CONTEXT_FLOATING_POINT = (CONTEXT_i386 or 8) ; 387 state CONTEXT_DEBUG_REGISTERS = (CONTEXT_i386 or 16); DB 0-3,6,7 CONTEXT_EXTENDED_REGISTERS = (CONTEXT_i386 or 32); cpu specific extensions CONTEXT_FULL = (CONTEXT_CONTROL or CONTEXT_INTEGER or\ CONTEXT_SEGMENTS) Context structure: ------------------ struc CONTEXT ContextFlags dd ? ; CONTEXT_??? flags Dr0 dd ? ; Debug registers Dr1 dd ? Dr2 dd ? Dr3 dd ? Dr6 dd ? Dr7 dd ? ControlWord dd ? ; FPU context StatusWord dd ? TagWord dd ? ErrorOffset dd ? ErrorSelector dd ? DataOffset dd ? DataSelector dd ? RegisterArea dt 8 dup (?) Cr0NpxState dd ? SegGs dd ? ; Segment registers SegFs dd ? SegEs dd ? SegDs dd ? Edi dd ? ; Integer registers Esi dd ? Ebx dd ? Edx dd ? Ecx dd ? Eax dd ? Ebp dd ? ; Control registers Eip dd ? SegCs dd ? EFlags dd ? Esp dd ? SegSs dd ? ExtendedRegisters db 512 dup (?) ends 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. In order to use a child window control, you must create it with CreateWindow or CreateWindowEx. Note that you don't have to register the window class since it's registered for you by Windows. The class name parameter MUST be the predefined class name. Say, if you want to create a button, you must specify "button" as the class name in CreateWindowEx. The other parameters you must fill in are the parent window handle and the control ID. The control ID must be unique among the controls. The control ID is the ID of that control. You use it to differentiate between the controls. 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: 1. Say Hello -- Put a text string into the edit box 2. Clear Edit Box -- Clear the content of the edit box 3. Get Text -- Display a message box with the text in the edit box 4. Exit -- Close the program. .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 "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: Let's analyze the program. .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 We create the controls during processing of WM_CREATE message. We call CreateWindowEx with an extra window style, WS_EX_CLIENTEDGE, which makes the client area look sunken. The name of each control is a predefined one, "edit" for edit control, "button" for button control. Next we specify the child window's styles. Each control has extra styles in addition to the normal window styles. For example, the button styles are prefixed with "BS_" for "button style", edit styles are prefixed with "ES_" for "edit style". You have to look these styles up in a Win32 API reference. Note that you put a control ID in place of the menu handle. This doesn't cause any harm since a child window control cannot have a menu. 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 Low word of wParam High word of wParam lParam Menu Menu ID 0 0 Control Control ID Notification code Child Window Handle 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. A modeless dialog box is created by calling CreateDialogParam API function. A modal dialog box is created by calling DialogBoxParam. The only distinction between an application modal dialog box and a system modal one is the DS_SYSMODAL style. If you include DS_SYSMODAL style in a dialog box template, that dialog box will be a system modal one. You can communicate with any child window control on a dialog box by using SendDlgItemMessage function. Its syntax is like this: SendDlgItemMessage proto hwndDlg:DWORD,\ idControl:DWORD,\ uMsg:DWORD,\ wParam:DWORD,\ lParam:DWORD 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: call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer 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: DlgProc proto hDlg:DWORD ,\ iMsg:DWORD ,\ wParam:DWORD ,\ lParam:DWORD 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. "Using a dialog box as main window" can be interpreted in two different senses. 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 WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 .IF ax==IDM_GETTEXT invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK .ELSEIF ax==IDM_CLEAR invoke SetDlgItemText,hWnd,IDC_EDIT,NULL .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE mov edx,wParam shr edx,16 .IF dx==BN_CLICKED .IF ax==IDC_BUTTON invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString .ELSEIF ax==IDC_EXIT invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .ENDIF .ENDIF .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start ------------------------------------------------------------------------ Dialog.rc ------------------------------------------------------------------------ #include "resource.h" #define IDC_EDIT 3000 #define IDC_BUTTON 3001 #define IDC_EXIT 3002 #define IDM_GETTEXT 32000 #define IDM_CLEAR 32001 #define IDM_EXIT 32003 MyDialog DIALOG 10, 10, 205, 60 STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK CAPTION "Our First Dialog Box" CLASS "DLGCLASS" 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, WS_GROUP END 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 -------- Let's analyze this first example. This example shows how to register a dialog template as a window class and create a "window" from that class. It simplifies your program since you don't have to create the child window controls yourself. Let's first analyze the dialog template. MyDialog DIALOG 10, 10, 205, 60 Declare the name of a dialog, in this case, "MyDialog" followed by the keyword "DIALOG". The following four numbers are: x, y , width, and height of the dialog box in dialog box units (not the same as pixels). STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK Declare the styles of the dialog box. CAPTION "Our First Dialog Box" 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: control-type "text" ,controlID, x, y, width, height [,styles] control-types are resource compiler's constants so you have to consult the manual. Now we go to the assembly source code. The interesting part is in the window class structure: 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. invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL 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. invoke IsDialogMessage, hDlg, ADDR msg .IF eax ==FALSE invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDIF 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 DlgProc 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 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 DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_INITDIALOG invoke GetDlgItem, hWnd,IDC_EDIT invoke SetFocus,eax .ELSEIF uMsg==WM_CLOSE invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 .IF ax==IDM_GETTEXT invoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK .ELSEIF ax==IDM_CLEAR invoke SetDlgItemText,hWnd,IDC_EDIT,NULL .ELSEIF ax==IDM_EXIT invoke EndDialog, hWnd,NULL .ENDIF .ELSE mov edx,wParam shr edx,16 .if dx==BN_CLICKED .IF ax==IDC_BUTTON invoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString .ELSEIF ax==IDC_EXIT invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0 .ENDIF .ENDIF .ENDIF .ELSE mov eax,FALSE ret .ENDIF mov eax,TRUE ret DlgProc endp end start ------------------------------------------------------------------------ dialog.rc (part 2) ------------------------------------------------------------------------ #include "resource.h" #define IDC_EDIT 3000 #define IDC_BUTTON 3001 #define IDC_EXIT 3002 #define IDR_MENU1 3003 #define IDM_GETTEXT 32000 #define IDM_CLEAR 32001 #define IDM_EXIT 32003 MyDialog DIALOG 10, 10, 205, 60 STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK CAPTION "Our Second Dialog Box" MENU IDR_MENU1 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 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 analysis follows: DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD We declare the function prototype for DlgProc so we can refer to it with addr operator in the line below: invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL 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. The processing of WM_COMMAND messages remains the same. When you want to destroy the dialog box, the only way is to call EndDialog function. Do not try DestroyWindow! EndDialog doesn't destroy the dialog box immediately. It only sets a flag for the internal dialog box manager and continues to execute the next instructions. 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. The number of parameters sent to hook procedures and emumeration callbacks varies - see the Window SDK. 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 ........ To avoid these long chains, assembler programmers have developed various techniques. You will have seen many of these in sample code around Win32 assembler web sites and in the asm journal, using conditional jumps, macros or table scans. I do not wish to compare these various methods, merely to put forward my own current favourite, which I believe has these advantages:- 1. It works on all assemblers 2. It is modular, ie. the code for each window can be concentrated in a particular part of your source code 3. It is easy to follow from the source code what message causes what result 4. The same function can easily be called from within different window procedures My method results in a very simple Window Procedure as follows (A386 format):- WndProc: MOV EDX,OFFSET MAINMESSAGES CALL GENERAL_WNDPROC RET 10h where the messages and functions (specific to this particular window procedure) are set out in a table such as this:- ;---------------------------------------------------------- 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 and where GENERAL_WINDPROC is as follows:- 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. jorgon@compuserve.com 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 ;-) # fire.s : fire.asm of apj 4 ported to Linux/SVGAlib ========================== # gcc -o fire fire.s -lvga .globl main .type main,@function main: pushl %ebp movl %esp,%ebp call vga_init # Init vga pushl $5 call vga_setmode # set mode to 5 = 320x200x256 addl $4,%esp pushl $0 call vga_setpage # Point to page 0 (There is only 1 page) addl $4,%esp pushl $0x3c8 # Get IOpermission, starting from 3c8h pushl $2 # to 3c9h pushl $1 # Turn On value call ioperm addl $12,%esp pushl $0x60 # Get IOpermission, for 60h : keyboard pushl $1 Pushl $1 call ioperm addl $12,%esp inb $0x60,%al # Read current value of keyboard movb %al,key 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 # addb (%ebx,%esi),%al # al,[si] ; pick color and addb 320(%ebx,%esi),%al # add al,[si+320] ; pick one more and shrb $2,%al # shr al,2 movb %al,-960(%ebx,%esi) # mov [si-960],al ; put color loop Sloop popl %edi # pop di popl %ecx # pop cx 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 ;Summary: Calculates absolute value of a signed integer in eax. ;Compatibility: 386+ ;Notes: 9 bytes, 4 clocks (P5), destroys ecx mov ecx, eax ; Duplicate value shr ecx, 31 ; Fill ecx with its sign xor eax, ecx ; Do 'not eax' if negative sub eax, ecx ; Do 'inc eax' if negative ; 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: L0: B330 MOV BL,30 ; try with al = ZERO D1E0 SHL AX,1 ; ... but if bit = set, ... 80D300 ADC BL,00 ; ... make it a ONE, 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