052 Iczelions_Win32_Assembly_Tutorials.pdf

273
1 ICZELIONS Win32 Assembly Language Tutorials First Edition Copyright Notice : All tutorials written by Iczelion are freeware: they are free so long as you use it for non-profitable purposes. Commercial use is strictly prohibited. This is a compilation of the legendary tutorials on Win32 Assembly Language by Iczelion - the most widely read and recomended tutorials by hundreds of web sites around - and referred to by almost every author in his book on the subject. Here, all the tutorials have been converted to searchable PDF format in a sequance, primarily for the ease of printing. The contents of these tutorials are absolutely as- original. The original files are present at author's website in html format. During reading, you will have to download the example files for the tutorials - so I have added links to those files whereever used. Links to author's websites are: http://win32assembly.online.fr/tutorials.html http://win32asm.cjb.net You can contact Iczelion at: [email protected] These tutorials are up-to-date as on April 01, 2007. I will try to update this version if the author adds something new in his tutorials. If you have suggestions regarding improving this document, you can contact me at: [email protected] Mian.

description

Iczelion - ovi tutorijalizakonassembly programming

Transcript of 052 Iczelions_Win32_Assembly_Tutorials.pdf

Page 1: 052 Iczelions_Win32_Assembly_Tutorials.pdf

1

ICZELION�SWin32 Assembly Language Tutorials

First Edition Copyright Notice : All tutorials written by Iczelion are freeware: they are free so long as you use it for non-profitable purposes. Commercial use is strictly prohibited.

This is a compilation of the legendary tutorials on Win32 Assembly Language by Iczelion - the most widely read and recomended tutorials by hundreds of web sites around - and referred to by almost every author in his book on the subject.

Here, all the tutorials have been converted to searchable PDF format in a sequance, primarily for the ease of printing. The contents of these tutorials are absolutely as-original. The original files are present at author's website in html format. During reading, you will have to download the example files for the tutorials - so I have added linksto those files whereever used. Links to author's websites are:

http://win32assembly.online.fr/tutorials.htmlhttp://win32asm.cjb.net

You can contact Iczelion at: [email protected]

These tutorials are up-to-date as on April 01, 2007. I will try to update this version if the author adds something new in his tutorials. If you have suggestions regarding improving this document, you can contact me at:[email protected]

Mian.

Page 2: 052 Iczelions_Win32_Assembly_Tutorials.pdf

2

Tutorial 1: The Basics

This tutorial assumes that the reader knows how to use MASM. If you're not familiar with MASM, download win32asm.exe and study the text inside the package before going on with the tutorial. Good. You're now ready. Let's go!

Theory:

Win32 programs run in protected mode which is available since 80286. But 80286 is now history. So we only have to concern ourselves with 80386 and its descendants. Windows runs each Win32 program in separated virtual space. That means each Win32 program will have its own 4 GB address space. However, this doesn't mean every win32 program has 4GB of physical memory, only that the program can address any address in that range. Windows will do anything necessary to make the memory the program references valid. Of course, the program must adhere to the rules set by Windows, else it will cause the dreaded General Protection Fault. Each program is alone in its address space. This is in contrast to the situation in Win16. All Win16 programs can *see* each other. Not so under Win32. This feature helps reduce the chance of one program writing over other program's code/data.Memory model is also drastically different from the old days of the 16-bit world. Under Win32, we need not be concerned with memory model or segments anymore! There's only one memory model: Flat memory model. There's no more 64K segments. The memory is a large continuous space of 4 GB. That also means you don't have to play with segment registers. You can use any segment register to address any point in the memory space. That's a GREAT help to programmers. This is what makes Win32 assembly programming as easy as C.When you program under Win32, you must know some important rules. One such rule is that, Windows uses esi, edi, ebp and ebx internally and it doesn't expect the values in those registers to change. So remember this rule first: if you use any of those four registers in your callback function, don't ever forget to restore them before returning control to Windows. A callback function is your own function which is called by Windows. The obvious example is the windows procedure. This doesn't mean that you cannot use those four registers, you can. Just be sure to restore them back before passing control back to Windows.

Content:

Here's the skeleton program. If you don't understand some of the codes, don't panic. I'll explain each of them later.

.386

.MODEL Flat, STDCALL

.DATA <Your initialized data> ......

.DATA? <Your uninitialized data> ......

.CONST <Your constants> ......

.CODE <label> <Your code> ..... end <label>

Page 3: 052 Iczelions_Win32_Assembly_Tutorials.pdf

3

That's all! Let's analyze this skeleton program.

.386This is an assembler directive, telling the assembler to use 80386 instruction set. You can also use .486, .586 but the safest bet is to stick to .386. There are actually two nearly identical forms for each CPU model. .386/.386p, .486/.486p. Those "p" versions are necessary only when your program uses privileged instructions. Privileged instructions are the instructions reserved by the CPU/operating system when in protected mode. They can only be used by privileged code, such as the virtual device drivers. Most of the time, your program will work in non-privileged mode so it's safe to use non-p versions.

.MODEL FLAT, STDCALL

.MODEL is an assembler directive that specifies memory model of your program. Under Win32, there's only on model, FLAT model.STDCALL tells MASM about parameter passing convention. Parameter passing convention specifies the order of parameter passing, left-to-right or right-to-left, and also who will balance the stack frame after the function call.Under Win16, there are two types of calling convention, C and PASCALC calling convention passes parameters from right to left, that is , the rightmost parameter is pushed first. The caller is responsible for balancing the stack frame after the call. For example, in order to call a function named foo(int first_param, int second_param, int third_param) in C calling convention the asm codes will look like this:

push [third_param] ; Push the third parameterpush [second_param] ; Followed by the secondpush [first_param] ; And the firstcall fooadd sp, 12 ; The caller balances the stack frame

PASCAL calling convention is the reverse of C calling convention. It passes parameters from left to right and the callee is responsible for the stack balancing after the call.Win16 adopts PASCAL convention because it produces smaller codes. C convention is useful when you don't know how many parameters will be passed to the function as in the case of wsprintf(). In the case of wsprintf(), the function has no way to determine beforehand how many parameters will be pushed on the stack, so it cannot do the stack balancing.STDCALL is the hybrid of C and PASCAL convention. It passes parameter from right to left but the callee is responsible for stack balancing after the call.Win32 platform use STDCALL exclusively. Except in one case: wsprintf(). You must use C calling convention with wsprintf().

.DATA

.DATA?

.CONST

.CODEAll four directives are what's called section. You don't have segments in Win32, remember? But you can divide your entire address space into logical sections. The start of one section denotes the end of the previous section. There'are two groups of section: data and code. Data sections are divided into 3 categories:

� .DATA This section contains initialized data of your program.� .DATA? This section contains uninitialized data of your program. Sometimes you

just want to preallocate some memory but don't want to initialize it. This section is

Page 4: 052 Iczelions_Win32_Assembly_Tutorials.pdf

4

for that purpose. The advantage of uninitialized data is: it doesn't take space in the executable file. For example, if you allocate 10,000 bytes in your .DATA? section, your executable is not bloated up 10,000 bytes. Its size stays much the same. You only tell the assembler how much space you need when the program is loaded into memory, that's all.

� .CONST This section contains declaration of constants used by your program. Constants in this section can never be modified in your program. They are just *constant*.

You don't have to use all three sections in your program. Declare only the section(s) you want to use.

There's only one section for code: .CODE. This is where your codes reside.<label>end <label>where <label> is any arbitrary label is used to specify the extent of your code. Both labels must be identical. All your codes must reside between <label> and end <label>

Page 5: 052 Iczelions_Win32_Assembly_Tutorials.pdf

5

Tutorial 2: MessageBoxIn this tutorial, we will create a fully functional Windows program that displays a message box saying "Win32 assembly is great!".

Download the example file here.

Theory:

Windows prepares a wealth of resources for Windows programs. Central to this is the Windows API (Application Programming Interface). Windows API is a huge collection of very useful functions that reside in Windows itself, ready for use by any Windows programs. These functions are stored in several dynamic-linked libraries (DLLs) such as kernel32.dll, user32.dll and gdi32.dll. Kernel32.dll contains API functions that deal with memory and process management. User32.dll controls the user interface aspects of your program. Gdi32.dll is responsible for graphics operations. Other than "the main three", there are other DLLs that your program can use, provided you have enough information about the desired API functions.Windows programs dynamically link to these DLLs, ie. the codes of API functions are not included in the Windows program executable file. In order for your program to know where to find the desired API functions at runtime, you have to embed that information into the executable file. The information is in import libraries. You must link your programs with the correct import libraries or they will not be able to locate API functions.When a Windows program is loaded into memory, Windows reads the information stored in the program. That information includes the names of functions the program uses and the DLLs those functions reside in. When Windows finds such info in the program, it'll load the DLLs and perform function address fixups in the program so the calls will transfer control to the right function.There are two categoriesof API functions: One for ANSI and the other for Unicode. The names of API functions for ANSI are postfixed with "A", eg. MessageBoxA. Those for Unicode are postfixed with "W" (for Wide Char, I think). Windows 95 natively supports ANSI and Windows NT Unicode.We are usually familiar with ANSI strings, which are arrays of characters terminated by NULL. ANSI character is 1 byte in size. While ANSI code is sufficient for European languages, it cannot handle several oriental languages which have several thousands of unique characters. That's why UNICODE comes in. A UNICODE character is 2 bytes in size, making it possible to have 65536 unique characters in the strings.But most of the time, you will use an include file which can determine and select the appropriate API functions for your platform. Just refer to API function names without the postfix.

Example:

I'll present the bare program skeleton below. We will flesh it out later.

.386

.model flat, stdcall

.data

.codestart:end start

The execution starts from the first instruction immediately below the label specified after end directive. In the above skeleton, the execution will start at the first instruction immediately below start label. The execution will proceed instruction by instruction until some flow-control instructions such as jmp, jne, je, ret etc is found. Those instructions redirect the flow of execution to some other instructions. When the program needs to exit to Windows, it should call an API function, ExitProcess.

ExitProcess proto uExitCode:DWORD

The above line is called a function prototype. A function prototype defines the attributes of a function to the assembler/linker so it can do type-checking for you. The format of a function prototype is like this:

Page 6: 052 Iczelions_Win32_Assembly_Tutorials.pdf

6

FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...

In short, the name of the function followed by the keyword PROTO and then by the list of data types of the parameters,separated by commas. In the ExitProcess example above, it defines ExitProcess as a function which takes only one parameter of type DWORD. Functions prototypes are very useful when you use the high-level call syntax, invoke. You can think of invoke as a simple call with type-checking. For example, if you do:

call ExitProcess

without pushing a dword onto the stack, the assembler/linker will not be able to catch that error for you. You'll notice it later when your program crashes. But if you use:

invoke ExitProcess

The linker will inform you that you forgot to push a dword on the stack thus avoiding error. I recommend you use invoke instead of simple call. The syntax of invoke is as follows:

INVOKE expression [,arguments]

expression can be the name of a function or it can be a function pointer. The function parameters are separated by commas.

Most of function prototypes for API functions are kept in include files. If you use hutch's MASM32, they will be in MASM32/include folder. The include files have .inc extension and the function prototypes for functions in a DLL is stored in .inc file with the same name as the DLL. For example, ExitProcess is exported by kernel32.lib so the function prototype for ExitProcess is stored in kernel32.inc.You can also create function prototypes for your own functions.Throughout my examples, I'll use hutch's windows.inc which you can download from http://win32asm.cjb.net

Now back to ExitProcess, uExitCode parameter is the value you want the program to return to Windows after the program terminates. You can call ExitProcess like this:

invoke ExitProcess, 0

Put that line immediately below start label, you will get a win32 program which immediately exits to Windows, but it's a valid program nonetheless.

.386

.model flat, stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\kernel32.lib.data.codestart:

invoke ExitProcess,0end start

option casemap:none tells MASM to make labels case-sensitive so ExitProcess and exitprocess are different. Note a new directive, include. This directive is followed by the name of a file you want to insert at the place the directive is. In the above example, when MASM processes the line include \masm32\include\windows.inc, it will open windows.inc which is in \MASM32\include folder and process the content of windows.inc as if you paste the content of windows.inc there. hutch's windows.inc contains definitions of constants and structures you need in win32 programming. It doesn't contain any function prototype. windows.inc is by no means comprehensive. hutch and I try to put as many constants and structures into it as possible but there are still many left to be included. It'll be

Page 7: 052 Iczelions_Win32_Assembly_Tutorials.pdf

7

constantly updated. Check out hutch's and my homepage for updates.From windows.inc, your program got constant and structure definitions. Now for function prototypes, you need to include other include files. They are all stored in \masm32\include folder.

In our example above, we call a function exported by kernel32.dll, so we need to include the function prototypes from kernel32.dll. That file is kernel32.inc. If you open it with a text editor, you will see that it's full of function prototypes for kernel32.dll. If you don't include kernel32.inc, you can still call ExitProcess but only with simple call syntax. You won't be able to invoke the function. The point here is that: in order to invoke a function, you have to put its function prototype somewhere in the source code. In the above example, if you don't include kernel32.inc, you can define the function prototype for ExitProcess anywhere in the source code above the invoke command and it will work. The include files are there to save you the work of typing out the prototypes yourself so use them whenever you can.Now we encounter a new directive, includelib. includelib doesn't work like include. It 's only a way to tell the assembler what import library your program uses. When the assembler sees an includelib directive, it puts a linker command into the object file so that the linker knows what import libraries your program needs to link with. You're not forced to use includelib though. You can specify the names of the import libraries in the command line of the linker but believe me, it's tedious and the command line can hold only 128 characters.

Now save the example under the name msgbox.asm. Assuming that ml.exe is in your path, assemble msgbox.asm with:

ml /c /coff /Cp msgbox.asm

� /c tells MASM to assemble only. Do not invoke link.exe. Most of the time, you would not want to call link.exe automatically since you may have to perform some other tasks prior to calling link.exe./coff tells MASM to create .obj file in COFF format. MASM uses a variation of COFF (Common Object File Format) which is used under Unix as its own object and executable file format./Cp tells MASM to preserve case of user identifiers. If you use hutch's MASM32 package, you may put "option casemap:none" at the head of your source code, just below .model directive to achieve the same effect.

After you successfully assemble msgbox.asm, you will get msgbox.obj. msgbox.obj is an object file. An object file is only one step away from an executable file. It contains the instructions/data in binary form. What is lacking is some fixups of addresses by the linker.

Then go on with link:

link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj/SUBSYSTEM:WINDOWS informs Link what sort of executable this program is/LIBPATH:<path to import library> tells Link where the import libraries are. If you use MASM32, they will be in MASM32\lib folder.Link reads in the object file and fixes it with addresses from the import libraries. When the process is finished you get msgbox.exe.

Now you get msgbox.exe. Go on, run it. You'll find that it does nothing. Well, we haven't put anything interesting into it yet. But it's a Windows program nonetheless. And look at its size! In my PC, it is 1,536 bytes.

Next we're going to put in a message box. Its function prototype is:

MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD

hwnd is the handle to parent window. You can think of a handle as a number that represents the window you're referrring to. Its value is not important to you. You only remember that it represents the window. When you want to do anything with the window, you must refer to it by its handle.lpText is a pointer to the text you want to display in the client area of the message box. A pointer is really an address of something. A pointer to text string==The address of that string.

Page 8: 052 Iczelions_Win32_Assembly_Tutorials.pdf

8

lpCaption is a pointer to the caption of the message boxuType specifies the icon and the number and type of buttons on the message boxLet's modify msgbox.asm to include the message box.

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\kernel32.libinclude \masm32\include\user32.incincludelib \masm32\lib\user32.lib

.dataMsgBoxCaption db "Iczelion Tutorial No.2",0MsgBoxText db "Win32 Assembly is Great!",0

.codestart:invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OKinvoke ExitProcess, NULLend start

Assemble and run it. You will see a message box displaying the text "Win32 Assembly is Great!".

Let's look again at the source code.We define two zero-terminated strings in .data section. Remember that every ANSI string in Windows must be terminated by NULL (0 hexadecimal).We use two constants, NULL and MB_OK. Those constants are documented in windows.inc. So you can refer to them by name instead of the values. This improves readability of your source code.The addr operator is used to pass the address of a label to the function. It's valid only in the context of invoke directive. You can't use it to assign the address of a label to a register/variable, for example. You can use offset instead of addr in the above example. However, there are some differences between the two:

1. addr cannot handle forward reference while offset can. For example, if the label is defined somewhere further in the source code than the invoke line, addr will not work.

invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK......MsgBoxCaption db "Iczelion Tutorial No.2",0MsgBoxText db "Win32 Assembly is Great!",0

MASM will report error. If you use offset instead of addr in the above code snippet, MASM will assemble it happily.

2. addr can handle local variables while offset cannot. A local variable is only some reserved space in the stack. You will only know its address during runtime. offset is interpreted during assembly time by the assembler. So it's natural that offset won't work for local variables. addr is able to handle local variables because of the fact that the assembler checks first whether the variable referred to by addr is a global or local one. If it's a global variable, it puts the address of that variable into the object file. In this regard, it works like offset. If it's a local variable, it generates an instruction sequence like this before it actually calls the function:

lea eax, LocalVar push eax

Page 9: 052 Iczelions_Win32_Assembly_Tutorials.pdf

9

Since lea can determine the address of a label at runtime, this works fine.

Page 10: 052 Iczelions_Win32_Assembly_Tutorials.pdf

10

Tutorial 3: A Simple WindowIn this tutorial, we will build a Windows program that displays a fully functional window on the desktop.Download the example file here

Theory:

Windows programs rely heavily on API functions for their GUI. This approach benefits both users and programmers. For users, they don't have to learn how to navigate the GUI of each new programs, the GUI of Windows programs are alike. Forprogrammers, the GUI codes are already there,tested, and ready for use. The downside for programmers is the increased complexity involved. In order to create or manipulate any GUI objects such as windows, menu or icons, programmers must follow a strict recipe. But that can be overcome by modular programming or OOP paradigm.I'll outline the steps required to create a window on the desktop below:

1. Get the instance handle of your program (required)2. Get the command line (not required unless your program wants to process a command line)3. Register window class (required ,unless you use predefined window types, eg. MessageBox or a dialog box)4. Create the window (required)5. Show the window on the desktop (required unless you don't want to show the window immediately)6. Refresh the client area of the window7. Enter an infinite loop, checking for messages from Windows8. If messages arrive, they are processed by a specialized function that is responsible for the window9. Quit program if the user closes the window

As you can see, the structure of a Windows program is rather complex compared to a DOS program. But the world of Windows is drastically different from the world of DOS. Windows programs must be able to coexist peacefully with each other. They must follow stricter rules. You, as a programmer, must also be more strict with your programming style and habit.

Content:

Below is the source code of our simple window program. Before jumping into the gory details of Win32 ASM programming, I'll point out some fine points which will ease your programming.

� You should put all Windows constants, structures and function prototypes in an include file and include it at the beginning of your .asm file. It'll save you a lot of effort and typo. Currently, the most complete include file for MASM is hutch's windows.inc which you can download from his page or my page. You can also define your own constants & structure definitions but you should put them into a separate include file.

� Use includelib directive to specify the import library used in your program. For example, if your program calls MessageBox, you should put the line:

includelib user32.lib

at the beginning of your .asm file. This directive tells MASM that your program will make uses of functions in that import library. If your program calls functions in more than one library, just add an includelib for each library you use. Using IncludeLib directive, you don't have to worry about import libraries at link time. You can use /LIBPATH linker switch to tell Link where all the libs are.

� When declaring API function prototypes, structures, or constants in your include file, try to stick to the original names used in Windows include files, including case. This will save you a lot of headache when looking up some item in Win32 API reference.

� Use makefile to automate your assembling process. This will save you a lot of typing.

.386

.model flat,stdcall

Page 11: 052 Iczelions_Win32_Assembly_Tutorials.pdf

11

option casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incincludelib \masm32\lib\user32.lib ; calls to functions in user32.lib and kernel32.libinclude \masm32\include\kernel32.incincludelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA ; initialized dataClassName db "SimpleWinClass",0 ; the name of our window classAppName db "Our First Window",0 ; the name of our window

.DATA? ; Uninitialized datahInstance HINSTANCE ? ; Instance handle of our programCommandLine LPSTR ?.CODE ; Here begins our codestart:invoke GetModuleHandle, NULL ; get the instance handle of our program.

; Under Win32, hmodule==hinstance mov hInstance,eaxmov hInstance,eaxinvoke GetCommandLine ; get the command line. You don't have to call this function IF

; your program doesn't process the command line.mov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; call the main functioninvoke ExitProcess, eax ; quit our program. The exit code is returned in eax from WinMain.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEX ; create local variables on stackLOCAL msg:MSGLOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX ; fill values in members of wcmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstancepop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wc ; register our window classinvoke CreateWindowEx,NULL,\

ADDR ClassName,\ADDR AppName,\WS_OVERLAPPEDWINDOW,\CW_USEDEFAULT,\CW_USEDEFAULT,\CW_USEDEFAULT,\CW_USEDEFAULT,\NULL,\NULL,\hInst,\

Page 12: 052 Iczelions_Win32_Assembly_Tutorials.pdf

12

NULLmov hwnd,eaxinvoke ShowWindow, hwnd,CmdShow ; display our window on desktopinvoke UpdateWindow, hwnd ; refresh the client area

.WHILE TRUE ; Enter message loopinvoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParam ; return exit code in eaxret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.IF uMsg==WM_DESTROY ; if the user closes our window

invoke PostQuitMessage,NULL ; quit our application.ELSE

invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Default message processingret

.ENDIFxor eax,eaxret

WndProc endp

end start

Analysis:

You may be taken aback that a simple Windows program requires so much coding. But most of those codes are just *template* codes that you can copy from one source code file to another. Or if you prefer, you could assemble some of these codes into a library to be used as prologue and epilogue codes. You can write only the codes in WinMain function. In fact, this is what C compilers do. They let you write WinMain codes without worrying about other housekeeping chores. The only catch is that you must have a function named WinMain else C compilers will not be able to combine your codes with the prologue and epilogue. You do not have such restriction with assembly language. You can use any function name instead of WinMain or no function at all.Prepare yourself. This's going to be a long, long tutorial. Let's analyze this program to death!

.386

.model flat,stdcalloption casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

The first three lines are "necessities". .386 tells MASM we intend to use 80386 instruction set in this program. .model flat,stdcall tells MASM that our program uses flat memory addressing model. Also we will use stdcall parameter passing convention as the default one in our program.Next is the function prototype for WinMain. Since we will call WinMain later, we must define its function prototype first so that we will be able to invoke it.We must include windows.inc at the beginning of the source code. It contains important structures and constants that are used by our program. The include file , windows.inc, is just a text file. You can open it with any text editor. Please note that windows.inc does not contain all structures, and constants (yet). hutch and I are working on it. You can add in new items if they

Page 13: 052 Iczelions_Win32_Assembly_Tutorials.pdf

13

are not in the file.Our program calls API functions that reside in user32.dll (CreateWindowEx, RegisterWindowClassEx, for example) and kernel32.dll (ExitProcess), so we must link our program to those two import libraries. The next question : how can I know which import library should be linked to my program? The answer: You must know where the API functions called by your program reside. For example, if you call an API function in gdi32.dll, you must link with gdi32.lib.This is the approach of MASM. TASM 's way of import library linking is much more simpler: just link to one and only one file: import32.lib.

.DATAClassName db "SimpleWinClass",0AppName db "Our First Window",0

.DATA?hInstance HINSTANCE ?CommandLine LPSTR ?

Next are the "DATA" sections.In .DATA, we declare two zero-terminated strings(ASCIIZ strings): ClassName which is the name of our window class and AppName which is the name of our window. Note that the two variables are initialized.In .DATA?, two variables are declared: hInstance (instance handle of our program) and CommandLine (command line of our program). The unfamiliar data types, HINSTANCE and LPSTR, are really new names for DWORD. You can look them up in windows.inc. Note that all variables in .DATA? section are not initialized, that is, they don't have to hold any specific value on startup, but we want to reserve the space for future use.

.CODEstart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax.....

end start.CODE contains all your instructions. Your codes must reside between <starting label>: and end <starting label>. The name of the label is unimportant. You can name it anything you like so long as it is unique and doesn't violate the naming convention of MASM.Our first instruction is the call to GetModuleHandle to retrieve the instance handle of our program. Under Win32, instance handle and module handle are one and the same. You can think of instance handle as the ID of your program. It is used as parameter to several API functions our program must call, so it's generally a good idea to retrieve it at the beginning of our program.Note: Actually under win32, instance handle is the linear address of your program in memory.Upon returning from a Win32 function, the function's return value, if any, can be found in eax. All other values are returned through variables passed in the function parameter list you defined for the call.A Win32 function that you call will nearly always preserve the segment registers and the ebx, edi, esi and ebp registers. Conversely, ecx and edx are considered scratch registers and are always undefined upon return from a Win32 function.Note: Don't expect the values of eax, ecx, edx to be preserved across API function calls.The bottom line is that: when calling an API function, expects return value in eax. If any of your function will be called by Windows, you must also play by the rule: preserve and restore the values of the segment registers, ebx, edi, esi and ebp upon function return else your program will crash very shortly, this includes your window procedure and windows callback functions.The GetCommandLine call is unnecessary if your program doesn't process a command line. In this example, I show you how to call it in case you need it in your program.Next is the WinMain call. Here it receives four parameters: the instance handle of our program, the instance handle of the previous instance of our program, the command line and window state at first appearance. Under Win32, there's NO previous instance. Each program is alone in its address space, so the value of hPrevInst is always 0. This is a leftover from the day of Win16 when all instances of a program run in the same address space and an instance wants to know if it's the first instance. Under win16, if hPrevInst is NULL, then this instance is the first one.Note: You don't have to declare the function name as WinMain. In fact, you have complete freedom in this regard. You don't have to use any WinMain-equivalent function at all. You can paste the codes inside WinMain function next to GetCommandLine and your program will still be able to function perfectly.

Page 14: 052 Iczelions_Win32_Assembly_Tutorials.pdf

14

Upon returning from WinMain, eax is filled with exit code. We pass that exit code as the parameter to ExitProcess which terminates our application.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

The above line is the function declaration of WinMain. Note the parameter:type pairs that follow PROC directive. They are parameters that WinMain receives from the caller. You can refer to these parameters by name instead of by stack manipulation. In addition, MASM will generate the prologue and epilogue codes for the function. So we don't have to concern ourselves with stack frame on function enter and exit.

LOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWND

LOCAL directive allocates memory from the stack for local variables used in the function. The bunch of LOCAL directives must be immediately below the PROC directive. The LOCAL directive is immediately followed by <the name of local variable>:<variable type>. So LOCAL wc:WNDCLASSEX tells MASM to allocate memory from the stack the size of WNDCLASSEX structure for the variable named wc. We can refer to wc in our codes without any difficulty involved in stack manipulation. That's really a godsend, I think. The downside is that local variables cannot be used outside the function they're created and will be automatically destroyed when the function returns to the caller. Another drawback is that you cannot initialize local variables automatically because they're just stack memory allocated dynamically when the function is entered . You have to manually assign them with desired values after LOCAL directives.

mov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstancepop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wc

The inimidating lines above are really simple in concept. It just takes several lines of instruction to accomplish. The concept behind all these lines is window class. A window class is nothing more than a blueprint or specification of a window. It defines several important characteristics of a window such as its icon, its cursor, the function responsible for it, its color etc. You create a window from a window class. This is some sort of object oriented concept. If you want to create more than one window with the same characteristics, it stands to reason to store all these characteristics in only one place and refer to them when needed. This scheme will save lots of memory by avoiding duplication of information. Remember, Windows is designed in the past when memory chips are prohibitive and most computers have 1 MB of memory. Windows must be very efficient in using the scarce memory resource. The point is: if you define your own window, you must fill the desired characteristics of your window in a WNDCLASS or WNDCLASSEX structure and call RegisterClass or RegisterClassEx before you're able to create your window. You only have to register the window class once for each window type you want to create a window from.Windows has several predefined Window classes, such as button and edit box. For these windows (or controls), you don't have to register a window class, just call CreateWindowEx with the predefined class name.The single most important member in the WNDCLASSEX is lpfnWndProc. lpfn stands for long pointer to function. Under Win32, there's no "near" or "far" pointer, just pointer because of the new FLAT memory model. But this is again a leftover from the day of Win16. Each window class must be associated with a function called window procedure. The window procedure is responsible for message handling of all windows created from the associated window class. Windows will send messages to the window procedure to notify it of important events concerning the windows it 's responsible for,such as user keyboard or mouse input. It's up to the window procedure to respond intelligently to each window message it receives. You will

Page 15: 052 Iczelions_Win32_Assembly_Tutorials.pdf

15

spend most of your time writing event handlers in window procedure.I describe each member of WNDCLASSEX below:

WNDCLASSEX STRUCT DWORDcbSize DWORD ?style DWORD ?lpfnWndProc DWORD ?cbClsExtra DWORD ?cbWndExtra DWORD ?hInstance DWORD ?hIcon DWORD ?hCursor DWORD ?hbrBackground DWORD ?lpszMenuName DWORD ?lpszClassName DWORD ?hIconSm DWORD ?

WNDCLASSEX ENDS

cbSize: The size of WNDCLASSEX structure in bytes. We can use SIZEOF operator to get the value.style: The style of windows created from this class. You can combine several styles together using "or" operator.lpfnWndProc: The address of the window procedure responsible for windows created from this class.cbClsExtra: Specifies the number of extra bytes to allocate following the window-class structure. The operating system initializes the bytes to zero. You can store window class-specific data here.cbWndExtra: Specifies the number of extra bytes to allocate following the window instance. The operating system initializes the bytes to zero. If an application uses the WNDCLASS structure to register a dialog box created by using the CLASS directive in the resource file, it must set this member to DLGWINDOWEXTRA.hInstance: Instance handle of the module.hIcon: Handle to the icon. Get it from LoadIcon call.hCursor: Handle to the cursor. Get it from LoadCursor call.hbrBackground: Background color of windows created from the class.lpszMenuName: Default menu handle for windows created from the class.lpszClassName: The name of this window class.hIconSm: Handle to a small icon that is associated with the window class. If this member is NULL, the system searches the icon resource specified by the hIcon member for an icon of the appropriate size to use as the small icon.

invoke CreateWindowEx, NULL,\ADDR ClassName,\ADDR AppName,\WS_OVERLAPPEDWINDOW,\CW_USEDEFAULT,\CW_USEDEFAULT,\CW_USEDEFAULT,\CW_USEDEFAULT,\NULL,\NULL,\hInst,\NULL

After registering the window class, we can call CreateWindowEx to create our window based on the submitted window class. Notice that there are 12 parameters to this function.

CreateWindowExA proto dwExStyle:DWORD,\lpClassName:DWORD,\lpWindowName:DWORD,\dwStyle:DWORD,\X:DWORD,\Y:DWORD,\nWidth:DWORD,\nHeight:DWORD,\

Page 16: 052 Iczelions_Win32_Assembly_Tutorials.pdf

16

hWndParent:DWORD ,\hMenu:DWORD,\hInstance:DWORD,\lpParam:DWORD

Let's see detailed description of each parameter:dwExStyle: Extra window styles. This is the new parameter that is added to the old CreateWindow. You can put new window styles for Windows 95 & NT here.You can specify your ordinary window style in dwStyle but if you want some special styles such as topmost window, you must specify them here. You can use NULL if you don't want extra window styles.lpClassName: (Required). Address of the ASCIIZ string containing the name of window class you want to use as template for this window. The Class can be your own registered class or predefined window class. As stated above, every window you created must be based on a window class.lpWindowName: Address of the ASCIIZ string containing the name of the window. It'll be shown on the title bar of the window. If this parameter is NULL, the title bar of the window will be blank.dwStyle: Styles of the window. You can specify the appearance of the window here. Passing NULL is ok but the window will have no system menu box, no minimize-maximize buttons, and no close-window button. The window would not be of much use at all. You will need to press Alt+F4 to close it. The most common window style is WS_OVERLAPPEDWINDOW. A window style is only a bit flag. Thus you can combine several window styles by "or" operator to achieve the desired appearance of the window. WS_OVERLAPPEDWINDOW style is actually a combination of the most common window styles by this method.X,Y: The coordinate of the upper left corner of the window. Normally this values should be CW_USEDEFAULT, that is, you want Windows to decide for you where to put the window on the desktop.nWidth, nHeight: The width and height of the window in pixels. You can also use CW_USEDEFAULT to let Windows choose the appropriate width and height for you.hWndParent: A handle to the window's parent window (if exists). This parameter tells Windows whether this window is a child (subordinate) of some other window and, if it is, which window is the parent. Note that this is not the parent-child relationship of multiple document interface (MDI). Child windows are not bound to the client area of the parent window. This relationship is specifically for Windows internal use. If the parent window is destroyed, all child windows will be destroyed automatically. It's really that simple. Since in our example, there's only one window, we specify this parameter as NULL.hMenu: A handle to the window's menu. NULL if the class menu is to be used. Look back at the a member of WNDCLASSEX structure, lpszMenuName. lpszMenuName specifies *default* menu for the window class. Every window created from this window class will have the same menu by default. Unless you specify an *overriding* menu for a specific window via its hMenu parameter. hMenu is actually a dual-purpose parameter. In case the window you want to create is of a predefined window type (ie. control), such control cannot own a menu. hMenu is used as that control's ID instead. Windows can decide whether hMenu is really a menu handle or a control ID by looking at lpClassName parameter. If it's the name of a predefined window class, hMenu is a control ID. If it's not, then it's a handle to the window's menu.hInstance: The instance handle for the program module creating the window.lpParam: Optional pointer to a data structure passed to the window. This is used by MDI window to pass the CLIENTCREATESTRUCT data. Normally, this value is set to NULL, meaning that no data is passed via CreateWindow(). The window can retrieve the value of this parameter by the call to GetWindowLong function.

mov hwnd,eaxinvoke ShowWindow, hwnd,CmdShowinvoke UpdateWindow, hwnd

On successful return from CreateWindowEx, the window handle is returned in eax. We must keep this value for future use. The window we just created is not automatically displayed. You must call ShowWindow with the window handle and the desired *display state* of the window to make it display on the screen. Next you can call UpdateWindow to order your window to repaint its client area. This function is useful when you want to update the content of the client area. You can omit this call though.

.WHILE TRUEinvoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDW

Page 17: 052 Iczelions_Win32_Assembly_Tutorials.pdf

17

At this time, our window is up on the screen. But it cannot receive input from the world. So we have to *inform* it of relevant events. We accomplish this with a message loop. There's only one message loop for each module. This message loop continually checks for messages from Windows with GetMessage call. GetMessage passes a pointer to a MSG structure to Windows. This MSG structure will be filled with information about the message that Windows want to send to a window in the module. GetMessage function will not return until there's a message for a window in the module. During that time, Windows can give control to other programs. This is what forms the cooperative multitasking scheme of Win16 platform. GetMessage returns FALSE if WM_QUIT message is received which, in the message loop, will terminate the loop and exit the program.TranslateMessage is a utility function that takes raw keyboard input and generates a new message (WM_CHAR) that is placed on the message queue. The message with WM_CHAR contains the ASCII value for the key pressed, which is easier to deal with than the raw keyboard scan codes. You can omit this call if your program doesn't process keystrokes.DispatchMessage sends the message data to the window procedure responsible for the specific window the message is for.

mov eax,msg.wParamret

WinMain endp

If the message loop terminates, the exit code is stored in wParam member of the MSG structure. You can store this exit code into eax to return it to Windows. At the present time, Windows does not make use of the return value, but it's better to be on the safe side and plays by the rule.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

This is our window procedure. You don't have to name it WndProc. The first parameter, hWnd, is the window handle of the window that the message is destined for. uMsg is the message. Note that uMsg is not a MSG structure. It's just a number, really. Windows defines hundreds of messages, most of which your programs will not be interested in. Windows will send an appropriate message to a window in case something relevant to that window happens. The window procedure receives the message and reacts to it intelligently. wParam and lParam are just extra parameters for use by some messages. Some messages do send accompanying data in addition to the message itself. Those data are passed to the window procedure by means of lParam and wParam.

.IF uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endp

Here comes the crucial part. This is where most of your program's intelligence resides. The codes that respond to each Windows message are in the window procedure. Your code must check the Windows message to see if it's a message it's interested in. If it is, do anything you want to do in response to that message and then return with zero in eax. If it's not, you MUST call DefWindowProc, passing all parameters you received to it for default processing.. This DefWindowProc is an API function that processes the messages your program are not interested in.The only message that you MUST respond to is WM_DESTROY. This message is sent to your window procedure whenever your window is closed. By the time your window procedure receives this message, your window is already removed from the screen. This is just a notification that your window was destroyed, you should prepare yourself to return to Windows. In response to this, you can perform housekeeping prior to returning to Windows. You have no choice but to quit when it comes to this state. If you want to have a chance to stop the user from closing your window, you should process WM_CLOSE message. Now back to WM_DESTROY, after performing housekeeping chores, you must call PostQuitMessage which will post WM_QUIT back to your module. WM_QUIT will make GetMessage return with zero value in eax, which in turn, terminates the message loop and quits to Windows. You can send WM_DESTROY message to your own window procedure by calling DestroyWindow function.

Page 18: 052 Iczelions_Win32_Assembly_Tutorials.pdf

18

Tutorial 4: Painting with TextIn this tutorial, we will learn how to "paint" text in the client area of a window. We'll also learn about device context.You can download the source code here.

Theory:

Text in Windows is a type of GUI object. Each character is composed of numerous pixels (dots) that are lumped together into a distinct pattern. That's why it's called "painting" instead of "writing". Normally, you paint text in your own client area (actually, you can paint outside client area but that's another story). Putting text on screen in Windows is drastically different from DOS. In DOS, you can think of the screen in 80x25 dimension. But in Windows, the screen are shared by several programs. Some rules must be enforced to avoid programs writing over each other's screen. Windows ensures this by limiting painting area of each window to its own client area only. The size of client area of a window is also not constant. The user can change the size anytime. So you must determine the dimensions of your own client area dynamically.Before you can paint something on the client area, you must ask for permission from Windows. That's right, you don't have absolute control of the screen as you were in DOS anymore. You must ask Windows for permission to paint your own client area. Windows will determine the size of your client area, font, colors and other GDI attributes and sends a handle to device context back to your program. You can then use the device context as a passport to painting on your client area.What is a device context? It's just a data structure maintained internally by Windows. A device context is associated with a particular device, such as a printer or video display. For a video display, a device context is usually associated with a particular window on the display.Some of the values in the device context are graphic attributes such as colors, font etc. These are default values which you can change at will. They exist to help reduce the load from having to specify these attributes in every GDI function calls.You can think of a device context as a default environment prepared for you by Windows. You can override some default settings later if you so wish.When a program need to paint, it must obtain a handle to a device context. Normally, there are several ways to accomplish this.

call BeginPaint in response to WM_PAINT message.call GetDC in response to other messages.call CreateDC to create your own device context

One thing you must remember, after you're through with the device context handle, you must release it during the processing of a single message. Don't obtain the handle in response to one message and release it in response to another.Windows posts WM_PAINT messages to a window to notify that it's now time to repaint its client area. Windows does not save the content of client area of a window. Instead, when a situation occurs that warrants a repaint of client area (such as when a window was covered by another and is just uncovered), Windows puts WM_PAINT message in that window's message queue. It's the responsibility of that window to repaint its own client area. You must gather all information about how to repaint your client area in the WM_PAINT section of your window procedure, so the window procudure can repaint the client area when WM_PAINT message arrives.Another concept you must come to terms with is the invalid rectangle. Windows defines an invalid rectangle as the smallest rectangular area in the client area that needs to be repainted. When Windows detects an invalid rectangle in the client area of a window , it posts WM_PAINT message to that window. In response to WM_PAINT message, the window can obtain a paintstruct structure which contains, among others, the coordinate of the invalid rectangle. You call BeginPaint in response to WM_PAINT message to validate the invalid rectangle. If you don't process WM_PAINT message, at the very least you must call DefWindowProc or ValidateRect to validate the invalid rectangle else Windows will repeatedly send you WM_PAINT message.Below are the steps you should perform in response to a WM_PAINT message:

Get a handle to device context with BeginPaint.Paint the client area.Release the handle to device context with EndPaint

Note that you don't have to explicitly validate the invalid rectangle. It's automatically done by the BeginPaint call. Between BeginPaint-Endpaint pair, you can call any GDI functions to paint your client area. Nearly all of them require the handle to device context as a parameter.

Content:

Page 19: 052 Iczelions_Win32_Assembly_Tutorials.pdf

19

We will write a program that displays a text string "Win32 assembly is great and easy!" in the center of the client area.

.386

.model flat,stdcalloption casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incincludelib \masm32\lib\user32.libinclude \masm32\include\kernel32.incincludelib \masm32\lib\kernel32.lib

.DATAClassName db "SimpleWinClass",0AppName db "Our First Window",0OurText db "Win32 assembly is great and easy!",0

.DATA?hInstance HINSTANCE ?CommandLine LPSTR ?

.CODEstart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eax

Page 20: 052 Iczelions_Win32_Assembly_Tutorials.pdf

20

invoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd

.WHILE TRUEinvoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL hdc:HDCLOCAL ps:PAINTSTRUCTLOCAL rect:RECT.IF uMsg==WM_DESTROY

invoke PostQuitMessage,NULL.ELSEIF uMsg==WM_PAINT

invoke BeginPaint,hWnd, ADDR psmov hdc,eaxinvoke GetClientRect,hWnd, ADDR rectinvoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \

DT_SINGLELINE or DT_CENTER or DT_VCENTERinvoke EndPaint,hWnd, ADDR ps

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax, eaxret

WndProc endpend start

Analysis:

The majority of the code is the same as the example in tutorial 3. I'll explain only the important changes.

LOCAL hdc:HDCLOCAL ps:PAINTSTRUCTLOCAL rect:RECT

These are local variables that are used by GDI functions in our WM_PAINT section. hdc is used to store the handle to device context returned from BeginPaint call. ps is a PAINTSTRUCT structure. Normally you don't use the values in ps. It's passed to BeginPaint function and Windows fills it with appropriate values. You then pass ps to EndPaint function when you finish painting the client area. rect is a RECT structure defined as follows:

RECT Structleft LONG ?top LONG ?right LONG ?bottom LONG ?

RECT endsLeft and top are the coordinates of the upper left corner of a rectangle Right and bottom are the coordinates of the lower right corner. One thing to remember: The origin of the x-y axes is at the upper left corner of the client area. So the point y=10 is BELOW the point y=0.

Page 21: 052 Iczelions_Win32_Assembly_Tutorials.pdf

21

invoke BeginPaint,hWnd, ADDR psmov hdc,eaxinvoke GetClientRect,hWnd, ADDR rectinvoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \

DT_SINGLELINE or DT_CENTER or DT_VCENTERinvoke EndPaint,hWnd, ADDR ps

In response to WM_PAINT message, you call BeginPaint with handle to the window you want to paint and an uninitialized PAINTSTRUCT structure as parameters. After successful call, eax contains the handle to device context. Next you call GetClientRect to retrieve the dimension of the client area. The dimension is returned in rect variable which you pass to DrawText as one of its parameters. DrawText's syntax is:

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

DrawText is a high-level text output API function. It handles some gory details such as word wrap, centering etc. so you could concentrate on the string you want to paint. Its low-level brother, TextOut, will be examined in the next tutorial. DrawText formats a text string to fit within the bounds of a rectangle. It uses the currently selected font,color and background (in the device context) to draw the text.Lines are wrapped to fit within the bounds of the rectangle. It returns the height of the output text in device units, in our case, pixels. Let's see its parameters:

hdc handle to device contextlpString The pointer to the string you want to draw in the rectangle. The string must be null-terminated else you would have to specify its length in the next parameter, nCount.nCount The number of characters to output. If the string is null-terminated, nCount must be -1. Otherwise nCount must contain the number of characters in the string you want to draw.lpRect The pointer to the rectangle (a structure of type RECT) you want to draw the string in. Note that thisrectangle is also a clipping rectangle, that is, you could not draw the string outside this rectangle.uFormat The value that specifies how the string is displayed in the rectangle. We use three values combined by "or" operator:

o DT_SINGLELINE specifies a single line of texto DT_CENTER centers the text horizontally.o DT_VCENTER centers the text vertically. Must be used with DT_SINGLELINE.

After you finish painting the client area, you must call EndPaint function to release the handle to device context.That's it. We can summarize the salient points here:

� You call BeginPaint-EndPaint pair in response to WM_PAINT message.� Do anything you like with the client area between the calls to BeginPaint and EndPaint.� If you want to repaint your client area in response to other messages, you have two choices:

o Use GetDC-ReleaseDC pair and do your painting between these callso Call InvalidateRect or UpdateWindow to invalidate the entire client area, forcing Windows to put

WM_PAINT message in the message queue of your window and do your painting in WM_PAINT section

Page 22: 052 Iczelions_Win32_Assembly_Tutorials.pdf

22

Tutorial 5: More about TextWe will experiment more with text attributes, ie. font and color.

Download the example file here.

Theory:

Windows color system is based on RGB values, R=red, G=Green, B=Blue. If you want to specify a color in Windows, you must state your desired color in terms of these three major colors. Each color value has a range from 0 to 255 (a byte value). For example, if you want pure red color, you should use 255,0,0. Or if you want pure white color, you must use 255,255,255. You can see from the examples that getting the color you need is very difficult with this system since you have to have a good grasp of how to mix and match colors.For text color and background, you use SetTextColor and SetBkColor, both of them require a handle to device context and a 32-bit RGB value. The 32-bit RGB value's structure is defined as:

RGB_value structunused db 0blue db ?green db ?red db ?

RGB_value ends

Note that the first byte is not used and should be zero. The order of the remaining three bytes is reversed,ie. blue, green, red. However, we will not use this structure since it's cumbersome to initialize and use. We will create a macro instead. The macro will receive three parameters: red, green and blue values. It'll produce the desired 32-bit RGB value and store it in eax. The macro is as follows:

RGB macro red,green,bluexor eax,eaxmov ah,blueshl eax,8mov ah,greenmov al,red

endm

You can put this macro in the include file for future use.You can "create" a font by calling CreateFont or CreateFontIndirect. The difference between the two functions is that CreateFontIndirect receives only one parameter: a pointer to a logical font structure, LOGFONT. CreateFontIndirect is the more flexible of the two especially if your programs need to change fonts frequently. However, in our example, we will "create" only one font for demonstration, we can get away with CreateFont. After the call to CreateFont, it will return a handle to a font which you must select into the device context. After that, every text API function will use the font we have selected into the device context.

Content:

.386

.model flat,stdcalloption casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.inc

Page 23: 052 Iczelions_Win32_Assembly_Tutorials.pdf

23

include \masm32\include\gdi32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\gdi32.lib

RGB macro red,green,bluexor eax,eaxmov ah,blueshl eax,8mov ah,greenmov al,red

endm

.dataClassName db "SimpleWinClass",0AppName db "Our First Window",0TestString db "Win32 assembly is great and easy!",0FontName db "script",0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd

Page 24: 052 Iczelions_Win32_Assembly_Tutorials.pdf

24

.WHILE TRUEinvoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL hdc:HDCLOCAL ps:PAINTSTRUCTLOCAL hfont:HFONT

.IF uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.ELSEIF uMsg==WM_PAINTinvoke BeginPaint,hWnd, ADDR psmov hdc,eaxinvoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\

OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\ADDR FontName

invoke SelectObject, hdc, eaxmov hfont,eaxRGB 200,200,50invoke SetTextColor,hdc,eaxRGB 0,0,255invoke SetBkColor,hdc,eaxinvoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestStringinvoke SelectObject,hdc, hfontinvoke EndPaint,hWnd, ADDR ps

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endp

end start

Analysis:

invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\ADDR FontName

CreateFont creates a logical font that is the closest match to the given parameters and the font data available. This function has more parameters than any other function in Windows. It returns a handle to logical font to be used by SelectObject function. We will examine its parameters in detail.

CreateFont proto nHeight:DWORD,\nWidth:DWORD,\nEscapement:DWORD,\

Page 25: 052 Iczelions_Win32_Assembly_Tutorials.pdf

25

nOrientation:DWORD,\nWeight:DWORD,\cItalic:DWORD,\cUnderline:DWORD,\cStrikeOut:DWORD,\cCharSet:DWORD,\cOutputPrecision:DWORD,\cClipPrecision:DWORD,\cQuality:DWORD,\cPitchAndFamily:DWORD,\lpFacename:DWORD

nHeight The desired height of the characters . 0 means use default size.nWidth The desired width of the characters. Normally this value should be 0 which allows Windows to match the width to the height. However, in our example, the default width makes the characters hard to read, so I use the width of 16 instead.nEscapement Specifies the orientation of the next character output relative to the previous one in tenths of a degree. Normally, set to 0. Set to 900 to have all the characters go upward from the first character, 1800 to write backwards, or 2700 to write each character from the top down.nOrientation Specifies how much the character should be rotated when output in tenths of a degree. Set to 900 to have all the characters lying on their backs, 1800 for upside-down writing, etc.nWeight Sets the line thickness of each character. Windows defines the following sizes:

FW_DONTCARE equ 0FW_THIN equ 100FW_EXTRALIGHT equ 200FW_ULTRALIGHT equ 200FW_LIGHT equ 300FW_NORMAL equ 400FW_REGULAR equ 400FW_MEDIUM equ 500FW_SEMIBOLD equ 600FW_DEMIBOLD equ 600FW_BOLD equ 700FW_EXTRABOLD equ 800FW_ULTRABOLD equ 800FW_HEAVY equ 900FW_BLACK equ 900

cItalic 0 for normal, any other value for italic characters.cUnderline 0 for normal, any other value for underlined characters.cStrikeOut 0 for normal, any other value for characters with a line through the center.cCharSet The character set of the font. Normally should be OEM_CHARSET which allows Windows to select font which is operating system-dependent.cOutputPrecision Specifies how much the selected font must be closely matched to the characteristics we want. Normally should be OUT_DEFAULT_PRECIS which defines default font mapping behavior.cClipPrecision Specifies the clipping precision. The clipping precision defines how to clip characters that are partially outside the clipping region. You should be able to get by with CLIP_DEFAULT_PRECIS which defines the default clipping behavior.cQuality Specifies the output quality. The output quality defines how carefully GDI must attempt to match the logical-font attributes to those of an actual physical font. There are three choices: DEFAULT_QUALITY, PROOF_QUALITY and DRAFT_QUALITY.cPitchAndFamily Specifies pitch and family of the font. You must combine the pitch value and the family value with "or" operator.lpFacename A pointer to a null-terminated string that specifies the typeface of the font.

The description above is by no means comprehensive. You should refer to your Win32 API reference for more details.

Page 26: 052 Iczelions_Win32_Assembly_Tutorials.pdf

26

invoke SelectObject, hdc, eaxmov hfont,eax

After we get the handle to the logical font, we must use it to select the font into the device context by calling SelectObject. SelectObject puts the new GDI objects such as pens, brushs, and fonts into the device context to be used by GDI functions. It returns the handle to the replaced object which we should save for future SelectObject call. After SelectObject call, any text output function will use the font we just selected into the device context.

RGB 200,200,50invoke SetTextColor,hdc,eaxRGB 0,0,255invoke SetBkColor,hdc,eax

Use RGB macro to create a 32-bit RGB value to be used by SetColorText and SetBkColor.

invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString

Call TextOut function to draw the text on the client area. The text will be in the font and color we specified previously.

invoke SelectObject,hdc, hfont

When we are through with the font, we should restore the old font back into the device context. You should always restore the object that you replaced in the device context.

Page 27: 052 Iczelions_Win32_Assembly_Tutorials.pdf

27

Tutorial 6: Keyboard InputWe will learn how a Windows program receives keyboard input.

Download the example here.

Theory:

Since normally there's only one keyboard in each PC, all running Windows programs must share it between them. Windows is responsible for sending the key strokes to the window which has the input focus.Although there may be several windows on the screen, only one of them has the input focus. The window which has input focus is the only one which can receive key strokes. You can differentiate the window which has input focus from other windows by looking at the title bar. The title bar of the window which has input focus is highlighted.Actually, there are two main types of keyboard messages, depending on your view of the keyboard. You can view a keyboard as a collection of keys. In this case, if you press a key, Windows sends a WM_KEYDOWN message to the window which has input focus, notifying that a key is pressed. When you release the key, Windows sends a WM_KEYUP message. You treat a key as a button. Another way to look at the keyboard is that it's a character input device. When you press "a" key, Windows sends a WM_CHAR message to the window which has input focus, telling it that the user sends "a" character to it. In fact, Windows sends WM_KEYDOWN and WM_KEYUP messages to the window which has input focus and those messages will be translated to WM_CHAR messages by TranslateMessage call. The window procedure may decide to process all three messages or only the messages it's interested in. Most of the time, you can ignore WM_KEYDOWN and WM_KEYUP since TranslateMessage function call in the message loop translate WM_KEYDOWN and WM_KEYUP messages to WM_CHAR messages. We will focus on WM_CHAR in this tutorial.

Example:

.386

.model flat,stdcalloption casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\gdi32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\gdi32.lib

.dataClassName db "SimpleWinClass",0AppName db "Our First Window",0char WPARAM 20h ; the character the program receives from keyboard

.data?hInstance HINSTANCE ?CommandLine LPSTR ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLine

Page 28: 052 Iczelions_Win32_Assembly_Tutorials.pdf

28

mov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL hdc:HDCLOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.ELSEIF uMsg==WM_CHARpush wParampop charinvoke InvalidateRect, hWnd,NULL,TRUE

.ELSEIF uMsg==WM_PAINTinvoke BeginPaint,hWnd, ADDR psmov hdc,eaxinvoke TextOut,hdc,0,0,ADDR char,1invoke EndPaint,hWnd, ADDR ps

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

Page 29: 052 Iczelions_Win32_Assembly_Tutorials.pdf

29

.ENDIFxor eax,eaxret

WndProc endpend start

Analysis:

char WPARAM 20h ; the character the program receives from keyboard

This is the variable that will store the character received from the keyboard. Since the character is sent in WPARAM of the window procedure, we define the variable as type WPARAM for simplicity. The initial value is 20h or the space since when our window refreshes its client area the first time, there is no character input. So we want to display space instead.

.ELSEIF uMsg==WM_CHARpush wParampop charinvoke InvalidateRect, hWnd,NULL,TRUE

This is added in the window procedure to handle the WM_CHAR message. It just puts the character into the variable named "char" and then calls InvalidateRect. InvalidateRect makes the specified rectangle in the client area invalid which forces Windows to send WM_PAINT message to the window procedure. Its syntax is as follows:

InvalidateRect proto hWnd:HWND,\lpRect:DWORD,\bErase:DWORD

lpRect is a pointer to the rectagle in the client area that we want to declare invalid. If this parameter is null, the entire client area will be marked as invalid.bErase is a flag telling Windows if it needs to erase the background. If this flag is TRUE, then Windows will erase the backgroud of the invalid rectangle when BeginPaint is called.

So the strategy we used here is that: we store all necessary information relating to painting the client area and generate WM_PAINT message to paint the client area. Of course, the codes in WM_PAINT section must know beforehand what's expected of them. This seems a roundabout way of doing things but it's the way of Windows.Actually we can paint the client area during processing WM_CHAR message by calling GetDC and ReleaseDC pair. There is no problem there. But the fun begins when our window needs to repaint its client area. Since the codes that paint the character are in WM_CHAR section, the window procedure will not be able to repaint our character in the client area. So the bottom line is: put all necessary data and codes that do painting in WM_PAINT. You can send WM_PAINT message from anywhere in your code anytime you want to repaint the client area.

invoke TextOut,hdc,0,0,ADDR char,1

When InvalidateRect is called, it sends a WM_PAINT message back to the window procedure. So the codes in WM_PAINT section is called. It calls BeginPaint as usual to get the handle to device context and then call TextOut which draws our character in the client area at x=0, y=0. When you run the program and press any key, you will see that character echo in the upper left corner of the client window. And when the window is minimized and maximized again, the character is still there since all the codes and data essential to repaint are all gathered in WM_PAINT section.

Page 30: 052 Iczelions_Win32_Assembly_Tutorials.pdf

30

Tutorial 7: Mouse InputWe will learn how to receive and respond to mouse input in our window procedure. The example program will wait for left mouse clicks and display a text string at the exact clicked spot in the client area.

Download the example here.

Theory:

As with keyboard input, Windows detects and sends notifications about mouse activities that are relevant to each window. Those activities include left and right clicks, mouse cursor movement over window, double clicks. Unlike keyboard input which is directed to the window that has input focus, mouse messages are sent to any window that the mouse cursor is over, active or not. In addition, there are mouse messages about the non-client area too. But most of the time, we can blissfully ignore them. We can focus on those relating to the client area.There are two messages for each mouse button: WM_LBUTTONDOWN,WM_RBUTTONDOWN and WM_LBUTTONUP, WM_RBUTTONUP messages. For a mouse with three buttons, there are also WM_MBUTTONDOWN and WM_MBUTTONUP. When the mouse cursor moves over the client area, Windows sends WM_MOUSEMOVE messages to the window under the cursor.A window can receive double click messages, WM_LBUTTONDBCLK or WM_RBUTTONDBCLK, if and only if its window class has CS_DBLCLKS style flag, else the window will receive only a series of mouse button up and down messages.For all these messages, the value of lParam contains the position of the mouse. The low word is the x-coordinate, and the high word is the y-coordinate relative to upper left corner of the client area of the window. wParam indicates the state of the mouse buttons and Shift and Ctrl keys.

Example:

.386

.model flat,stdcalloption casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\gdi32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\gdi32.lib

.dataClassName db "SimpleWinClass",0AppName db "Our First Window",0MouseClick db 0 ; 0=no click yet

.data?hInstance HINSTANCE ?CommandLine LPSTR ?hitpoint POINT <>

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eax

Page 31: 052 Iczelions_Win32_Assembly_Tutorials.pdf

31

invoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL hdc:HDCLOCAL ps:PAINTSTRUCT

.IF uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.ELSEIF uMsg==WM_LBUTTONDOWNmov eax,lParamand eax,0FFFFhmov hitpoint.x,eaxmov eax,lParamshr eax,16mov hitpoint.y,eaxmov MouseClick,TRUEinvoke InvalidateRect,hWnd,NULL,TRUE

.ELSEIF uMsg==WM_PAINTinvoke BeginPaint,hWnd, ADDR psmov hdc,eax

Page 32: 052 Iczelions_Win32_Assembly_Tutorials.pdf

32

.IF MouseClickinvoke lstrlen,ADDR AppNameinvoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax

.ENDIFinvoke EndPaint,hWnd, ADDR ps

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endpend start

Analysis:

.ELSEIF uMsg==WM_LBUTTONDOWNmov eax,lParamand eax,0FFFFhmov hitpoint.x,eaxmov eax,lParamshr eax,16mov hitpoint.y,eaxmov MouseClick,TRUEinvoke InvalidateRect,hWnd,NULL,TRUE

The window procedure waits for left mouse button click. When it receives WM_LBUTTONDOWN, lParam contains the coordinate of the mouse cursor in the client area. It saves the coordinate in a variable of type POINT which is defined as:

POINT STRUCTx dd ?y dd ?

POINT ENDS

and sets the flag, MouseClick, to TRUE, meaning that there's at least a left mouse button click in the client area.

mov eax,lParamand eax,0FFFFhmov hitpoint.x,eax

Since x-coordinate is the low word of lParam and the members of POINT structure are 32-bit in size, we have to zero out the high word of eax prior to storing it in hitpoint.x.

shr eax,16mov hitpoint.y,eax

Because y-coordinate is the high word of lParam, we must put it in the low word of eax prior to storing it in hitpoint.y. We do this by shifting eax 16 bits to the right.After storing the mouse position, we set the flag, MouseClick, to TRUE in order to let the painting code inWM_PAINT section know that there's at least a click in the client area so it can draw the string at the mouse position. Next we call InvalidateRect function to force the window to repaint its entire client area.

.IF MouseClickinvoke lstrlen,ADDR AppName

Page 33: 052 Iczelions_Win32_Assembly_Tutorials.pdf

33

invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax.ENDIF

The painting code in WM_PAINT section must check if MouseClick is true, since when the window was created, it received a WM_PAINT message which at that time, no mouse click had occurred so it should not draw the string in the client area. We initialize MouseClick to FALSE and change its value to TRUE when an actual mouse click occurs.If at least one mouse click has occurred, it draws the string in the client area at the mouse position. Note that it calls lstrlen to get the length of the string to display and sends the length as the last parameter of TextOut function.

Page 34: 052 Iczelions_Win32_Assembly_Tutorials.pdf

34

Tutorial 8: MenuIn this tutorial, we will learn how to incorporate a menu into our window.Download the example 1 and example 2.

Theory:

Menu is one of the most important component in your window. Menu presents a list of services a program offers to the user. The user doesn't have to read the manual included with the program to be able to use it, he can peruse the menu to get an overview of the capability of a particular program and start playing with it immediately. Since a menu is a tool to get the user up and running quickly, you should follow the standard. Succintly put, the first two menu items should be File and Edit and the last should be Help. You can insert your own menu items between Edit and Help. If a menu item invokes a dialog box, you should append an ellipsis (...) to the menu string.Menu is a kind of resource. There are several kinds of resources such as dialog box, string table, icon, bitmap, menu etc. Resources are described in a separated file called a resource file which normally has .rc extension. You then combine the resources with the source code during the link stage. The final product is an executable file which contains both instructions and resources.You can write resource scripts using any text editor. They're composed of phrases which describe the appearances and other attributes of the resources used in a particular program Although you can write resource scripts with a text editor, it's rather cumbersome. A better alternative is to use a resource editor which lets you visually design resources with ease. Resource editors are usually included in compiler packages such as Visual C++, Borland C++, etc.You describe a menu resource like this:

MyMenu MENU{

[menu list here]}

C programmers may recognize that it is similar to declaring a structure. MyMenu being a menu name followed by MENU keyword and menu list within curly brackets. Alternatively, you can use BEGIN and END instead of the curly brackets if you wish. This syntax is more palatable to Pascal programmers.Menu list can be either MENUITEM or POPUP statement.MENUITEM statement defines a menu bar which doesn't invoke a popup menu when selected.The syntax is as follows:

MENUITEM "&text", ID [,options]It begins by MENUITEM keyword followed by the text you want to use as menu bar string. Note the ampersand. It causes the character that follows it to be underlined. Following the text string is the ID of the menu item. The ID is a number that will be used to identify the menu item in the message sent to the window procedure when the menu item is selected. As such, each menu ID must be unique among themselves.Options are optional. Available options are as follows:

� GRAYED The menu item is inactive, and it does not generate a WM_COMMAND message. The text is grayed.

� INACTIVE The menu item is inactive, and it does not generate a WM_COMMAND message. The text is displayed normally.

� MENUBREAK This item and the following items appear on a new line of the menu.� HELP This item and the following items are right-justified.

You can use one of the above option or combine them with "or" operator. Beware that INACTIVE and GRAYEDcannot be combined together.POPUP statement has the following syntax:

POPUP "&text" [,options]{

[menu list]}POPUP statement defines a menu bar that, when selected, drops down a list of menu items in a small popup window. The menu list can be a MENUTIEM or POPUP statement. There's a special kind of MENUITEM statement, MENUITEM SEPARATOR, which will draw a horizontal line in the popup window.

Page 35: 052 Iczelions_Win32_Assembly_Tutorials.pdf

35

The next step after you are finished with the menu resource script is to reference it in your program.You can do this in two different places in your program.

� In lpszMenuName member of WNDCLASSEX structure. Say, if you have a menu named "FirstMenu", you can assigned the menu to your window like this:

.DATA

MenuName db "FirstMenu",0

...........................

...........................

.CODE

...........................mov wc.lpszMenuName, OFFSET MenuName...........................

� In menu handle parameter of CreateWindowEx like this:

.DATA

MenuName db "FirstMenu",0hMenu HMENU ?

...........................

...........................

.CODE

...........................invoke LoadMenu, hInst, OFFSET MenuNamemov hMenu, eaxinvoke CreateWindowEx,NULL,OFFSET ClsName,\

OFFSET Caption, WS_OVERLAPPEDWINDOW,\CW_USEDEFAULT,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,\NULL,\

hMenu,\hInst,\NULL\

...........................

So you may ask, what's the difference between these two methods?When you reference the menu in the WNDCLASSEX structure, the menu becomes the "default" menu for the window class. Every window of that class will have the same menu.If you want each window created from the same class to have different menus, you must choose the second form. In this case, any window that is passed a menu handle in its CreateWindowEx function will have a menu that "overrides" the default menu defined in the WNDCLASSEX structure.Next we will examine how a menu notifies the window procedure when the user selects a menu item.When the user selects a menu item, the window procedure will receive a WM_COMMAND message. The low word of wParam contains the menu ID of the selected menu item.Now we have sufficient information to create and use a menu. Let's do it.

Example:

The first example shows how to create and use a menu by specifying the menu name in the window class.

Page 36: 052 Iczelions_Win32_Assembly_Tutorials.pdf

36

.386

.model flat,stdcalloption casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

.dataClassName db "SimpleWinClass",0AppName db "Our First Window",0MenuName db "FirstMenu",0 ; The name of our menu in the resource file.Test_string db "You selected Test menu item",0Hello_string db "Hello, my friend",0Goodbye_string db "See you again, bye",0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?

.constIDM_TEST equ 1 ; Menu IDsIDM_HELLO equ 2IDM_GOODBYE equ 3IDM_EXIT equ 4

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,OFFSET MenuName ; Put our menu name heremov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eax

Page 37: 052 Iczelions_Win32_Assembly_Tutorials.pdf

37

invoke RegisterClassEx, addr wcinvoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

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 ax==IDM_TEST

invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK.ELSEIF ax==IDM_HELLO

invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK.ELSEIF ax==IDM_GOODBYE

invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK.ELSE

invoke DestroyWindow,hWnd.ENDIF

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endpend start**************************************************************************************************************************

Menu.rc**************************************************************************************************************************

#define IDM_TEST 1#define IDM_HELLO 2#define IDM_GOODBYE 3#define IDM_EXIT 4

FirstMenu MENU{POPUP "&PopUp"

{MENUITEM "&Say Hello",IDM_HELLOMENUITEM "Say &GoodBye", IDM_GOODBYEMENUITEM SEPARATORMENUITEM "E&xit",IDM_EXIT}

MENUITEM "&Test", IDM_TEST

Page 38: 052 Iczelions_Win32_Assembly_Tutorials.pdf

38

}

Analysis:

Let's analyze the resource file first.#define IDM_TEST 1 /* equal to IDM_TEST equ 1*/

#define IDM_HELLO 2#define IDM_GOODBYE 3#define IDM_EXIT 4The above lines define the menu IDs used by the menu script. You can assign any value to the ID as long as the

value is unique in the menu.

FirstMenu MENU

Declare your menu with MENU keyword.

POPUP "&PopUp"{MENUITEM "&Say Hello",IDM_HELLOMENUITEM "Say &GoodBye", IDM_GOODBYEMENUITEM SEPARATORMENUITEM "E&xit",IDM_EXIT}

Define a popup menu with four menu items, the third one is a menu separator.

MENUITEM "&Test", IDM_TEST

Define a menu bar in the main menu.Next we will examine the source code.

MenuName db "FirstMenu",0 ; The name of our menu in the resource file.Test_string db "You selected Test menu item",0Hello_string db "Hello, my friend",0Goodbye_string db "See you again, bye",0MenuName is the name of the menu in the resource file. Note that you can define more than one menu in the

resource file so you must specify which menu you want to use. The remaining three lines define the text strings to be displayed in message boxes that are invoked when the appropriate menu item is selected by the user.

IDM_TEST equ 1 ; Menu IDsIDM_HELLO equ 2IDM_GOODBYE equ 3IDM_EXIT equ 4Define menu IDs for use in the window procedure. These values MUST be identical to those defined in the

resource file.

.ELSEIF uMsg==WM_COMMANDmov eax,wParam.IF ax==IDM_TEST

invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK.ELSEIF ax==IDM_HELLO

invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK.ELSEIF ax==IDM_GOODBYE

invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK.ELSE

Page 39: 052 Iczelions_Win32_Assembly_Tutorials.pdf

39

invoke DestroyWindow,hWnd.ENDIF

In the window procedure, we process WM_COMMAND messages. When the user selects a menu item, the menu ID of that menu item is sended to the window procedure in the low word of wParam along with the WM_COMMAND message. So when we store the value of wParam in eax, we compare the value in ax to the menu IDs we defined previously and act accordingly. In the first three cases, when the user selects Test, Say Hello, and Say GoodBye menu items, we just display a text string in a message box.If the user selects Exit menu item, we call DestroyWindow with the handle of our window as its parameter which will close our window.As you can see, specifying menu name in a window class is quite easy and straightforward. However you can also use an alternate method to load a menu in your window. I won't show the entire source code here. The resource file is the same in both methods. There are some minor changes in the source file which I 'll show below.

.data?hInstance HINSTANCE ?CommandLine LPSTR ?hMenu HMENU ? ; handle of our menuDefine a variable of type HMENU to store our menu handle.

invoke LoadMenu, hInst, OFFSET MenuNamemov hMenu,eaxINVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\hInst,NULL

Before calling CreateWindowEx, we call LoadMenu with the instance handle and a pointer to the name of our menu. LoadMenu returns the handle of our menu in the resource file which we pass to CreateWindowEx.

Page 40: 052 Iczelions_Win32_Assembly_Tutorials.pdf

40

Tutorial 9: Child Window ControlsIn this tutorial, we will explore child window controls which are very important input and output devices of our programs.

Download the example here.

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.

Example:

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 box2. Clear Edit Box -- Clear the content of the edit box3. Get Text -- Display a message box with the text in the edit box4. Exit -- Close the program.

.386

.model flat,stdcalloption casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

Page 41: 052 Iczelions_Win32_Assembly_Tutorials.pdf

41

.dataClassName db "SimpleWinClass",0AppName db "Our First Window",0MenuName db "FirstMenu",0ButtonClassName db "button",0ButtonText db "My First Button",0EditClassName db "edit",0TestString 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

.constButtonID equ 1 ; The control ID of the button controlEditID equ 2 ; The control ID of the edit controlIDM_HELLO equ 1IDM_CLEAR equ 2IDM_GETTEXT equ 3IDM_EXIT equ 4

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_BTNFACE+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName, \

ADDR AppName, WS_OVERLAPPEDWINDOW,\CW_USEDEFAULT, CW_USEDEFAULT,\300,200,NULL,NULL, hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMAL

Page 42: 052 Iczelions_Win32_Assembly_Tutorials.pdf

42

invoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

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,eaxinvoke SetFocus, hwndEditinvoke 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_HELLOinvoke SetWindowText,hwndEdit,ADDR TestString

.ELSEIF ax==IDM_CLEARinvoke SetWindowText,hwndEdit,NULL

.ELSEIF ax==IDM_GETTEXTinvoke GetWindowText,hwndEdit,ADDR buffer,512invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

.ELSEinvoke DestroyWindow,hWnd

.ENDIF.ELSE

.IF ax==ButtonIDshr eax,16.IF ax==BN_CLICKED

invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0.ENDIF

.ENDIF.ENDIF

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eax

retWndProc endpend start

Analysis:

Let's analyze the program.

Page 43: 052 Iczelions_Win32_Assembly_Tutorials.pdf

43

.ELSEIF uMsg==WM_CREATEinvoke 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,eaxinvoke SetFocus, hwndEditinvoke CreateWindowEx,NULL, ADDR ButtonClassName,\

ADDR ButtonText,\WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\75,70,140,25,hWnd,ButtonID,hInstance,NULL

mov hwndButton,eaxWe 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_COMMANDmov 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 0Control 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_HELLOinvoke SetWindowText,hwndEdit,ADDR TestString

.ELSEIF ax==IDM_CLEARinvoke SetWindowText,hwndEdit,NULL

.ELSEIF ax==IDM_GETTEXTinvoke GetWindowText,hwndEdit,ADDR buffer,512invoke 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==ButtonIDshr eax,16

Page 44: 052 Iczelions_Win32_Assembly_Tutorials.pdf

44

.IF ax==BN_CLICKEDinvoke 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.

Page 45: 052 Iczelions_Win32_Assembly_Tutorials.pdf

45

Tutorial 10: Dialog Box as Main WindowNow 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.

Download the first example here, the second example here.

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

Page 46: 052 Iczelions_Win32_Assembly_Tutorials.pdf

46

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_bufferIn 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.

Examples:

dialog.asm

.386

.model flat,stdcalloption casemap:noneWinMain proto :DWORD,:DWORD,:DWORD,:DWORDinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

.dataClassName db "DLGCLASS",0MenuName db "MyMenu",0DlgName db "MyDialog",0

Page 47: 052 Iczelions_Win32_Assembly_Tutorials.pdf

47

AppName db "Our First Dialog Box",0TestString db "Wow! I'm in an edit box now",0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?buffer db 512 dup(?)

.constIDC_EDIT equ 3000IDC_BUTTON equ 3001IDC_EXIT equ 3002IDM_GETTEXT equ 32000IDM_CLEAR equ 32001IDM_EXIT equ 32002

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hDlg:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,DLGWINDOWEXTRApush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_BTNFACE+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULLmov hDlg,eaxinvoke ShowWindow, hDlg,SW_SHOWNORMALinvoke UpdateWindow, hDlginvoke GetDlgItem,hDlg,IDC_EDITinvoke 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 msginvoke DispatchMessage, ADDR msg

.ENDIF

Page 48: 052 Iczelions_Win32_Assembly_Tutorials.pdf

48

.ENDWmov eax,msg.wParamret

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_GETTEXTinvoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

.ELSEIF ax==IDM_CLEARinvoke SetDlgItemText,hWnd,IDC_EDIT,NULL

.ELSEinvoke DestroyWindow,hWnd

.ENDIF.ELSE

mov edx,wParamshr edx,16.IF dx==BN_CLICKED

.IF ax==IDC_BUTTONinvoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString

.ELSEIF ax==IDC_EXITinvoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

.ENDIF.ENDIF

.ENDIF.ELSE

invoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endpend 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, 60STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOKCAPTION "Our First Dialog Box"

Page 49: 052 Iczelions_Win32_Assembly_Tutorials.pdf

49

CLASS "DLGCLASS"BEGIN

EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFTDEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13, WS_GROUP

END

MyMenu MENUBEGIN

POPUP "Test Controls"BEGIN

MENUITEM "Get Text", IDM_GETTEXTMENUITEM "Clear Text", IDM_CLEARMENUITEM "", , 0x0800 /*MFT_SEPARATOR*/MENUITEM "E&xit", IDM_EXIT

ENDEND

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"

BEGINEDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFTDEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13PUSHBUTTON "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 ENDkeywords. Generally the syntax is as follows:

control-type "text" ,controlID, x, y, width, height [,styles]

Page 50: 052 Iczelions_Win32_Assembly_Tutorials.pdf

50

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,DLGWINDOWEXTRAmov 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,NULLAfter 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_EDITinvoke 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 msginvoke 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,stdcalloption casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

Page 51: 052 Iczelions_Win32_Assembly_Tutorials.pdf

51

.dataDlgName db "MyDialog",0AppName db "Our Second Dialog Box",0TestString db "Wow! I'm in an edit box now",0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?buffer db 512 dup(?)

.constIDC_EDIT equ 3000IDC_BUTTON equ 3001IDC_EXIT equ 3002IDM_GETTEXT equ 32000IDM_CLEAR equ 32001IDM_EXIT equ 32002

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULLinvoke ExitProcess,eax

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.IF uMsg==WM_INITDIALOG

invoke GetDlgItem, hWnd,IDC_EDITinvoke SetFocus,eax

.ELSEIF uMsg==WM_CLOSEinvoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

.ELSEIF uMsg==WM_COMMANDmov eax,wParam.IF lParam==0

.IF ax==IDM_GETTEXTinvoke GetDlgItemText,hWnd,IDC_EDIT,ADDR buffer,512invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

.ELSEIF ax==IDM_CLEARinvoke SetDlgItemText,hWnd,IDC_EDIT,NULL

.ELSEIF ax==IDM_EXITinvoke EndDialog, hWnd,NULL

.ENDIF.ELSE

mov edx,wParamshr edx,16.if dx==BN_CLICKED

.IF ax==IDC_BUTTONinvoke SetDlgItemText,hWnd,IDC_EDIT,ADDR TestString

.ELSEIF ax==IDC_EXITinvoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0

.ENDIF.ENDIF

.ENDIF.ELSE

mov eax,FALSEret

.ENDIFmov eax,TRUE

Page 52: 052 Iczelions_Win32_Assembly_Tutorials.pdf

52

retDlgProc endpend 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, 60STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOKCAPTION "Our Second Dialog Box"MENU IDR_MENU1BEGIN

EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFTDEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13

END

IDR_MENU1 MENUBEGIN

POPUP "Test Controls"BEGIN

MENUITEM "Get Text", IDM_GETTEXTMENUITEM "Clear Text", IDM_CLEARMENUITEM "", , 0x0800 /*MFT_SEPARATOR*/MENUITEM "E&xit", IDM_EXIT

ENDEND

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

Page 53: 052 Iczelions_Win32_Assembly_Tutorials.pdf

53

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_INITDIALOGinvoke GetDlgItem, hWnd,IDC_EDITinvoke SetFocus,eax

.ELSEIF uMsg==WM_CLOSEinvoke 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.

Page 54: 052 Iczelions_Win32_Assembly_Tutorials.pdf

54

Tutorial 11: More about Dialog BoxWe will learn more about dialog box in this tutorial. Specifically, we will explore the topic of how to use dialog boxs as our input-output devices. If you read the previous tutorial, this one will be a breeze since only a minor modification is all that's needed to be able to use dialog boxes as adjuncts to our main window. Also in this tutorial, we will learn how to use common dialog boxes.

Download the dialog box examples here and here. Download Common Dialog Box example here.

Theory:

Very little is to be said about how to use dialog boxes as input-output devices of our program. Your program creates the main window as usual and when you want to display the dialog box, just call CreateDialogParam or DialogBoxParam. With DialogBoxParam call, you don't have to do anything more, just process the messages in the dialog box procedure. With CreateDialogParam, you must insert IsDialogMessage call in the message loop to let dialog box manager handle the keyboard navigation in your dialog box for you. Since the two cases are trivial, I'll not put the source code here. You can download the examples and examine them yourself, here and here.Let's go on to the common dialog boxes. Windows has prepared predefined dialog boxes for use by your applications. These dialog boxes exist to provide standardized user interface. They consist of file, print, color, font, and search dialog boxes. You should use them as much as possible. The dialog boxes reside in comdlg32.dll. In order to use them, you have to link to comdlg32.lib. You create these dialog boxes by calling appropriate functions in the common dialog library. For open file dialog, it is GetOpenFileName, for save as dialog it is GetSaveFileName, for print dialog it is PrintDlg and so on. Each one of these functions takes a pointer to a structure as its parameter. You should look them up in Win32 API reference. In this tutorial, I'll demonstrate how to create and use an open file dialog.Below is the function prototype of GetOpenFileName function:

GetOpenFileName proto lpofn:DWORDYou can see that it receives only one parameter, a pointer to an OPENFILENAME structure. The return value TRUE means the user selected a file to open, it's FALSE otherwise. We will look at OPENFILENAME structure next.

OPENFILENAME STRUCTlStructSize DWORD ?hwndOwner HWND ?hInstance HINSTANCE ?lpstrFilter LPCSTR ?lpstrCustomFilter LPSTR ?nMaxCustFilter DWORD ?nFilterIndex DWORD ?lpstrFile LPSTR ?nMaxFile DWORD ?lpstrFileTitle LPSTR ?nMaxFileTitle DWORD ?lpstrInitialDir LPCSTR ?lpstrTitle LPCSTR ?Flags DWORD ?nFileOffset WORD ?nFileExtension WORD ?lpstrDefExt LPCSTR ?lCustData LPARAM ?lpfnHook DWORD ?lpTemplateName LPCSTR ?

OPENFILENAME ENDSLet's see the meaning of the frequently used members.

Page 55: 052 Iczelions_Win32_Assembly_Tutorials.pdf

55

lStructSize The size of the OPENFILENAME structure , in byteshwndOwner The window handle of the open file dialog box.hInstance Instance handle of the application that creates the open file dialog box

lpstrFilter

The filter strings in the format of pairs of null terminated strings. The first string in each pair is the description. The second string is the filter pattern. for example:

FilterString db "All Files (*.*)",0, "*.*",0db "Text Files (*.txt)",0,"*.txt",0,0

Note that only the pattern in the second string in each pair is actually used by Windows to filter out the files. Also noted that you have to put an extra 0 at the end of the filter strings to denote the end of it.

nFilterIndexSpecify which pair of the filter strings will be initially used when the open file dialog is first displayed. The index is 1-based, that is the first pair is 1, the second pair is 2 and so on. So in the above example, if we specify nFilterIndex as 2, the second pattern, "*.txt" will be used.

lpstrFile

Pointer to the buffer that contains the filename used to initialize the filename edit control on the dialog box. The buffer should be at least 260 bytes long.After the user selects a file to open, the filename with full path is stored in this buffer. You can extract the information from it later.

nMaxFile The size of the lpstrFile buffer.lpstrTitle Pointer to the title of the open file dialog boxFlags Determine the styles and characteristics of the dialog box.

nFileOffsetAfter the user selects a file to open, this member contains the index to the first character of the actual filename. For example, if the full name with path is "c:\windows\system\lz32.dll", the this member will contain the value 18.

nFileExtension After the user selects a file to open, this member contains the index to the first character of the file extension

Example:

The following program displays an open file dialog box when the user selects File-> Open from the menu. When the user selects a file in the dialog box, the program displays a message box showing the full name, filename,and extension of the selected file.

.386

.model flat,stdcalloption casemap:noneWinMain proto :DWORD,:DWORD,:DWORD,:DWORDinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\comdlg32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\comdlg32.lib

.constIDM_OPEN equ 1IDM_EXIT equ 2MAXSIZE equ 260OUTPUTSIZE equ 512

.dataClassName db "SimpleWinClass",0AppName db "Our Main Window",0MenuName db "FirstMenu",0

Page 56: 052 Iczelions_Win32_Assembly_Tutorials.pdf

56

ofn OPENFILENAME <>FilterString db "All Files",0,"*.*",0

db "Text Files",0,"*.txt",0,0buffer db MAXSIZE dup(0)OurTitle db "-=Our First Open File Dialog Box=-: Choose the file to open",0FullPathName db "The Full Filename with Path is: ",0FullName db "The Filename is: ",0ExtensionName db "The Extension is: ",0OutputString db OUTPUTSIZE dup(0)CrLf db 0Dh,0Ah,0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,300,200,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParam

Page 57: 052 Iczelions_Win32_Assembly_Tutorials.pdf

57

retWinMain 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 ax==IDM_OPEN

mov ofn.lStructSize,SIZEOF ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter, OFFSET FilterStringmov ofn.lpstrFile, OFFSET buffermov ofn.nMaxFile,MAXSIZEmov ofn.Flags, OFN_FILEMUSTEXIST or \

OFN_PATHMUSTEXIST or OFN_LONGNAMES or\OFN_EXPLORER or OFN_HIDEREADONLY

mov ofn.lpstrTitle, OFFSET OurTitleinvoke GetOpenFileName, ADDR ofn.if eax==TRUE

invoke lstrcat,offset OutputString,OFFSET FullPathNameinvoke lstrcat,offset OutputString,ofn.lpstrFileinvoke lstrcat,offset OutputString,offset CrLfinvoke lstrcat,offset OutputString,offset FullNamemov eax,ofn.lpstrFilepush ebxxor ebx,ebxmov bx,ofn.nFileOffsetadd eax,ebxpop ebxinvoke lstrcat,offset OutputString,eaxinvoke lstrcat,offset OutputString,offset CrLfinvoke lstrcat,offset OutputString,offset ExtensionNamemov eax,ofn.lpstrFilepush ebxxor ebx,ebxmov bx,ofn.nFileExtensionadd eax,ebxpop ebxinvoke lstrcat,offset OutputString,eaxinvoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OKinvoke RtlZeroMemory,offset OutputString,OUTPUTSIZE

.endif.else

invoke DestroyWindow, hWnd.endif

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endpend start

Page 58: 052 Iczelions_Win32_Assembly_Tutorials.pdf

58

Analysis:

mov ofn.lStructSize,SIZEOF ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstance

We fill in the routine members of ofn structures.

mov ofn.lpstrFilter, OFFSET FilterString

This FilterString is the filename filter that we specify as follows:

FilterString db "All Files",0,"*.*",0db "Text Files",0,"*.txt",0,0

Note that All four strings are zero terminated. The first string is the description of the following string. The actual pattern is the even number string, in this case, "*.*" and "*.txt". Actually we can specify any pattern we want here. We MUST put an extra zero after the last pattern string to denote the end of the filter string. Don't forget this else your dialog box will behave strangely.

mov ofn.lpstrFile, OFFSET buffermov ofn.nMaxFile,MAXSIZE

We specify where the dialog box will put the filename that the user selects. Note that we must specify its size in nMaxFile member. We can later extract the filename from this buffer.

mov ofn.Flags, OFN_FILEMUSTEXIST or \OFN_PATHMUSTEXIST or OFN_LONGNAMES or\OFN_EXPLORER or OFN_HIDEREADONLY

Flags specifies the characteristics of the dialog box.OFN_FILEMUSTEXIST and OFN_PATHMUSTEXIST flags demand that the filename and path that the user types in the filename edit control MUST exist.OFN_LONGNAMES flag tells the dialog box to show long filenames.OFN_EXPLORER flag specifies that the appearance of the dialog box must be explorer-like.OFN_HIDEREADONLY flag hides the read-only checkbox on the dialog box.There are many more flags that you can use. Consult your Win32 API reference.

mov ofn.lpstrTitle, OFFSET OurTitle

Specify the title of the dialog box.

invoke GetOpenFileName, ADDR ofn

Call the GetOpenFileName function. Passing the pointer to the ofn structure as its parameter.At this time, the open file dialog box is displayed on the screen. The function will not return until the user selects a file to open or presses the cancel button or closes the dialog box.It 'll return the value TRUE in eax if the user selects a file to open. It returns FALSE otherwise.

.if eax==TRUEinvoke lstrcat,offset OutputString,OFFSET FullPathNameinvoke lstrcat,offset OutputString,ofn.lpstrFileinvoke lstrcat,offset OutputString,offset CrLfinvoke lstrcat,offset OutputString,offset FullName

Page 59: 052 Iczelions_Win32_Assembly_Tutorials.pdf

59

In case the user selects a file to open, we prepare an output string to be displayed in a message box. We allocate a block of memory in OutputString variable and then we use an API function, lstrcat, to concatenate the strings together. In order to put the strings into several lines, we must separate each line with a carriage return-line feed pair.

mov eax,ofn.lpstrFilepush ebxxor ebx,ebxmov bx,ofn.nFileOffsetadd eax,ebxpop ebxinvoke lstrcat,offset OutputString,eax

The above lines require some explanation. nFileOffset contains the index into the ofn.lpstrFile. But you cannot add them together directly since nFileOffset is a WORD-sized variable and lpstrFile is a DWORD-sized one. So I have to put the value of nFileOffset into the low word of ebx and add it to the value of lpstrFile.

invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK

We display the string in a message box.

invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE

We must *clear* the OutputString before we can fill in another string. So we use RtlZeroMemory function to do the job.

Page 60: 052 Iczelions_Win32_Assembly_Tutorials.pdf

60

Tutorial 12: Memory Management and File I/OWe will learn the rudimentary of memory management and file i/o operation in this tutorial. In addition we'll use common dialog boxes as input-output devices.

Download the example here.

Theory:

Memory management under Win32 from the application's point of view is quite simple and straightforward. Each process owns a 4 GB memory address space. The memory model used is called flat memory model. In this model, all segment registers (or selectors) point to the same starting address and the offset is 32-bit so an application can access memory at any point in its own address space without the need to change the value of selectors. This simplifies memory management a lot. There's no "near" or "far" pointer anymore.Under Win16, there are two main categories of memory API functions: Global and Local. Global-type API calls deal with memory allocated in other segments thus they're "far" memory functions. Local-type API calls deal with the local heap of the process so they're "near" memory functions. Under Win32, these two types are identical. Whether you call GlobalAlloc or LocalAlloc, you get the same result.Steps in allocating and using memory are as follows:

1. Allocate a block of memory by calling GlobalAlloc. This function returns a handle to the requested memory block.

2. "Lock" the memory block by calling GlobalLock. This function accepts a handle to the memory block and returns a pointer to the memory block.

3. You can use the pointer to read or write memory.4. "Unlock" the memory block by calling GlobalUnlock . This function invalidates the pointer to the memory

block.5. Free the memory block by calling GlobalFree. This function accepts the handle to the memory block.

You can also substitute "Global" by "Local" such as LocalAlloc, LocalLock,etc.The above method can be further simplified by using a flag in GlobalAlloc call, GMEM_FIXED. If you use this flag, the return value from Global/LocalAlloc will be the pointer to the allocated memory block, not the memory block handle. You don't have to call Global/LocalLock and you can pass the pointer to Global/LocalFree without calling Global/LocalUnlock first. But in this tutorial, I'll use the "traditional" approach since you may encounter it when reading the source code of other programs.

File I/O under Win32 bears remarkable semblance to that under DOS. The steps needed are the same. You onlyhave to change interrupts to API calls and it's done. The required steps are the followings:

1. Open or Create the file by calling CreateFile function. This function is very versatile: in addition to files, it can open communication ports, pipes, disk drives or console. On success, it returns a handle to file or device. You can then use this handle to perform operations on the file or device.Move the file pointer to the desired location by calling SetFilePointer.

2. Perform read or write operation by calling ReadFile or WriteFile. These functions transfer data from a block of memory to or from the file. So you have to allocate a block of memory large enough to hold the data.

3. Close the file by calling CloseHandle. This function accepts the file handle.

Content:

The program listed below displays an open file dialog box. It lets the user select a text file to open and shows the content of that file in an edit control in its client area. The user can modify the text in the edit control as he wishes, and can choose to save the content in a file.

Page 61: 052 Iczelions_Win32_Assembly_Tutorials.pdf

61

.386

.model flat,stdcalloption casemap:noneWinMain proto :DWORD,:DWORD,:DWORD,:DWORDinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\comdlg32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\comdlg32.lib

.constIDM_OPEN equ 1IDM_SAVE equ 2IDM_EXIT equ 3MAXSIZE equ 260MEMSIZE equ 65535

EditID equ 1 ; ID of the edit control

.dataClassName db "Win32ASMEditClass",0AppName db "Win32 ASM Edit",0EditClass db "edit",0MenuName db "FirstMenu",0ofn OPENFILENAME <>FilterString db "All Files",0,"*.*",0

db "Text Files",0,"*.txt",0,0buffer db MAXSIZE dup(0)

.data?hInstance HINSTANCE ?CommandLine LPSTR ?hwndEdit HWND ? ; Handle to the edit controlhFile HANDLE ? ; File handlehMemory HANDLE ? ;handle to the allocated memory blockpMemory DWORD ? ;pointer to the allocated memory blockSizeReadWrite DWORD ? ; number of bytes actually read or write

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULL

Page 62: 052 Iczelions_Win32_Assembly_Tutorials.pdf

62

push hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,300,200,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.IF uMsg==WM_CREATE

invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\0,0,0,hWnd,EditID,\hInstance,NULL

mov hwndEdit,eaxinvoke SetFocus,hwndEdit

;==============================================; Initialize the members of OPENFILENAME structure;==============================================

mov ofn.lStructSize,SIZEOF ofnpush hWndpop ofn.hWndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter, OFFSET FilterStringmov ofn.lpstrFile, OFFSET buffermov ofn.nMaxFile,MAXSIZE

.ELSEIF uMsg==WM_SIZEmov eax,lParammov edx,eaxshr edx,16and eax,0ffffhinvoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

.ELSEIF uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.ELSEIF uMsg==WM_COMMANDmov eax,wParam.if lParam==0

Page 63: 052 Iczelions_Win32_Assembly_Tutorials.pdf

63

.if ax==IDM_OPENmov ofn.Flags, OFN_FILEMUSTEXIST or \

OFN_PATHMUSTEXIST or OFN_LONGNAMES or\OFN_EXPLORER or OFN_HIDEREADONLY

invoke GetOpenFileName, ADDR ofn.if eax==TRUE

invoke CreateFile,ADDR buffer,\GENERIC_READ or GENERIC_WRITE ,\FILE_SHARE_READ or FILE_SHARE_WRITE,\NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\NULL

mov hFile,eaxinvoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZEmov hMemory,eaxinvoke GlobalLock,hMemorymov pMemory,eaxinvoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULLinvoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemoryinvoke CloseHandle,hFileinvoke GlobalUnlock,pMemoryinvoke GlobalFree,hMemory

.endifinvoke SetFocus,hwndEdit

.elseif ax==IDM_SAVEmov ofn.Flags,OFN_LONGNAMES or\

OFN_EXPLORER or OFN_HIDEREADONLYinvoke GetSaveFileName, ADDR ofn

.if eax==TRUEinvoke CreateFile,ADDR buffer,\

GENERIC_READ or GENERIC_WRITE ,\FILE_SHARE_READ or FILE_SHARE_WRITE,\NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\NULL

mov hFile,eaxinvoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZEmov hMemory,eaxinvoke GlobalLock,hMemorymov pMemory,eaxinvoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemoryinvoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULLinvoke CloseHandle,hFileinvoke GlobalUnlock,pMemoryinvoke GlobalFree,hMemory

.endifinvoke SetFocus,hwndEdit

.elseinvoke DestroyWindow, hWnd

.endif.endif

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxretWndProc endpend start

Page 64: 052 Iczelions_Win32_Assembly_Tutorials.pdf

64

Analysis:

invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\0,0,0,hWnd,EditID,\hInstance,NULL

mov hwndEdit,eax

In WM_CREATE section, we create an edit control. Note that the parameters that specify x, y, width,height of the control are all zeroes since we will resize the control later to cover the whole client area of the parent window.Note that in this case, we don't have to call ShowWindow to make the edit control appear on the screen because we include WS_VISIBLE style. You can use this trick in the parent window too.

;==============================================; Initialize the members of OPENFILENAME structure;==============================================

mov ofn.lStructSize,SIZEOF ofnpush hWndpop ofn.hWndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter, OFFSET FilterStringmov ofn.lpstrFile, OFFSET buffermov ofn.nMaxFile,MAXSIZE

After creating the edit control, we take this time to initialize the members of ofn. Because we want to reuse ofn in the save as dialog box too, we fill in only the *common* members that're used by both GetOpenFileName and GetSaveFileName.WM_CREATE section is a great place to do once-only initialization.

.ELSEIF uMsg==WM_SIZEmov eax,lParammov edx,eaxshr edx,16and eax,0ffffhinvoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

We receive WM_SIZE messages when the size of the client area of our main window changes. We also receive it when the window is first created. In order to be able to receive this message, the window class styles must include CS_VREDRAW and CS_HREDRAW styles. We use this opportunity to resize our edit control to the same size as the client area of the parent window. First we have to know the current width and height of the client area of the parent window. We get this info from lParam. The high word of lParam contains the height and the low word of lParam the width of the client area. We then use the information to resize the edit control by calling MoveWindow function which, in addition to changing the position of the window, can alter the size too.

.if ax==IDM_OPENmov ofn.Flags, OFN_FILEMUSTEXIST or \

OFN_PATHMUSTEXIST or OFN_LONGNAMES or\OFN_EXPLORER or OFN_HIDEREADONLY

invoke GetOpenFileName, ADDR ofn

When the user selects File/Open menu item, we fill in the Flags member of ofn structure and call GetOpenFileName function to display the open file dialog box.

.if eax==TRUEinvoke CreateFile,ADDR buffer,\

GENERIC_READ or GENERIC_WRITE ,\

Page 65: 052 Iczelions_Win32_Assembly_Tutorials.pdf

65

FILE_SHARE_READ or FILE_SHARE_WRITE,\NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\NULL

mov hFile,eax

After the user selects a file to open, we call CreateFile to open the file. We specifies that the function should try to open the file for read and write. After the file is opened, the function returns the handle to the opened file which we store in a global variable for future use. This function has the following syntax:

CreateFile proto lpFileName:DWORD,\dwDesiredAccess:DWORD,\dwShareMode:DWORD,\lpSecurityAttributes:DWORD,\dwCreationDistribution:DWORD\,dwFlagsAndAttributes:DWORD\,hTemplateFile:DWORD

dwDesiredAccess specifies which operation you want to perform on the file.

� 0 Open the file to query its attributes. You have to rights to read or write the data.� GENERIC_READ Open the file for reading.� GENERIC_WRITE Open the file for writing.

dwShareMode specifies which operation you want to allow other processes to perform on the file that 's being opened.

� 0 Don't share the file with other processes.� FILE_SHARE_READ allow other processes to read the data from the file being opened� FILE_SHARE_WRITE allow other processes to write data to the file being opened.

lpSecurityAttributes has no significance under Windows 95.dwCreationDistribution specifies the action CreateFile will perform when the file specified in lpFileName exists or when it doesn't exist.

� CREATE_NEW Creates a new file. The function fails if the specified file already exists.� CREATE_ALWAYS Creates a new file. The function overwrites the file if it exists.� OPEN_EXISTING Opens the file. The function fails if the file does not exist.� OPEN_ALWAYS Opens the file, if it exists. If the file does not exist, the function creates the file as if

dwCreationDistribution were CREATE_NEW.� TRUNCATE_EXISTING Opens the file. Once opened, the file is truncated so that its size is zero bytes. The

calling process must open the file with at least GENERIC_WRITE access. The function fails if the file does not exist.

dwFlagsAndAttributes specifies the file attributes

� FILE_ATTRIBUTE_ARCHIVE The file is an archive file. Applications use this attribute to mark files for backup or removal.

� FILE_ATTRIBUTE_COMPRESSED The file or directory is compressed. For a file, this means that all of the data in the file is compressed. For a directory, this means that compression is the default for newly created files and subdirectories.

� FILE_ATTRIBUTE_NORMAL The file has no other attributes set. This attribute is valid only if used alone.� FILE_ATTRIBUTE_HIDDEN The file is hidden. It is not to be included in an ordinary directory listing.� FILE_ATTRIBUTE_READONLY The file is read only. Applications can read the file but cannot write to it or

delete it.� FILE_ATTRIBUTE_SYSTEM The file is part of or is used exclusively by the operating system.

Page 66: 052 Iczelions_Win32_Assembly_Tutorials.pdf

66

invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZEmov hMemory,eaxinvoke GlobalLock,hMemorymov pMemory,eax

When the file is opened, we allocate a block of memory for use by ReadFile and WriteFile functions. We specify GMEM_MOVEABLE flag to let Windows move the memory block around to consolidate memory. GMEM_ZEROINIT flag tells GlobalAlloc to fill the newly allocated memory block with zeroes.When GlobalAlloc returns successfully, eax contains the handle to the allocated memory block. We pass this handle to GlobalLock function which returns a pointer to the memory block.

invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULLinvoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory

When the memory block is ready for use, we call ReadFile function to read data from the file. When a file is first opened or created, the file pointer is at offset 0. So in this case, we start reading from the first byte in the file onwards. The first parameter of ReadFile is the handle of the file to read, the second is the pointer to the memory block to hold the data, next is the number of bytes to read from the file, the fourth param is the address of the variable of DWORD size that will be filled with the number of bytes actually read from the file.After we fill the memory block with the data, we put the data into the edit control by sending WM_SETTEXT message to the edit control with lParam containing the pointer to the memory block. After this call, the edit control shows the data in its client area.

invoke CloseHandle,hFileinvoke GlobalUnlock,pMemoryinvoke GlobalFree,hMemory

.endif

At this point, we have no need to keep the file open any longer since our purpose is to write the modified data from the edit control to another file, not the original file. So we close the file by calling CloseHandle with the file handle as its parameter. Next we unlock the memory block and free it. Actually you don't have to free the memory at this point, you can reuse the memory block during the save operation later. But for demonstration purpose , I choose to free it here.

invoke SetFocus,hwndEdit

When the open file dialog box is displayed on the screen, the input focus shifts to it. So after the open file dialog is closed, we must move the input focus back to the edit control.This end the read operation on the file. At this point, the user can edit the content of the edit control.And when he wants to save the data to another file, he must select File/Save as menuitem which displays a save as dialog box. The creation of the save as dialog box is not much different from the open file dialog box. In fact, they differ in only the name of the functions, GetOpenFileName and GetSaveFileName. You can reuse most members of the ofn structure too except the Flags member.

mov ofn.Flags,OFN_LONGNAMES or\OFN_EXPLORER or OFN_HIDEREADONLY

In our case, we want to create a new file, so OFN_FILEMUSTEXIST and OFN_PATHMUSTEXIST must be left out else the dialog box will not let us create a file that doesn't already exist.The dwCreationDistribution parameter of the CreateFile function must be changed to CREATE_NEW since we want to create a new file.The remaining code is identical to those in the open file section except the following:

invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemoryinvoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL

Page 67: 052 Iczelions_Win32_Assembly_Tutorials.pdf

67

We send WM_GETTEXT message to the edit control to copy the data from it to the memory block we provide, the return value in eax is the length of the data inside the buffer. After the data is in the memory block, we write them to the new file.

Page 68: 052 Iczelions_Win32_Assembly_Tutorials.pdf

68

Tutorial 13: Memory Mapped FilesI'll show you what memory mapped files are and how to use them to your advantages. Using a memory mapped file is quite easy as you'll see in this tutorial.

Download the example here.

Theory:

If you examine the example in the previous tutorial closely, you'll find that it has a serious shortcoming: what if the file you want to read is larger than the allocated memory block? or what if the string you want to search for is cut off in half at the end of the memory block? The traditional answer for the first question is that you should repeatedly read in the data from the file until the end of file is encountered. The answer to the second question is that you should prepare for the special case at the end of the memory block. This is called a boundary value problem. It presents headaches to programmers and causes innumerable bugs.It would be nice if we can allocate a very large block of memory, enough to store the whole file but our program would be a resource hog. File mapping to the rescue. By using file mapping, you can think of the whole file as being already loaded into memory and you can use a memory pointer to read or write data from the file. As easy as that. No need to use memory API functions and separate File I/O API functions anymore, they are one and the same under file mapping. File mapping is also used as a means to share data between processes. Using file mapping in this way, there's no actual file involved. It's more like a reserved memory block that every process can *see*. But sharing data between processes is a delicate subject, not to be treated lightly. You have to implement process and thread synchronization else your applications will crash in very short order.We will not touch the subject of file mapping as a means to create a shared memory region in this tutorial. We'll concentrate on how to use file mapping as a means to "map" a file into memory. In fact, the PE loader uses file mapping to load executable files into memory. It is very convenient since only the necessary portions can be selectively read from the file on the disk. Under Win32, you should use file mapping as much as possible.There are some limitation to file mapping though. Once you create a memory mapped file, its size cannot be changed during that session. So file mapping is great for read-only files or file operations that don't affect the file size. That doesn't mean that you cannot use file mapping if you want to increase the file size. You can estimate the new size and create the memory mapped file based on the new size and the file will grow to that size. It's just inconvenient, that's all.Enough for the explanation. Let's dive into implementation of file mapping. In order to use file mapping, these steps must be performed:

1. call CreateFile to open the file you want to map.2. call CreateFileMapping with the file handle returned by CreateFile as one of its parameter. This function

creates a file mapping object from the file opened by CreateFile.3. call MapViewOfFile to map a selected file region or the whole file to memory. This function returns a

pointer to the first byte of the mapped file region.4. Use the pointer to read or write the file5. call UnmapViewOfFile to unmap the file.6. call CloseHandle with the handle to the mapped file as the parameter to close the mapped file.7. call CloseHandle again this time with the file handle returned by CreateFile to close the actual file.

Example:

The program listed below lets you open a file via an open file dialog box. It opens the file using file mapping, if it's successful, the window caption is changed to the name of the opened file. You can save the file in another name by select File/Save as menuitem. The program will copy the whole content of the opened file to the new file. Note that you don't have to call GlobalAlloc to allocate a memory block in this program.

.386

.model flat,stdcallWinMain proto :DWORD,:DWORD,:DWORD,:DWORDinclude \masm32\include\windows.inc

Page 69: 052 Iczelions_Win32_Assembly_Tutorials.pdf

69

include \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\comdlg32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\comdlg32.lib

.constIDM_OPEN equ 1IDM_SAVE equ 2IDM_EXIT equ 3MAXSIZE equ 260

.dataClassName db "Win32ASMFileMappingClass",0AppName db "Win32 ASM File Mapping Example",0MenuName db "FirstMenu",0ofn OPENFILENAME <>FilterString db "All Files",0,"*.*",0

db "Text Files",0,"*.txt",0,0buffer db MAXSIZE dup(0)hMapFile HANDLE 0 ; Handle to the memory mapped file, must be

;initialized with 0 because we also use it as;a flag in WM_DESTROY section too

.data?hInstance HINSTANCE ?CommandLine LPSTR ?hFileRead HANDLE ? ; Handle to the source filehFileWrite HANDLE ? ; Handle to the output filehMenu HANDLE ?pMemory DWORD ? ; pointer to the data in the source fileSizeWritten DWORD ? ; number of bytes actually written by WriteFile

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eax

Page 70: 052 Iczelions_Win32_Assembly_Tutorials.pdf

70

mov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\

ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,300,200,NULL,NULL,\

hInst,NULLmov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.IF uMsg==WM_CREATE

invoke GetMenu,hWnd ;Obtain the menu handlemov hMenu,eaxmov ofn.lStructSize,SIZEOF ofnpush hWndpop ofn.hWndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter, OFFSET FilterStringmov ofn.lpstrFile, OFFSET buffermov ofn.nMaxFile,MAXSIZE

.ELSEIF uMsg==WM_DESTROY.if hMapFile!=0

call CloseMapFile.endifinvoke PostQuitMessage,NULL

.ELSEIF uMsg==WM_COMMANDmov eax,wParam.if lParam==0

.if ax==IDM_OPENmov ofn.Flags, OFN_FILEMUSTEXIST or \

OFN_PATHMUSTEXIST or OFN_LONGNAMES or\OFN_EXPLORER or OFN_HIDEREADONLYinvoke GetOpenFileName, ADDR ofn

.if eax==TRUEinvoke CreateFile,ADDR buffer,\

GENERIC_READ ,\0,\NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\NULL

mov hFileRead,eaxinvoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULLmov hMapFile,eaxmov eax,OFFSET buffermovzx edx,ofn.nFileOffsetadd eax,edxinvoke SetWindowText,hWnd,eax

Page 71: 052 Iczelions_Win32_Assembly_Tutorials.pdf

71

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYEDinvoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

.endif.elseif ax==IDM_SAVE

mov ofn.Flags,OFN_LONGNAMES or\OFN_EXPLORER or OFN_HIDEREADONLY

invoke GetSaveFileName, ADDR ofn.if eax==TRUE

invoke CreateFile,ADDR buffer,\GENERIC_READ or GENERIC_WRITE ,\FILE_SHARE_READ or FILE_SHARE_WRITE,\NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\NULL

mov hFileWrite,eaxinvoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0mov pMemory,eaxinvoke GetFileSize,hFileRead,NULLinvoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULLinvoke UnmapViewOfFile,pMemorycall CloseMapFileinvoke CloseHandle,hFileWriteinvoke SetWindowText,hWnd,ADDR AppNameinvoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

.endif.else

invoke DestroyWindow, hWnd.endif

.endif.ELSE

invoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endp

CloseMapFile PROCinvoke CloseHandle,hMapFilemov hMapFile,0invoke CloseHandle,hFileReadret

CloseMapFile endp

end start

Analysis:

invoke CreateFile,ADDR buffer,\GENERIC_READ ,\0,\NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\NULL

When the user selects a file in the open file dialog, we call CreateFile to open it. Note that we specify GENERIC_READ to open this file for read-only access and dwShareMode is zero because we don't want some other process to modify the file during our operation.

Page 72: 052 Iczelions_Win32_Assembly_Tutorials.pdf

72

invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

Then we call CreateFileMapping to create a memory mapped file from the opened file. CreateFileMapping has the following syntax:

CreateFileMapping proto hFile:DWORD,\lpFileMappingAttributes:DWORD,\flProtect:DWORD,\dwMaximumSizeHigh:DWORD,\dwMaximumSizeLow:DWORD,\lpName:DWORD

You should know first that CreateFileMapping doesn't have to map the whole file to memory. You can use this function to map only a part of the actual file to memory. You specify the size of the memory mapped file in dwMaximumSizeHigh and dwMaximumSizeLow params. If you specify the size that 's larger than the actual file, the actual file will be expanded to the new size. If you want the memory mapped file to be the same size as the actual file, put zeroes in both params.You can use NULL in lpFileMappingAttributes parameter to have Windows creates a memory mapped file with default security attributes.flProtect defines the protection desired for the memory mapped file. In our example, we use PAGE_READONLY to allow only read operation on the memory mapped file. Note that this attribute must not contradict the attribute used in CreateFile else CreateFileMapping will fail.lpName points to the name of the memory mapped file. If you want to share this file with other process, you must provide it a name. But in our example, our process is the only one that uses this file so we ignore this parameter.

mov eax,OFFSET buffermovzx edx,ofn.nFileOffsetadd eax,edxinvoke SetWindowText,hWnd,eax

If CreateFileMapping is successful, we change the window caption to the name of the opened file. The filename with full path is stored in buffer, we want to display only the filename in the caption so we must add the value of nFileOffset member of the OPENFILENAME structure to the address of buffer.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYEDinvoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

As a precaution, we don't want the user to open multiple files at once, so we gray out the Open menu item and enable the Save menu item. EnableMenuItem is used to change the attribute of menu item.After this, we wait for the user to select File/Save as menu item or close our program. If the user chooses to close our program, we must close the memory mapped file and the actual file like the code below:

.ELSEIF uMsg==WM_DESTROY.if hMapFile!=0

call CloseMapFile.endifinvoke PostQuitMessage,NULL

In the above code snippet, when the window procedure receives the WM_DESTROY message, it checks the value of hMapFile first whether it is zero or not. If it's not zero, it calls CloseMapFile function which contains the following code:

CloseMapFile PROCinvoke CloseHandle,hMapFilemov hMapFile,0invoke CloseHandle,hFileReadret

CloseMapFile endp

Page 73: 052 Iczelions_Win32_Assembly_Tutorials.pdf

73

CloseMapFile closes the memory mapped file and the actual file so that there 'll be no resource leakage when our program exits to Windows.If the user chooses to save that data to another file, the program presents him with a save as dialog box. After he types in the name of the new file, the file is created by CreateFile function.

invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0mov pMemory,eax

Immediately after the output file is created, we call MapViewOfFile to map the desired portion of the memory mapped file into memory. This function has the following syntax:

MapViewOfFile proto hFileMappingObject:DWORD,\dwDesiredAccess:DWORD,\dwFileOffsetHigh:DWORD,\dwFileOffsetLow:DWORD,\dwNumberOfBytesToMap:DWORD

dwDesiredAccess specifies what operation we want to do to the file. In our example, we want to read the data only so we use FILE_MAP_READ.dwFileOffsetHigh and dwFileOffsetLowspecify the starting file offset of the file portion that you want to map into memory. In our case, we want to read in the whole file so we start mapping from offset 0 onwards.dwNumberOfBytesToMap specifies the number of bytes to map into memory. If you want to map the whole file (specified by CreateFileMapping), pass 0 to MapViewOfFile.After calling MapViewOfFile, the desired portion is loaded into memory. You'll be given the pointer to the memory block that contains the data from the file.

invoke GetFileSize,hFileRead,NULL

Find out how large the file is. The file size is returned in eax. If the file is larger than 4 GB, the high DWORD of the file size is stored in FileSizeHighWord. Since we don't expect to handle such large file, we can ignore it.

invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

Write the data that is mapped into memory into the output file.

invoke UnmapViewOfFile,pMemory

When we're through with the input file, unmap it from memory.

call CloseMapFileinvoke CloseHandle,hFileWrite

And close all the files.

invoke SetWindowText,hWnd,ADDR AppName

Restore the original caption text.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

Enable the Open menu item and gray out the Save As menu item.

Page 74: 052 Iczelions_Win32_Assembly_Tutorials.pdf

74

Tutorial 14: ProcessWe will learn what a process is and how to create and terminate it.

Download the example here.

Preliminary:

What is a process? I quote this definition from Win32 API reference:"A process is an executing application that consists of a private virtual address space, code, data, and other operating system resources, such as files, pipes, and synchronization objects that are visible to the process."As you can see from the definition above, a process "owns" several objects: the address space, the executing module(s), and anything that the executing modules create or open. At the minimum, a process must consist of an executing module, a private address space and a thread. Every process must have at least one thread. What's a thread? A thread is actually an execution queue. When Windows first creates a process, it creates only one thread per process. This thread usually starts execution from the first instruction in the module. If the process later needs more threads, it can explicitly create them.When Windows receives a command to create a process, it creates the private memory address space for the process and then it maps the executable file into the space. After that it creates the primary thread for the process.Under Win32, you can also create processes from your own programs by calling CreateProcess function. CreateProcess has the following syntax:

CreateProcess proto lpApplicationName:DWORD,\lpCommandLine:DWORD,\lpProcessAttributes:DWORD,\lpThreadAttributes:DWORD,\bInheritHandles:DWORD,\dwCreationFlags:DWORD,\lpEnvironment:DWORD,\lpCurrentDirectory:DWORD,\lpStartupInfo:DWORD,\lpProcessInformation:DWORD

Don't be alarmed by the number of parameters. We can ignore most of them.

lpApplicationName --> The name of the executable file with or without pathname that you want to execute. If this parameter is null, you must provide the name of the executable file in the lpCommandLine parameter.lpCommandLine --> The command line arguments to the program you want to execute. Note that if the lpApplicationName is NULL, this parameter must contain the name of the executable file too. Like this: "notepad.exe readme.txt"lpProcessAttributes and lpthreadAttributes --> Specify the security attributes for the process and the primary thread. If they're NULLs, the default security attributes are used.bInheritHandles --> A flag that specify if you want the new process to inherit all opened handles from your process.dwCreationFlags --> Several flags that determine the behavior of the process you want to created, such as, do you want to process to be created but immediately suspended so that you can examine or modify it before it runs? You can also specify the priority class of the thread(s) in the new process. This priority class is used to determine the scheduling priority of the threads within the process. Normally we use NORMAL_PRIORITY_CLASS flag.lpEnvironment --> A pointer to the environment block that contains several environment strings for the new process. If this parameter is NULL, the new process inherits the environment block from the parent process.lpCurrentDirectory --> A pointer to the string that specifies the current drive and directory for the child process. NULL if you want the child process to inherit from the parent process.lpStartupInfo --> Points to a STARTUPINFO structure that specifies how the main window for the new process should appear. The STARTUPINFO structure contains many members that specifies the appearance of the main window of the child process. If you don't want anything special, you can fill the STARTUPINFO structure with the values from the parent process by calling GetStartupInfo function.lpProcessInformation --> Points to a PROCESS_INFORMATION structure that receives identification information about the new process. The PROCESS_INFORMATION structure has the following members:

Page 75: 052 Iczelions_Win32_Assembly_Tutorials.pdf

75

PROCESS_INFORMATION STRUCThProcess HANDLE ? ; handle to the child processhThread HANDLE ? ; handle to the primary thread of the child processdwProcessId DWORD ? ; ID of the child processdwThreadId DWORD ? ; ID of the primary thread of the child process

PROCESS_INFORMATION ENDSProcess handle and process ID are two different things. A process ID is a unique identifier for the process in the system. A process handle is a value returned by Windows for use with other process-related API functions. A process handle cannot be used to identify a process since it's not unique.

After the CreateProcess call, a new process is created and the CreateProcess call return immediately. You can check if the new process is still active by calling GetExitCodeProcess function which has the following syntax:

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

If this call is successful, lpExitCode contains the termination status of the process in question. If the value in lpExitCode is equal to STILL_ACTIVE, then that process is still running.

You can forcibly terminate a process by calling TerminateProcess function. It has the following syntax:

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

You can specify the desired exit code for the process, any value you like. TerminateProcess is not a clean way to terminate a process since any dll attached to the process will not be notified that the process was terminated.

Example:

The following example will create a new process when the user selects the "create process" menu item. It will attempt to execute "msgbox.exe". If the user wants to terminate the new process, he can select the "terminate process" menu item. The program will check first if the new process is already destroyed, if it is not, the programwill call TerminateProcess function to destroy the new process.

.386

.model flat,stdcalloption casemap:noneWinMain proto :DWORD,:DWORD,:DWORD,:DWORDinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

.constIDM_CREATE_PROCESS equ 1IDM_TERMINATE equ 2IDM_EXIT equ 3

.dataClassName db "Win32ASMProcessClass",0AppName db "Win32 ASM Process Example",0MenuName db "FirstMenu",0processInfo PROCESS_INFORMATION <>programname db "msgbox.exe",0

.data?hInstance HINSTANCE ?

Page 76: 052 Iczelions_Win32_Assembly_Tutorials.pdf

76

CommandLine LPSTR ?hMenu HANDLE ?ExitCode DWORD ? ; contains the process exitcode status from GetExitCodeProcess call.

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,300,200,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwndinvoke GetMenu,hwndmov hMenu,eax.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL startInfo:STARTUPINFO.IF uMsg==WM_DESTROY

invoke PostQuitMessage,NULL.ELSEIF uMsg==WM_INITMENUPOPUP

invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode.if eax==TRUE

Page 77: 052 Iczelions_Win32_Assembly_Tutorials.pdf

77

.if ExitCode==STILL_ACTIVEinvoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYEDinvoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED

.elseinvoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED

.endif.else

invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED

.endif.ELSEIF uMsg==WM_COMMAND

mov eax,wParam.if lParam==0

.if ax==IDM_CREATE_PROCESS.if processInfo.hProcess!=0

invoke CloseHandle,processInfo.hProcessmov processInfo.hProcess,0

.endifinvoke GetStartupInfo,ADDR startInfoinvoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\

NORMAL_PRIORITY_CLASS,\NULL,NULL,ADDR startInfo,ADDR processInfo

invoke CloseHandle,processInfo.hThread.elseif ax==IDM_TERMINATE

invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode.if ExitCode==STILL_ACTIVE

invoke TerminateProcess,processInfo.hProcess,0.endifinvoke CloseHandle,processInfo.hProcessmov processInfo.hProcess,0

.elseinvoke DestroyWindow,hWnd

.endif.endif

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endpend start

Analysis:

The program creates the main window and retrieves the menu handle for future use. It then waits for the user to select a command from the menu. When the user selects "Process" menu item in the main menu, we process WM_INITMENUPOPUP message to modify the menu items inside the popup menu before it's displayed.

.ELSEIF uMsg==WM_INITMENUPOPUPinvoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode.if eax==TRUE

.if ExitCode==STILL_ACTIVEinvoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYEDinvoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED

.elseinvoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED

Page 78: 052 Iczelions_Win32_Assembly_Tutorials.pdf

78

.endif.else

invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED

.endif

Why do we want to process this message? Because we want to prepare the menu items in the popup menu before the user can see them. In our example, if the new process is not started yet, we want to enable the "start process" and gray out the "terminate process" menu items. We do the reverse if the new process is already active.We first check if the new process is still running by calling GetExitCodeProcess function with the process handle that was filled in by CreateProcess function. If GetExitCodeProcess returns FALSE, it means the process is not started yet so we gray out the "terminate process" menu item. If GetExitCodeProcess returns TRUE, we know that a new process has been started, but we have to check further if it is still running. So we compare the value in ExitCode to the value STILL_ACTIVE, if they're equal, the process is still running: we must gray out the "start process" menu item since we don't want to start several concurrent processes.

.if ax==IDM_CREATE_PROCESS.if processInfo.hProcess!=0

invoke CloseHandle,processInfo.hProcessmov processInfo.hProcess,0

.endifinvoke GetStartupInfo,ADDR startInfoinvoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\

NORMAL_PRIORITY_CLASS,\NULL,NULL,ADDR startInfo,ADDR processInfo

invoke CloseHandle,processInfo.hThread

When the user selects "start process" menu item, we first check if hProcess member of PROCESS_INFORMATION structure is already closed. If this is the first time, the value of hProcess will always be zero since we define PROCESS_INFORMATION structure in .data section. If the value of hProcess member is not 0, it means the child process has exited but we haven't closed its process handle yet. So this is the time to do it.We call GetStartupInfo function to fill in the startupinfo structure that we will pass to CreateProcess function. After that we call CreateProcess function to start the new process. Note that I haven't checked the return value of CreateProcess because it will make the example more complex. In real life, you should check the return value of CreateProcess. Immediately after CreateProcess, we close the primary thread handle returned in processInfo structure. Closing the handle doesn't mean we terminate the thread, only that we don't want to use the handle to refer to the thread from our program. If we don't close it, it will cause a resource leak.

.elseif ax==IDM_TERMINATEinvoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode.if ExitCode==STILL_ACTIVE

invoke TerminateProcess,processInfo.hProcess,0.endifinvoke CloseHandle,processInfo.hProcessmov processInfo.hProcess,0

When the user selects "terminate process" menu item, we check if the new process is still active by calling GetExitCodeProcess function. If it's still active, we call TerminateProcess function to kill the process. Also we close the child process handle since we have no need for it anymore.

Page 79: 052 Iczelions_Win32_Assembly_Tutorials.pdf

79

Tutorial 15: Multithreading ProgrammingWe will learn how to create a multithreading program in this tutorial. We also study the communication methods between the threads.

Download the example here.

Theory:

In previous tutorial, you learn the a process consists of at least one thread: the primary thread. A thread is a chain of execution. You can also create additional threads in your program. You can view multithreading as multitasking within one program. In term of implementation, a thread is a function that runs concurrently with the main program. You can run several instances of the same function or you can run several functions simultaneously depending on your requirement. Multithreading is specific to Win32, no Win16 counterpart exists.Threads run in the same process so they can access any resources in the process such as global variables, handles etc. However, each thread has its own stack so local variables in each thread are private. Each thread also owns its private register set so when Windows switches to other threads, the thread can "remember" its last status and can "resume" the task when it gains control again. This is handled internally by Windows.We can divide threads into two caterories:

1. User interface thread: This type of thread creates its own window so it receives windows messages. It can respond to the user via its own window hence the name. This type of thread is subject to Win16 Mutex rule which allows only one user interface thread in 16-bit user and gdi kernel. While a user interface thread is executing code in 16-bit user and gdi kernel, other UI threads cannot use the service of the 16-bit user and gdi kernel. Note that this Win16 Mutex is specific to Windows 95 since underneath, Windows 95 API functions thunk down to 16-bit code. Windows NT has no Win16 Mutex so the user interface threads under NT work more smoothly than under Windows 95.

2. Worker thread: This type of thread does not create a window so it cannot receive any windows message. It exists primarily to do the assigned job in the background hence the name worker thread.

I advise the following strategy when using multithreading capability of Win32: Let the primary thread do user interface stuff and the other threads do the hard work in the background. In this way, the primary thread is like a Governor, other threads are like the Governor's staff. The Governor delegates jobs to his staff while he maintains contact with the public. The Governor staff obediently performs the work and reports back to the Governor. If the Governor were to perform every task himself, he would not be able to give much attention to the public or the press. That's akin to a window which is busy doing a lengthy job in its primary thread: it doesn't respond to the user until the job is completed. Such a program can benefit from creating an additonal thread which is responsible for the lengthy job, allowing the primary thread to respond to the user's commands.We can create a thread by calling CreateThread function which has the following syntax:

CreateThread proto lpThreadAttributes:DWORD,\dwStackSize:DWORD,\lpStartAddress:DWORD,\lpParameter:DWORD,\dwCreationFlags:DWORD,\lpThreadId:DWORD

CreateThread function looks a lot like CreateProcess.lpThreadAttributes --> You can use NULL if you want the thread to have default security descriptor.dwStackSize --> specify the stack size of the thread. If you want the thread to have the same stack size as the primary thread, use NULL as this parameter.lpStartAddress--> Address of the thread function.It's the function that will perform the work of the thread. This function MUST receive one and only one 32-bit parameter and return a 32-bit value.lpParameter --> The parameter you want to pass to the thread function.dwCreationFlags --> 0 means the thread runs immediately after it's created. The opposite is CREATE_SUSPENDED flag.lpThreadId --> CreateThread function will fill the thread ID of the newly created thread at this address.

Page 80: 052 Iczelions_Win32_Assembly_Tutorials.pdf

80

If CreateThread call is sucessful, it returns the handle of the newly created thread. Otherwise, it returns NULL.The thread function runs as soon as CreateThread call is success ful unless you specify CREATE_SUSPENDED flag in dwCreationFlags. In that case, the thread is suspended until ResumeThread function is called.When the thread function returns with ret instruction, Windows calls ExitThread function for the thread function implicitly. You can call ExitThread function with in your thread function yourself but there' s little point in doing so.You can retrieve the exit code of a thread by calling GetExitCodeThread function.If you want to terminate a thread from other thread, you can call TerminateThread function. But you should use this function under extreme circumstance since this function terminates the thread immediately without giving the thread any chance to clean up after itself.

Now let's move to the communication methods between threads.There are three of them:

� Using global variables� Windows messages� Event

Threads share the process's resources including global variables so the threads can use global varibles to communicate with each other. However this method must be used with care. Thread synchronization must enter into consideration. For example, if two threads use the same structure of 10 members , what happens when Windows suddenly yanks the control from one of the thread when it was in the middle of updating the structure? The other thread will be left with an inconsistent data in the structure! Don't make any mistake, multithreading programs are harder to debug and maintain. This sort of bug seems to happen at random which is very hard to track down.You can also use Windows messages to communicate between threads. If the threads are all user interface ones, there's no problem: this method can be used as a two-way communication. All you have to do is defining one or more custom windows messages that are meaningful to the threads. You define a custom message by using WM_USER message as the base value say , you can define it like this:

WM_MYCUSTOMMSG equ WM_USER+100h

Windows will not use any value from WM_USER upward for its own messages so you can use the value WM_USER and above as your own custom message value.If one of the thread is a user interface thread and the other is a worker one, you cannot use this method as two-waycommunication since a worker thread doesn't have its own window so it doesn't have a message queue. You can use the following scheme:

User interface Thread ------> global variable(s)----> Worker threadWorker Thread ------> custom window message(s) ----> User interface Thread

In fact, we will use this method in our example.The last communication method is an event object. You can view an event object as a kind of flag. If the event object is in "unsignalled" state, the thread is dormant or sleeping, in this state, the thread doesn't receive CPU time slice. When the event object is in "signalled" state,Windows "wakes up" the thread and it starts performing the assigned task.

Example:

You should download the example zip file and run thread1.exe. Click the "Savage Calculation" menu item. This will instruct the program to perform "add eax,eax " for 600,000,000 times. Note that during that time, you cannot do anything with the main window: you cannot move it, you cannot activate its menu, etc. When the calculation is completed, a message box appears. After that the window accepts your command normally.To avoid this type of inconveniece to the user, we can move the "calculation" routine into a separate worker thread and let the primary thread continue with its user interface task. You can see that even though the main window responds more slowly than usual, it still responds

Page 81: 052 Iczelions_Win32_Assembly_Tutorials.pdf

81

.386

.model flat,stdcalloption casemap:noneWinMain proto :DWORD,:DWORD,:DWORD,:DWORDinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

.constIDM_CREATE_THREAD equ 1IDM_EXIT equ 2WM_FINISH equ WM_USER+100h

.dataClassName db "Win32ASMThreadClass",0AppName db "Win32 ASM MultiThreading Example",0MenuName db "FirstMenu",0SuccessString db "The calculation is completed!",0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?hwnd HANDLE ?ThreadID DWORD ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,300,200,NULL,NULL,\hInst,NULL

Page 82: 052 Iczelions_Win32_Assembly_Tutorials.pdf

82

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

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_CREATE_THREADmov eax,OFFSET ThreadProcinvoke CreateThread,NULL,NULL,eax,\

0,\ADDR ThreadID

invoke CloseHandle,eax.else

invoke DestroyWindow,hWnd.endif

.endif.ELSEIF uMsg==WM_FINISH

invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK.ELSE

invoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endp

ThreadProc PROC USES ecx Param:DWORDmov ecx,600000000

Loop1:add eax,eaxdec ecxjz Get_outjmp Loop1

Get_out:invoke PostMessage,hwnd,WM_FINISH,NULL,NULLret

ThreadProc ENDP

end start

Analysis:

The main program presents the user with a normal window with a menu. If the user selects "Create Thread" menu item, the program creates a thread as below:

Page 83: 052 Iczelions_Win32_Assembly_Tutorials.pdf

83

.if ax==IDM_CREATE_THREADmov eax,OFFSET ThreadProcinvoke CreateThread,NULL,NULL,eax,\

NULL,0,\ADDR ThreadID

invoke CloseHandle,eax

The above function creates a thread that will run a procedure named ThreadProc concurrently with the primary thread. After the successful call, CreateThread returns immediately and ThreadProc begins to run. Since we do not use thread handle, we should close it else there'll be some leakage of memory. Note that closing the thread handle doesn't terminate the thread. Its only effect is that we cannot use the thread handle anymore.

ThreadProc PROC USES ecx Param:DWORDmov ecx,600000000

Loop1:add eax,eaxdec ecxjz Get_outjmp Loop1

Get_out:invoke PostMessage,hwnd,WM_FINISH,NULL,NULLret

ThreadProc ENDP

As you can see, ThreadProc performs a savage calculation which takes quite a while to finish and when it finishs it posts a WM_FINISH message to the main window. WM_FINISH is our custom message defined like this:

WM_FINISH equ WM_USER+100hYou don't have to add WM_USER with 100h but it's safer to do so.The WM_FINISH message is meaningful only within our program. When the main window receives the WM_FINISH message, it respons by displaying a message box saying that the calculation is completed.You can create several threads in succession by selecting "Create Thread" several times.In this example, the communication is one-way in that only the thread can notify the main window. If you want the main thread to send commands to the worker thread, you can so as follows:

� add a menu item saying something like "Kill Thread" in the menu� a global variable which is used as a command flag. TRUE=Stop the thread, FALSE=continue the thread� Modify ThreadProc to check the value of the command flag in the loop.

When the user selects "Kill Thread" menu item, the main program will set the value TRUE in the command flag. When ThreadProc sees that the value of the command flag is TRUE, it exits the loop and returns thus ends the thread.

Page 84: 052 Iczelions_Win32_Assembly_Tutorials.pdf

84

Tutorial 16: Event Object

We will learn what an event object is and how to use it in a multithreaded program. Download the example here.

Theory:

From the previous tutorial, I demonstrated how threads communicate with a custom window message. I left out two other methods: global variable and event object. We will use both of them in this tutorial.An event object is like a switch: it has only two states: on or off. When an event object is turned on, it's in the "signalled" state. When it is turned off, it's in the "nonsignalled" state. You create an event object and put in a code snippet in the relevant threads to watch for the state of the event object. If the event object is in the nonsignalled state, the threads that wait for it will be asleep.When the threads are in wait state, they consume little CPU time.You create an event object by calling CreateEvent function which has the following syntax:

CreateEvent proto lpEventAttributes:DWORD,\bManualReset:DWORD,\bInitialState:DWORD,\lpName:DWORD

lpEventAttribute--> If you specify NULL value, the event object is created with default security descriptor.bManualReset--> If you want Windows to automatically reset the event object to nonsignalled state after WaitForSingleObject call, you must specify FALSE as this parameter. Else you must manually reset the event object with the call to ResetEvent.bInitialState--> If you want the event object to be created in the signalled state, specify TRUE as this parameter else the event object will be created in the nonsignalled state.lpName --> Pointer to an ASCIIZ string that is the name of the event object. This name is used when you want to call OpenEvent.

If the call is successful, it returns the handle to the newly created event object else it returns NULL.You can modify the state of an event object with two API calls: SetEvent and ResetEvent. SetEvent function sets the event object into signalled state. ResetEvent does the reverse.When the event object is created, you must put the call to WaitForSingleObject in the thread that wants to watch for the state of the event object. WaitForSingleObject has the following syntax:

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

hObject --> A handle to one of the synchronization object. Event object is a type of synchronization object.dwTimeout --> specify the time in milliseconds that this function will wait for the object to be in signalled state. If the specified time has passed and the event object is still in nonsignalled state, WaitForSingleObject returns the the caller. If you want to wait for the object indefinitely, you must specify the value INFINITE as this parameter.

Example:

The example below displays a window waiting for the user to select a command from the menu. If the user selects "run thread", the thread starts the savage calculation. When it's finished, a message box appears informing the user that the job is done. During the time that the thread is running, the user can select "stop thread" to stop the thread.

.386

.model flat,stdcalloption casemap:noneWinMain proto :DWORD,:DWORD,:DWORD,:DWORDinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.inc

Page 85: 052 Iczelions_Win32_Assembly_Tutorials.pdf

85

includelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

.constIDM_START_THREAD equ 1IDM_STOP_THREAD equ 2IDM_EXIT equ 3WM_FINISH equ WM_USER+100h

.dataClassName db "Win32ASMEventClass",0AppName db "Win32 ASM Event Example",0MenuName db "FirstMenu",0SuccessString db "The calculation is completed!",0StopString db "The thread is stopped",0EventStop BOOL FALSE

.data?hInstance HINSTANCE ?CommandLine LPSTR ?hwnd HANDLE ?hMenu HANDLE ?ThreadID DWORD ?ExitCode DWORD ?hEventStart HANDLE ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,OFFSET MenuNamemov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\

ADDR AppName,\WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,300,200,NULL,NULL,\hInst,NULL

Page 86: 052 Iczelions_Win32_Assembly_Tutorials.pdf

86

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwndinvoke GetMenu,hwndmov hMenu,eax.WHILE TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.IF uMsg==WM_CREATE

invoke CreateEvent,NULL,FALSE,FALSE,NULLmov hEventStart,eaxmov eax,OFFSET ThreadProcinvoke CreateThread,NULL,NULL,eax,\

NULL,0,\ADDR ThreadID

invoke CloseHandle,eax.ELSEIF uMsg==WM_DESTROY

invoke PostQuitMessage,NULL.ELSEIF uMsg==WM_COMMAND

mov eax,wParam.if lParam==0

.if ax==IDM_START_THREADinvoke SetEvent,hEventStartinvoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYEDinvoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED

.elseif ax==IDM_STOP_THREADmov EventStop,TRUEinvoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED

.elseinvoke DestroyWindow,hWnd

.endif.endif

.ELSEIF uMsg==WM_FINISHinvoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK

.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.ENDIFxor eax,eaxret

WndProc endp

ThreadProc PROC USES ecx Param:DWORDinvoke WaitForSingleObject,hEventStart,INFINITEmov ecx,600000000.WHILE ecx!=0

.if EventStop!=TRUEadd eax,eaxdec ecx

.else

Page 87: 052 Iczelions_Win32_Assembly_Tutorials.pdf

87

invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OKmov EventStop,FALSEjmp ThreadProc

.endif.ENDWinvoke PostMessage,hwnd,WM_FINISH,NULL,NULLinvoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLEDinvoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYEDjmp ThreadProcret

ThreadProc ENDPend start

Analysis:

In this example, I demonstrate another thread technique.

.IF uMsg==WM_CREATEinvoke CreateEvent,NULL,FALSE,FALSE,NULLmov hEventStart,eaxmov eax,OFFSET ThreadProcinvoke CreateThread,NULL,NULL,eax,\

NULL,0,\ADDR ThreadID

invoke CloseHandle,eax

You can see that I create the event object and the thread during the processing of WM_CREATE message. I create the event object in the nonsignalled state with automatic reset. After the event object is created, I create the thread. However the thread doesn't run immediately because it waits for the event object to be in the signalled state as the code below:

ThreadProc PROC USES ecx Param:DWORDinvoke WaitForSingleObject,hEventStart,INFINITEmov ecx,600000000

The first line of the thread procedure is the call to WaitForSingleObject. It waits infinitely for the signalled state of the event object before it returns. This means that even when the thread is created, we put it into a dormant state.When the user selects "run thread" command from the menu, we set the event object into signalled state as below:

.if ax==IDM_START_THREADinvoke SetEvent,hEventStart

The call to SetEvent turns the event object into the signalled state which in turn makes the WaitForSingleObject call in the thread procedure return and the thread starts running. When the user selects "stop thread" command, we set the value of the global variable "EventStop" to TRUE.

.if EventStop==FALSEadd eax,eaxdec ecx

.elseinvoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OKmov EventStop,FALSEjmp ThreadProc

.endif

Page 88: 052 Iczelions_Win32_Assembly_Tutorials.pdf

88

This stops the thread and jumps to the WaitForSingleObject call again. Note that we don't have to manually reset the event object into nonsignalled state because we specify the bManualReset parameter of the CreateEvent call as FALSE.

Page 89: 052 Iczelions_Win32_Assembly_Tutorials.pdf

89

Tutorial 17: Dynamic Link LibrariesIn this tutorial, we will learn about DLLs, what are they and how to create them.You can download the example here.

Theory:

If you program long enough, you'll find that the programs you wrote usually have some code routines in common. It's such a waste of time to rewrite them everytime you start coding new programs. Back in the old days of DOS, programmers store those commonly used routines in one or more libraries. When they want to use the functions, they just link the library to the object file and the linker extracts the functions from the library and inserts them into the final executable file. This process is called static linking. C runtime libraries are good examples. The drawback of this method is that you have identical functions in every program that calls them. Your disk space is wasted storing several identical copies of the functions. But for DOS programs, this method is quite acceptable since there is usually only one program that's active in memory. So there is no waste of precious memory.Under Windows, the situation becomes much more critical because you can have several programs running simultaneously. Memory will be eat up quickly if your program is quite large. Windows has a solution for this type of problem: dynamic link libraries. A dynamic link library is a kind of common pool of functions. Windows will not load several copies of a DLL into memory so even if there are many instances of your program running at the same time, there'll be only one copy of the DLL that program uses in memory. And I should clarify this point a bit. In reality, all processes that use the same dll will have their own copies of that dll. It will look like there are many copies of the DLL in memory. But in reality, Windows does it magic with paging and all processes share the same DLL code.So in physical memory, there is only one copy of DLL code. However, each process will have its own unique data section of the DLL.The program links to a DLL at runtime unlike the old static library. That's why it's called dynamic link library. You can also unload a DLL at runtime as well when you don't need it. If that program is the only one that uses the DLL, it'll be unloaded from memory immediately. But if the DLL is still used by some other program, the DLL remains in memory until the last program that uses its service unloads it.However, the linker has a more difficult job when it performs address fixups for the final executable file. Since it cannot "extract" the functions and insert them into the final executable file, somehow it must store enough information about the DLL and functions into the final execuable file for it to be able to locate and load the correct DLL at runtime.That's where import library comes in. An import library contains the information about the DLL it represents. The linker can extract the info it needs from the import libraries and stuff it into the executable file. When Windows loader loads the program into memory, it sees that the program links to a DLL so it searches for that DLL and maps it into the address space of the process as well and performs the address fixups for the calls to the functions in the DLL.You may choose to load the DLL yourself without relying on Windows loader. This method has its pros and cons:

� It doesn't need an import library so you can load and use any DLL even if it comes with no import library. However, you still have to know about the functions inside it, how many parameters they take and the likes.

� When you let the loader load the DLL for your program, if the loader cannot find the DLL it will report "A required .DLL file, xxxxx.dll is missing" and poof! your program doesn't have a chance to run even if that DLL is not essential to its operation. If you load the DLL yourself, when the DLL cannot be found and it's not essential to the operation, your program can just tell the user about the fact and go on.

� You can call *undocumented* functions that are not included in the import libraries. Provided that you know enough info about the functions.

� If you use LoadLibrary, you have to call GetProcAddress for every function that you want to call. GetProcAddress retrieves the entrypoint address of a function in a particular DLL. So your code might be a little bit larger and slower but by not much.

Seeing the advantages/disadvantages of LoadLibrary call, we go into detail how to create a DLL now.The following code is the DLL skeleton.

Page 90: 052 Iczelions_Win32_Assembly_Tutorials.pdf

90

;--------------------------------------------------------------------------------------; DLLSkeleton.asm;--------------------------------------------------------------------------------------.386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

.data

.codeDllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD

mov eax,TRUEret

DllEntry Endp;---------------------------------------------------------------------------------------------------; This is a dummy function; It does nothing. I put it here to show where you can insert functions into; a DLL.;----------------------------------------------------------------------------------------------------TestFunction proc

retTestFunction endp

End DllEntry

;-------------------------------------------------------------------------------------; DLLSkeleton.def;-------------------------------------------------------------------------------------LIBRARY DLLSkeletonEXPORTS TestFunction

The above program is the DLL skeleton. Every DLL must have an entrypoint function. Windows will call the entrypoint function everytime that:

� The DLL is first loaded� The DLL is unloaded� A thread is created in the same process� A thread is destroyed in the same process

DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORDmov eax,TRUEret

DllEntry Endp

You can name the entrypoint function anything you wish so long as you have a matching END <Entrypoint function name>. This function takes three parameters, only the first two of which are important.hInstDLL is the module handle of the DLL. It's not the same as the instance handle of the process. You should keep this value if you need to use it later. You can't obtain it again easily.reason can be one of the four values:

� DLL_PROCESS_ATTACH The DLL receives this value when it is first injected into the process address space. You can use this opportunity to do initialization.

Page 91: 052 Iczelions_Win32_Assembly_Tutorials.pdf

91

� DLL_PROCESS_DETACH The DLL receives this value when it is being unloaded from the process address space. You can use this opportunity to do some cleanup such as deallocate memory and so on.

� DLL_THREAD_ATTACH The DLL receives this value when the process creates a new thread.� DLL_THREAD_DETACH The DLL receives this value when a thread in the process is destroyed.

You return TRUE in eax if you want the DLL to go on running. If you return FALSE, the DLL will not be loaded. For example, if your initialization code must allocate some memory and it cannot do that successfully, the entrypoint function should return FALSE to indicate that the DLL cannot run.You can put your functions in the DLL following the entrypoint function or before it. But if you want them to be callable from other programs, you must put their names in the export list in the module definition file (.def).A DLL needs a module definition file in its developmental stage. We will take a look at it now.

LIBRARY DLLSkeletonEXPORTS TestFunction

Normally you must have the first line.The LIBRARY statement defines the internal module name of the DLL. You should match it with the filename of the DLL.The EXPORTS statement tells the linker which functions in the DLL are exported, that is, callable from other programs. In the example, we want other modules to be able to call TestFunction, so we put its name in the EXPORTS statement.Another change is in the linker switch. You must put /DLL switch and /DEF:<your def filename> in your linker switches like this:

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj

The assembler switches are the same, namely /c /coff /Cp. So after you link the object file, you will get .dll and .lib. The .lib is the import library which you can use to link to other programs that use the functions in the DLL.Next I'll show you how to use LoadLibrary to load a DLL.

;---------------------------------------------------------------------------------------------; UseDLL.asm;----------------------------------------------------------------------------------------------.386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\user32.lib

.dataLibName db "DLLSkeleton.dll",0FunctionName db "TestHello",0DllNotFound db "Cannot load library",0AppName db "Load Library",0FunctionNotFound db "TestHello function not found",0

.data?hLib dd ? ; the handle of the library (DLL)TestHelloAddr dd ? ; the address of the TestHello function

.codestart:

invoke LoadLibrary,addr LibName;---------------------------------------------------------------------------------------------------------; Call LoadLibrary with the name of the desired DLL. If the call is successful; it will return the handle to the library (DLL). If not, it will return NULL

Page 92: 052 Iczelions_Win32_Assembly_Tutorials.pdf

92

; You can pass the library handle to GetProcAddress or any function that requires; a library handle as a parameter.;------------------------------------------------------------------------------------------------------------

.if eax==NULLinvoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK

.elsemov hLib,eaxinvoke GetProcAddress,hLib,addr FunctionName

;-------------------------------------------------------------------------------------------------------------; When you get the library handle, you pass it to GetProcAddress with the address; of the name of the function in that DLL you want to call. It returns the address; of the function if successful. Otherwise, it returns NULL; Addresses of functions don't change unless you unload and reload the library.; So you can put them in global variables for future use.;-------------------------------------------------------------------------------------------------------------

.if eax==NULLinvoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK

.elsemov TestHelloAddr,eaxcall [TestHelloAddr]

;-------------------------------------------------------------------------------------------------------------; Next, you can call the function with a simple call with the variable containing; the address of the function as the operand.;-------------------------------------------------------------------------------------------------------------

.endifinvoke FreeLibrary,hLib

;-------------------------------------------------------------------------------------------------------------; When you don't need the library anymore, unload it with FreeLibrary.;-------------------------------------------------------------------------------------------------------------

.endifinvoke ExitProcess,NULL

end start

So you can see that using LoadLibrary is a little more involved but it's also more flexible.

Page 93: 052 Iczelions_Win32_Assembly_Tutorials.pdf

93

Tutorial 18: Common ControlsWe will learn what common controls are and how to use them. This tutorial will be a quick introduction to them only.Download the example source code here

.

Theory:

Windows 95 comes with several user-interface enhancements over Windows 3.1x. They make the GUI richer. Several of them are in widely used before Windows 95 hit the shelf, such as status bar, toolbars etc. Programmers have to code them themselves. Now Microsoft has included them with Windows 9x and NT. We will learn about them here.These are the new controls:

� Toolbar� Tooltip� Status bar� Property sheet� Property page� Tree view� List view� Animation� Drag list� Header� Hot-key� Image list� Progress bar� Right edit� Tab� Trackbar� Up-down

Since there are many of them, loading them all into memory and registering them would be a waste of resource. All of them, with the exception of rich edit control, are stored in comctl32.dll with applications can load when they want to use the controls. Rich edit control resides in its own dll, richedXX.dll, because it's very complicated and hence larger than its brethren.You can load comctl32.dll by including a call to InitCommonControls in your program. InitCommonControls is a function in comctl32.dll, so referring to it anywhere in your code will make PE loader load comctl32.dll when your program runs.You don't have to execute it, just include it in your code somewhere. This function does NOTHING! Its only instruction is "ret". Its sole purpose is to include reference to comctl32.dll in the import section so that PE loader will load it whenever the program is loaded. The real workhorse is the DLL entrypoint function which registers all common control classes when the dll is loaded. Common controls are created based on those classes just like other child window controls such as edit, listbox etc.Rich edit is another matter entirely. If you want to use it, you have to call LoadLibrary to load it explicitly and call FreeLibrary to unload it.Now we learn how to create them. You can use a resource editor to incorporate them into dialog boxes or you can create them yourself. Nearly all common controls are created by calling CreateWindowEx or CreateWindow, passing it the name of the control class. Some common controls have specific creation functions , however, they are just wrappers around CreateWindowEx to make it easier to create those controls. Existing specific creation functions are listed below:

� CreateToolbarEx� CreateStatusWindow� CreatePropertySheetPage� PropertySheet� ImageList_Create

Page 94: 052 Iczelions_Win32_Assembly_Tutorials.pdf

94

In order to create common controls, you have to know their class names. They are listed below:

Class Name Common ControlToolbarWindow32 Toolbartooltips_class32 Tooltipmsctls_statusbar32 Status barSysTreeView32 Tree viewSysListView32 List viewSysAnimate32 AnimationSysHeader32 Headermsctls_hotkey32 Hot-keymsctls_progress32 Progress barRICHEDIT Rich editmsctls_updown32 Up-downSysTabControl32 Tab

Property sheets and property pages and image list control have their own specific creation functions. Drag list control are souped-up listbox so it doesn't have its own class. The above class names are verified by checking resource script generated by Visual C++ resource editor. They differ from the class names listed by Borland's win32 api reference and Charles Petzold's Programming Windows 95. The above list is the accurate one.Those common controls can use general window styles such as WS_CHILD etc. They also have their own specific styles such as TVS_XXXXX for tree view control, LVS_xxxx for list view control, etc. Win32 api reference is your best friend in this regard.Now that we know how to create common controls, we can move on to communication method between common controls and their parent. Unlike child window controls, common controls don't communicate with the parent via WM_COMMAND. Instead they send WM_NOTIFY messages to the parent window when some interesting events occur with the common controls. The parent can control the children by sending messages to them. There are also many new messages for those new controls. You should consult your win32 api reference for more detail.Let's examine progress bar and status bar controls in the following example.

Sample code:.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\comctl32.incincludelib \masm32\lib\comctl32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.constIDC_PROGRESS equ 1 ; control IDsIDC_STATUS equ 2IDC_TIMER equ 3

.dataClassName db "CommonControlWinClass",0AppName db "Common Control Demo",0ProgressClass db "msctls_progress32",0 ; the class name of the progress bar

Page 95: 052 Iczelions_Win32_Assembly_Tutorials.pdf

95

Message db "Finished!",0TimerID dd 0

.data?hInstance HINSTANCE ?hwndProgress dd ?hwndStatus dd ?CurrentStep dd ?.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULTinvoke ExitProcess,eaxinvoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_APPWORKSPACEmov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\

CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eax.while TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endwmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.if uMsg==WM_CREATE

invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\WS_CHILD+WS_VISIBLE,100,\200,300,20,hWnd,IDC_PROGRESS,\hInstance,NULL

Page 96: 052 Iczelions_Win32_Assembly_Tutorials.pdf

96

mov hwndProgress,eaxmov eax,1000 ; the lParam of PBM_SETRANGE message contains the rangemov CurrentStep,eaxshl eax,16 ; the high range is in the high wordinvoke SendMessage,hwndProgress,PBM_SETRANGE,0,eaxinvoke SendMessage,hwndProgress,PBM_SETSTEP,10,0invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUSmov hwndStatus,eaxinvoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timermov TimerID,eax

.elseif uMsg==WM_DESTROYinvoke PostQuitMessage,NULL.if TimerID!=0

invoke KillTimer,hWnd,TimerID.endif

.elseif uMsg==WM_TIMER ; when a timer event occursinvoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; step up the progress in the progress barsub CurrentStep,10.if CurrentStep==0

invoke KillTimer,hWnd,TimerIDmov TimerID,0invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Messageinvoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATIONinvoke SendMessage,hwndStatus,SB_SETTEXT,0,0invoke SendMessage,hwndProgress,PBM_SETPOS,0,0

.endif.else

invoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endpend start

Analysis:invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULTinvoke ExitProcess,eaxinvoke InitCommonControls

I deliberately put InitCommonControls after ExitProcess to demonstrate that InitCommonControls is just there for putting a reference to comctl32.dll in the import section. As you can see, the common controls work even if InitCommonControls doesn't execute.

.if uMsg==WM_CREATEinvoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\

WS_CHILD+WS_VISIBLE,100,\200,300,20,hWnd,IDC_PROGRESS,\hInstance,NULL

mov hwndProgress,eaxHere is where we create the common control. Note that this CreateWindowEx call contains hWnd as the parent window handle. It also specifies a control ID for identifying this control. However, since we have the control's window handle, this ID is not used. All child window controls must have WS_CHILD style.

mov eax,1000mov CurrentStep,eaxshl eax,16invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eaxinvoke SendMessage,hwndProgress,PBM_SETSTEP,10,0

After the progress bar is created, we can set its range. The default range is from 0 to 100. If you are not satisfied with it, you can specify your own range with PBM_SETRANGE message. lParam of this message contains the range, the maximum range is in the high word and the minimum one is in the low word. You can specify how much

Page 97: 052 Iczelions_Win32_Assembly_Tutorials.pdf

97

a step takes by using PBM_SETSTEP message. The example sets it to 10 which means that when you send a PBM_STEPIT message to the progress bar, the progress indicator will rise by 10. You can also set your own indicator level by sending PBM_SETPOS messages. This message gives you tighter control over the progress bar.

invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUSmov hwndStatus,eaxinvoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timermov TimerID,eax

Next, we create a status bar by calling CreateStatusWindow. This call is easy to understand so I'll not comment on it. After the status window is created, we create a timer. In this example, we will update the progress bar at a regular interval of 100 ms so we must create a timer control. Below is the function prototype of SetTimer.

SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORDhWnd : Parent window handleTimerID : a nonzero timer identifier. You can create your own identifier.TimerInterval : the timer interval in milliseconds that must pass before the timer calls the timer procedure or sends a WM_TIMER messagelpTimerProc : the address of the timer function that will be called when the time interval expires. If this parameter is NULL, the timer will send WM_TIMER message to the parent window instead.

If this call is successful, it will return the TimerID. If it failed, it returns 0. This is why the timer identifer must be a nonzero value.

.elseif uMsg==WM_TIMERinvoke SendMessage,hwndProgress,PBM_STEPIT,0,0sub CurrentStep,10.if CurrentStep==0

invoke KillTimer,hWnd,TimerIDmov TimerID,0invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Messageinvoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATIONinvoke SendMessage,hwndStatus,SB_SETTEXT,0,0invoke SendMessage,hwndProgress,PBM_SETPOS,0,0

.endifWhen the specified time interval expires, the timer sends a WM_TIMER message. You will put your code that will be executed here. In this example, we update the progress bar and then check if the maximum limit has been reached. If it has, we kill the timer and then set the text in the status window with SB_SETTEXT message. A message box is displayed and when the user clicks OK, we clear the text in the status bar and the progress bar.

Page 98: 052 Iczelions_Win32_Assembly_Tutorials.pdf

98

Tutorial 19: Tree View ControlIn this tutorial, we will learn how to use tree view control. Moreover, we will also learn how to do drag and drop under tree view control and how to use an image list with it.Download the example here.

Theory:

A tree view control is a special kind of window that represents objects in hierarchical order. An example of it is the left pane of Windows Explorer. You can use this control to show relationships between objects.You can create a tree view control by calling CreateWindowEx, passing "SysTreeView32" as the class name or you can incorporate it into a dialog box. Don't forget to put InitCommonControls call in your code.There are several styles specific to the tree view control. These three are the ones mostly used.

TVS_HASBUTTONS == Displays plus (+) and minus (-) buttons next to parent items. The user clicks the buttons to expand or collapse a parent item's list of child items. To include buttons with items at the root of the tree view, TVS_LINESATROOT must also be specified.TVS_HASLINES == Uses lines to show the hierarchy of items.TVS_LINESATROOT == Uses lines to link items at the root of the tree-view control. This value is ignored if TVS_HASLINES is not also specified.

The tree view control, like other common controls, communicates with the parent window via messages. The parent window can send various messages to it and the tree view control can send "notification" messages to its parent window. In this regard, the tree view control is not different that any window.When something interesting occurs to it, it sends a WM_NOTIFY message to the parent window with accompanying information.

WM_NOTIFYwParam == Control ID, this value is not guaranteed to be unique so we don't use it.

Instead, we use hwndFrom or IDFrom member of the NMHDR structurepointed to by lParam

lParam == Pointer to NMHDR structure. Some controls may pass a pointer to largerstructure but it must have a NMHDR structure as its first member.That is, when you have lParam, you can be sure that it points to aNMHDR structure at least.

Next we will examine NMHDR structure.NMHDR struct DWORD

hwndFrom DWORD ?idFrom DWORD ?code DWORD ?

NMHDR endshwndFrom is the window handle of the control that sends this WM_NOTIFY message.idFrom is the control ID of the control that sends this WM_NOTIFY message.code is the actual message the control wants to send to the parent window.Tree view notifications are those with TVN_ at the beginning of the name. Tree view messages are those with TVM_, like TVM_CREATEDRAGIMAGE. The tree view control sends TVN_xxxx in the code member of NMHDR. The parent window can send TVM_xxxx to control it.

Adding items to a tree view controlAfter you create a tree view control, you can add items to it. You can do this by sending TVM_INSERTITEM to it.

TVM_INSERTITEMwParam = 0;lParam = pointer to a TV_INSERTSTRUCT;

You should know some terminology at this point about the relationship between items in the tree view control.An item can be parent, child, or both at the same time. A parent item is the item that has some other subitem(s) associated with it. At the same time, the parent item may be a child of some other item. An item without a parent is called a root item. There can be many root items in a tree view control. Now we examine TV_INSERTSTRUCTstructure

TV_INSERTSTRUCT STRUCT DWORDhParent DWORD ?hInsertAfter DWORD ?

Page 99: 052 Iczelions_Win32_Assembly_Tutorials.pdf

99

ITEMTYPE <>TV_INSERTSTRUCT ENDS

hParent = Handle to the parent item. If this member is the TVI_ROOT value or NULL, the item is inserted at the root of the tree-view control.hInsertAfter = Handle to the item after which the new item is to be inserted or one of the following values:

� TVI_FIRST ==> Inserts the item at the beginning of the list.� TVI_LAST ==> Inserts the item at the end of the list.� TVI_SORT ==> Inserts the item into the list in alphabetical order.

ITEMTYPE UNIONitemex TVITEMEX <>item TVITEM <>

ITEMTYPE ENDSWe will use only TVITEM here.

TV_ITEM STRUCT DWORDimask DWORD ?hItem DWORD ?state DWORD ?stateMask DWORD ?pszText DWORD ?cchTextMax DWORD ?iImage DWORD ?iSelectedImage DWORD ?cChildren DWORD ?lParam DWORD ?

TV_ITEM ENDSThis structure is used to send and receive info about a tree view item, depending on messages. For example, with TVM_INSERTITEM, it is used to specify the attribute of the item to be inserted into the tree view control. With TVM_GETITEM, it'll be filled with information about the selected tree view item.imask is used to specify which member(s) of the TV_ITEM structure is (are) valid. For example, if the value in imask is TVIF_TEXT, it means only the pszText member is valid. You can combine several flags together.hItem is the handle to the tree view item. Each item has its own handle, like a window handle. If you want to do something with an item, you must select it by its handle.pszText is the pointer to a null-terminated string that is the label of the tree view item.cchTextMax is used only when you want to retrieve the label of the tree view item. Because you will supply the pointer to the buffer in pszText, Windows has to know the size of the provided buffer. You have to give the size of the buffer in this member.iImage and iSelectedImage refers to the index into an image list that contains the images to be shown when the item is not selected and when it's selected. If you recall Windows Explorer left pane, the folder images are specified by these two members.In order to insert an item into the tree view control, you must at least fill in the hParent, hInsertAfter and you should fill imask and pszText members as well.

Adding images to the tree view controlIf you want to put an image to the left of the tree view item's label, you have to create an image list and associate it with the tree view control. You can create an image list by calling ImageList_Create.

ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, \cInitial:DWORD, cGrow:DWORD

This function returns the handle to an empty image list if successful.cx == width of each image in this image list, in pixels.cy == height of each image in this image list, in pixels. Every image in an image list must be equal to each other in size. If you specify a large bitmap, Windows will use cx and cy to *cut* it into several pieces. So you should prepare your own image as a strip of pictures with identical dimensions.flags == specify the type of images in this image list whether they are color or monochrome and their color depth. Consult your win32 api reference for more detailcInitial == The number of images that this image list will initially contain. Windows will use this info to allocate memory for the images.cGrow == Amount of images by which the image list can grow when the system needs to resize the list to make

Page 100: 052 Iczelions_Win32_Assembly_Tutorials.pdf

100

room for new images. This parameter represents the number of new images that the resized image list can contain.An image list is not a window! It's only an image deposit for use by other windows.After an image list is created, you can add images to it by calling ImageList_Add

ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORDThis function returns -1 if unsuccessful.himl == the handle of the image list you want to add images to. It is the value returned by a successful call to ImageList_CreatehbmImage == the handle to the bitmap to be added to the image list. You usually store the bitmap in the resource and load it with LoadBitmap call. Note that you don't have to specify the number of images contained in this bitmap because this information is inferred from cx and cy parameters passed to ImageList_Create call.hbmMask == Handle to the bitmap that contains the mask. If no mask is used with the image list, this parameter is ignored.Normally, we will add only two images to the image list for use with the tree view control: one that is used when thetree view item is not selected, and the other when the item is selected.When the image list is ready, you associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control.

TVM_SETIMAGELISTwParam = type of image list to set. There are two choices:

o TVSIL_NORMAL Set the normal image list, which contains the selected and unselected images for the tree-view item.

o TVSIL_STATE Set the state image list, which contains the images for tree-view items that are in a user-defined state.

lParam = Handle to the image list

Retrieve the info about tree view itemYou can retrieve the information about a tree view item by sending TVM_GETITEM message.

TVM_GETITEMwParam = 0lParam = pointer to the TV_ITEM structure to be filled with the information

Before you send this message, you must fill imask member with the flag(s) that specifies which member(s) of TV_ITEM you want Windows to fill. And most importantly, you must fill hItem with the handle to the item you want to get information from. And this poses a problem: How can you know the handle of the item you want to retrieve info from? Will you have to store all tree view handles?The answer is quite simple: you don't have to. You can send TVM_GETNEXTITEM message to the tree view control to retrieve the handle to the tree view item that has the attribute(s) you specified. For example, you can query the handle of the first child item, the root item, the selected item, and so on.

TVM_GETNEXTITEMwParam = flaglParam = handle to a tree view item (only necessary for some flag values)

The value in wParam is very important so I present all the flags below:

o TVGN_CARET Retrieves the currently selected item.o TVGN_CHILD Retrieves the first child item of the item specified by the hitem parametero TVGN_DROPHILITE Retrieves the item that is the target of a drag-and-drop operation.o TVGN_FIRSTVISIBLE Retrieves the first visible item.o TVGN_NEXT Retrieves the next sibling item.o TVGN_NEXTVISIBLE Retrieves the next visible item that follows the specified item. The specified

item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.

o TVGN_PARENT Retrieves the parent of the specified item.o TVGN_PREVIOUS Retrieves the previous sibling item.o TVGN_PREVIOUSVISIBLE Retrieves the first visible item that precedes the specified item. The

specified item must be visible. Use the TVM_GETITEMRECT message to determine whether an item is visible.

o TVGN_ROOT Retrieves the topmost or very first item of the tree-view control.

Page 101: 052 Iczelions_Win32_Assembly_Tutorials.pdf

101

You can see that, you can retrieve the handle to the tree view item you are interested in from this message. SendMessage returns the handle to the tree view item if successful. You can then fill the returned handle into hItem member of TV_ITEM to be used with TVM_GETITEM message.

Drag and Drop Operation in tree view controlThis part is the reason I decided to write this tutorial. When I tried to follow the example in win32 api reference (the win32.hlp from InPrise), I was very frustrated because the vital information is lacking. From trial and error, I finally figured out how to implement drag & drop in a tree view control and I don't want anyone to walk the same path as myself.Below is the steps in implementing drag & drop operation in a tree view control.

1. When the user tries to drag an item, the tree view control sends TVN_BEGINDRAG notification to the parent window. You can use this opportunity to create a drag image which is the image that will be used to represent the item while it's being dragged. You can send TVM_CREATEDRAGIMAGE to the tree view control to tell it to create a default drag image from the image that is currently used by the item that will be dragged. The tree view control will create an image list with just one drag image and return the handle to that image list to you.

2. After the drag image is created, you specify the hotspot of the drag image by calling ImageList_BeginDrag.

ImageList_BeginDrag PROTO himlTrack:DWORD, \iTrack:DWORD , \dxHotspot:DWORD, \dyHotspot:DWORD

himlTrack is the handle to the image list that contains the drag image.iTrack is the index into the image list that specifies the drag imagedxHotspot specifies the relative distance of the hotspot in horizontal plance in the drag image since this image will be used in place of the mouse cursor, so we need to specify which part of the image is the hotspot.dyHotspot specifies the relative distance of the hotspot in the vertical plane.Normally, iTrack would be 0 if you tell the tree view control to create the drag image for you. and dxHotspot and dyHotspot can be 0 if you want the left upper corner of the drag image to be the hotspot.

3. When the drag image is ready to be displayed, we call ImageList_DragEnter to display the drag image in the window.

ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORDhwndLock is the handle of the window that owns the drag image. The drag image will not be able to move outside that window.x and y are the x-and y-coordinate of the place where the drag image should be initially displayed. Note that these values are relative to the left upper corner of the window, not the client area.

4. Now that the drag image is displayed on the window, you will have to support the drag operation in the tree view control. However, there is a little problem here. We have to monitor the drag path with WM_MOUSEMOVE and the drop location with WM_LBUTTONUP messages. However, if the drag image is over some other child windows, the parent window will never receive any mouse message. The solution is to capture the mouse input with SetCapture. Using the call, the mouse messages will be directed to the specified window regardless of where the mouse cursor is.

5. Within WM_MOUSEMOVE handler, you will update the drag path with ImageList_DragMove call. This function moves the image that is being dragged during a drag-and-drop operation. Furthermore, if you so desire, you can hilite the item that the drag image is over by sending TVM_HITTEST to check if the drag image is over some item. If it is, you can send TVM_SELECTITEM with TVGN_DROPHILITE flag to hilite that item. Note that before sending TVM_SELECTITEM message, you must hide the drag image first else your drag image will leave ugly traces. You can hide the drag image by calling ImageList_DragShowNolock and, after the hilite operation is finished, call ImageList_DragShowNolock again to show the drag image.

Page 102: 052 Iczelions_Win32_Assembly_Tutorials.pdf

102

6. When the user releases the left mouse button, you must do several things. If you hilite an item, you must un-hilite it by sending TVM_SELECTITEM with TVGN_DROPHILITE flag again, but this time, lParam MUST be zero. If you don't un-hilite the item, you will get a strange effect: when you select some other item, that item will be enclosed by a rectangle but the hilite will still be on the last hilited item. Next, you must call ImageList_DragLeave followed by ImageList_EndDrag. You must release the mouse by callingReleaseCapture. If you create an image list, you must destroy it by calling ImageList_Destroy. After that, you can go on with what your program wants to do when the drag & drop operation is completed.

Code sample:

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\comctl32.incinclude \masm32\include\gdi32.incincludelib \masm32\lib\gdi32.libincludelib \masm32\lib\comctl32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD.constIDB_TREE equ 4006 ; ID of the bitmap resource.dataClassName db "TreeViewWinClass",0AppName db "Tree View Demo",0TreeViewClass db "SysTreeView32",0Parent db "Parent Item",0Child1 db "child1",0Child2 db "child2",0DragMode dd FALSE ; a flag to determine if we are in drag mode

.data?hInstance HINSTANCE ?hwndTreeView dd ? ; handle of the tree view controlhParent dd ? ; handle of the root tree view itemhImageList dd ? ; handle of the image list used in the tree view controlhDragImageList dd ? ; handle of the image list used to store the drag image

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULTinvoke ExitProcess,eaxinvoke InitCommonControls

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULL

Page 103: 052 Iczelions_Win32_Assembly_Tutorials.pdf

103

mov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_APPWORKSPACEmov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\

CW_USEDEFAULT,200,400,NULL,NULL,\hInst,NULL

mov hwnd,eax.while TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endwmov eax,msg.wParamret

WinMain endp

WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL tvinsert:TV_INSERTSTRUCTLOCAL hBitmap:DWORDLOCAL tvhit:TV_HITTESTINFO.if uMsg==WM_CREATE

invoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\0,200,400,hWnd,NULL,\hInstance,NULL ; Create the tree view control

mov hwndTreeView,eaxinvoke ImageList_Create,16,16,ILC_COLOR16,2,10 ; create the associated image listmov hImageList,eaxinvoke LoadBitmap,hInstance,IDB_TREE ; load the bitmap from the resourcemov hBitmap,eaxinvoke ImageList_Add,hImageList,hBitmap,NULL ; Add the bitmap into the image listinvoke DeleteObject,hBitmap ; always delete the bitmap resourceinvoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageListmov tvinsert.hParent,NULLmov tvinsert.hInsertAfter,TVI_ROOTmov tvinsert.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGEmov tvinsert.item.pszText,offset Parentmov tvinsert.item.iImage,0mov tvinsert.item.iSelectedImage,1invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsertmov hParent,eaxmov tvinsert.hParent,eaxmov tvinsert.hInsertAfter,TVI_LASTmov tvinsert.item.pszText,offset Child1invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsertmov tvinsert.item.pszText,offset Child2invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert

Page 104: 052 Iczelions_Win32_Assembly_Tutorials.pdf

104

.elseif uMsg==WM_MOUSEMOVE.if DragMode==TRUE

mov eax,lParamand eax,0ffffhmov ecx,lParamshr ecx,16mov tvhit.pt.x,eaxmov tvhit.pt.y,ecxinvoke ImageList_DragMove,eax,ecxinvoke ImageList_DragShowNolock,FALSEinvoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit.if eax!=NULL

invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax.endifinvoke ImageList_DragShowNolock,TRUE

.endif.elseif uMsg==WM_LBUTTONUP

.if DragMode==TRUEinvoke ImageList_DragLeave,hwndTreeViewinvoke ImageList_EndDraginvoke ImageList_Destroy,hDragImageListinvoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eaxinvoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0invoke ReleaseCapturemov DragMode,FALSE

.endif.elseif uMsg==WM_NOTIFY

mov edi,lParamassume edi:ptr NM_TREEVIEW.if [edi].hdr.code==TVN_BEGINDRAG

invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItemmov hDragImageList,eaxinvoke ImageList_BeginDrag,hDragImageList,0,0,0invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.yinvoke SetCapture,hWndmov DragMode,TRUE

.endifassume edi:nothing

.elseif uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endpend start

Analysis:

Within WM_CREATE handler, you create the tree view controlinvoke CreateWindowEx,NULL,ADDR TreeViewClass,NULL,\

WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT,0,\0,200,400,hWnd,NULL,\hInstance,NULL

Note the styles. TVS_xxxx are the tree view specific styles.

Page 105: 052 Iczelions_Win32_Assembly_Tutorials.pdf

105

invoke ImageList_Create,16,16,ILC_COLOR16,2,10mov hImageList,eaxinvoke LoadBitmap,hInstance,IDB_TREEmov hBitmap,eaxinvoke ImageList_Add,hImageList,hBitmap,NULLinvoke DeleteObject,hBitmapinvoke SendMessage,hwndTreeView,TVM_SETIMAGELIST,0,hImageList

Next, you create an empty image list with will accept images of 16x16 pixels in size, 16-bit color and initially, it will contain 2 images but can be expanded to 10 if need arises. We then load the bitmap from the resource and add it to the image list just created. After that, we delete the handle to the bitmap since it will not be used anymore. When the image list is all set, we associate it with the tree view control by sending TVM_SETIMAGELIST to the tree view control.

mov tvinsert.hParent,NULLmov tvinsert.hInsertAfter,TVI_ROOTmov tvinsert.u.item.imask,TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGEmov tvinsert.u.item.pszText,offset Parentmov tvinsert.u.item.iImage,0mov tvinsert.u.item.iSelectedImage,1invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert

We insert items into the tree view control, beginning from the root item. Since it will be root item, hParent member is NULL and hInsertAfter is TVI_ROOT. imask member specifies that pszText, iImage and iSelectedImage members of the TV_ITEM structure is valid. We fill those three members with appropriate value. pszText contains the label of the root item, iImage is the index into the image in the image list that will be displayed to the left of the unselected item, and iSelectedImage is the index into the image in the image list that will be displayed when the item is selected. When all appropriate members are filled in, we send TVM_INSERTITEM message to the tree view control to add the root item to it.

mov hParent,eaxmov tvinsert.hParent,eaxmov tvinsert.hInsertAfter,TVI_LASTmov tvinsert.u.item.pszText,offset Child1invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsertmov tvinsert.u.item.pszText,offset Child2invoke SendMessage,hwndTreeView,TVM_INSERTITEM,0,addr tvinsert

After the root item is added, we can attach the child items to it. hParent member is now filled with the handle of the parent item. And we will use identical images in the image list so we don't change iImage and iSelectedImagemember.

.elseif uMsg==WM_NOTIFYmov edi,lParamassume edi:ptr NM_TREEVIEW.if [edi].hdr.code==TVN_BEGINDRAG

invoke SendMessage,hwndTreeView,TVM_CREATEDRAGIMAGE,0,[edi].itemNew.hItemmov hDragImageList,eaxinvoke ImageList_BeginDrag,hDragImageList,0,0,0invoke ImageList_DragEnter,hwndTreeView,[edi].ptDrag.x,[edi].ptDrag.yinvoke SetCapture,hWndmov DragMode,TRUE

.endifassume edi:nothing

Now when the user tries to drag an item, the tree view control sends WM_NOTIFY message with TVN_BEGINDRAG as the code. lParam is the pointer to an NM_TREEVIEW structure which contains several pieces of information we need so we put its value into edi and use edi as the pointer to NM_TREEVIEW structure. assume edi:ptr NM_TREEVIEW is a way to tell MASM to treat edi as the pointer to NM_TREEVIEW structure. We then create a drag image by sending TVM_CREATEDRAGIMAGE to the tree view control. It returns the handle to the newly created image list with a drag image inside. We call ImageList_BeginDrag to set the hotspot in the drag image. Then we enter the drag operation by calling ImageList_DragEnter. This function displays the drag image at the specified location in the specified window. We use ptDrag structure that is a member of NM_TREEVIEW structure as the point where the drag image should be initially displayed.After that, we capture the mouse input and set the flag to indicate that we now enter drag mode.

.elseif uMsg==WM_MOUSEMOVE.if DragMode==TRUE

Page 106: 052 Iczelions_Win32_Assembly_Tutorials.pdf

106

mov eax,lParamand eax,0ffffhmov ecx,lParamshr ecx,16mov tvhit.pt.x,eaxmov tvhit.pt.y,ecxinvoke ImageList_DragMove,eax,ecxinvoke ImageList_DragShowNolock,FALSEinvoke SendMessage,hwndTreeView,TVM_HITTEST,NULL,addr tvhit.if eax!=NULL

invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,eax.endifinvoke ImageList_DragShowNolock,TRUE

.endifNow we concentrate on WM_MOUSEMOVE. When the user drags the drag image along, our parent window receives WM_MOUSEMOVE messages. In response to these messages, we update the drag image position with ImageList_DragMove. After that, we check if the drag image is over some item. We do that by sending TVM_HITTEST message to the tree view control with a point for it to check. If the drag image is over some item, we hilite that item by sending TVM_SELECTITEM message with TVGN_DROPHILITE flag to the tree view control. During the hilite operation, we hide the drag image so that it will not leave unsightly blots on the tree view control.

.elseif uMsg==WM_LBUTTONUP.if DragMode==TRUE

invoke ImageList_DragLeave,hwndTreeViewinvoke ImageList_EndDraginvoke ImageList_Destroy,hDragImageListinvoke SendMessage,hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0invoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_CARET,eaxinvoke SendMessage,hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0invoke ReleaseCapturemov DragMode,FALSE

.endifWhen the user releases the left mouse button, the drag operation is at the end. We leave the drag mode by calling ImageList_DragLeave, followed by ImageList_EndDrag and ImageList_Destroy. To make the tree view items look good, we also check the last hilited item, and select it. We must also un-hilite it else the other items will not get hilited when they are selected. And lastly, we release the mouse capture.

Page 107: 052 Iczelions_Win32_Assembly_Tutorials.pdf

107

Tutorial 20: Window SubclassingIn this tutorial, we will learn about window subclassing, what it is and how to use it to your advantage.Download the example here.

Theory:

If you program in Windows for some time, you will find some cases where a window has nearly the attributes you need in your program but not quite. Have you encountered a situation where you want some special kind of edit control that can filter out some unwanted text? The straightforward thing to do is to code your own window. But it's really hard work and time-consuming. Window subclassing to the rescue.In a nutshell, window subclassing allows you to "take over" the subclassed window. You will have absolute control over it. Let's take an example to make this clearer. Suppose you need a text box that accepts only hex numbers. If you use a simple edit control, you have no say whatsoever when your user types something other than hex numbers into your text box, ie. if the user types "zb+q*" into your text box, you can't do anything with it except rejecting the whole text string. This is unprofessional at least. In essence, you need the ability to examine each character the user typed into the text box right at the moment he typed it.We will examine how to do that now. When the user types something into a text box, Windows sends WM_CHAR message to the edit control's window procedure. This window procedure resides inside Windows itself so we can't modify it. But we can redirect the message flow to our own window procedure. So that our window procedure will get first shot at any message Windows sends to the edit control. If our window procedure chooses to act on the message, it can do so. But if it doesn't want to handle the message, it can pass it to the original window procedure. This way, our window procedure inserts itself between Windows and the edit control. Look at the flow below:

Before SubclassingWindows ==> edit control's window procedure

After SubclassingWindows ==> our window procedure -----> edit control's window procedure

Now we put our attention on how to subclass a window. Note that subclassing is not limited to controls, it can be used with any window.Let's think about how Windows knows where the edit control's window procedure resides. A guess?......lpfnWndProc member of WNDCLASSEX structure. If we can replace this member with the address of our own window procedure, Windows will send messages to our window proc instead.We can do that by calling SetWindowLong.

SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORDhWnd = handle of the window to change the value in the WNDCLASSEX structurenIndex == value to change.

GWL_EXSTYLE Sets a new extended window style.GWL_STYLE Sets a new window style.GWL_WNDPROC Sets a new address for the window procedure.GWL_HINSTANCE Sets a new application instance handle.GWL_ID Sets a new identifier of the window.GWL_USERDATA Sets the 32-bit value associated with the window. Each window has a corresponding 32-bit value intended for use by the application that created the window.

dwNewLong = the replacement value.So our job is easy: We code a window proc that will handle the messages for the edit control and then call SetWindowLong with GWL_WNDPROC flag, passing along the address of our window proc as the third parameter. If the function succeeds, the return value is the previous value of the specified 32-bit integer, in our case, the address of the original window procedure. We need to store this value for use within our window procedure.Remember that there will be some messages we don't want to handle, we will pass them to the original window procedure. We can do that by calling CallWindowProc function.

CallWindowProc PROTO lpPrevWndFunc:DWORD, \hWnd:DWORD,\Msg:DWORD,\wParam:DWORD,\lParam:DWORD

Page 108: 052 Iczelions_Win32_Assembly_Tutorials.pdf

108

lpPrevWndFunc = the address of the original window procedure.The remaining four parameters are the ones passed to our window procedure. We just pass them along to CallWindowProc.

Code Sample:.386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\comctl32.incincludelib \masm32\lib\comctl32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORDEditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.dataClassName db "SubclassWinClass",0AppName db "Subclassing Demo",0EditClass db "EDIT",0Message db "You pressed Enter in the text box!",0

.data?hInstance HINSTANCE ?hwndEdit dd ?OldWndProc dd ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_APPWORKSPACEmov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

Page 109: 052 Iczelions_Win32_Assembly_Tutorials.pdf

109

WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\

CW_USEDEFAULT,350,200,NULL,NULL,\hInst,NULL

mov hwnd,eax.while TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endwmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.if uMsg==WM_CREATE

invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\WS_CHILD+WS_VISIBLE+WS_BORDER,20,\20,300,25,hWnd,NULL,\hInstance,NULL

mov hwndEdit,eaxinvoke SetFocus,eax;-----------------------------------------; Subclass it!;-----------------------------------------invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProcmov OldWndProc,eax

.elseif uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD.if uMsg==WM_CHAR

mov eax,wParam.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK

.if al>="a" && al<="f"sub al,20h

.endifinvoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParamret

.endif.elseif uMsg==WM_KEYDOWN

mov eax,wParam.if al==VK_RETURN

invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATIONinvoke SetFocus,hEdit

.elseinvoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParamret

.endif.else

invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam

Page 110: 052 Iczelions_Win32_Assembly_Tutorials.pdf

110

ret.endifxor eax,eaxret

EditWndProc endpend start

Analysis:

invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProcmov OldWndProc,eax

After the edit control is created, we subclass it by calling SetWindowLong, replacing the address of the original window procedure with our own window procedure. Note that we store the address of the original window procedure for use with CallWindowProc. Note the EditWndProc is an ordinary window procedure.

.if uMsg==WM_CHARmov eax,wParam.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK

.if al>="a" && al<="f"sub al,20h

.endifinvoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParamret

.endifWithin EditWndProc, we filter WM_CHAR messages. If the character is between 0-9 or a-f, we accept it by passing along the message to the original window procedure. If it is a lower case character, we convert it to upper case by adding it with 20h. Note that, if the character is not the one we expect, we discard it. We don't pass it to the original window proc. So when the user types something other than 0-9 or a-f, the character just doesn't appear in the edit control.

.elseif uMsg==WM_KEYDOWNmov eax,wParam.if al==VK_RETURN

invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATIONinvoke SetFocus,hEdit

.elseinvoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParamret

.endI want to demonstrate the power of subclassing further by trapping Enter key. EditWndProc checks WM_KEYDOWN message if it's VK_RETURN (the Enter key). If it is, it displays a message box saying "You pressed the Enter key in the text box!". If it's not an Enter key, it passes the message to the original window procedure.You can use window subclassing to take control over other windows. It's one of the powerful techniques you should have in your arsenal.

Page 111: 052 Iczelions_Win32_Assembly_Tutorials.pdf

111

Tutorial 21: PipeIn this tutorial, we will explore pipe, what it is and what we can use it for. To make it more interesting, I throw in the technique on how to change the background and text color of an edit control.Download the example here.

Theory:

Pipe is a communication conduit or pathway with two ends. You can use pipe to exchange the data between two different processes, or within the same process. It's like a walkie-talkie. You give the other party one set and he can use it to communicate with you.There are two types of pipes: anonymous and named pipes. Anonymous pipe is, well, anonymous: that is, you can use it without knowing its name. A named pipe is the opposite: you have to know its name before you can use it.You can also categorize pipes according to its property: one-way or two-way. In a one-way pipe, the data can flow only in one direction: from one end to the other. While in a two-way pipe, the data can be exchanged between both ends.An anonymous pipe is always one-way while a named pipe can be one-way or two-way. A named pipe is usually used in a network environment where a server can connect to several clients.In this tutorial, we will examine anonymous pipe in some detail. Anonymous pipe's main purpose is to be used as a communcation pathway between a parent and child processes or between child processes.Anonymous pipe is really useful when you deal with a console application. A console application is a kind of win32 program which uses a console for its input & output. A console is like a DOS box. However, a console application is a fully 32-bit program. It can use any GUI function, the same as other GUI programs. It just happens to have a console for its use.A console application has three handles it can use for its input & output. They are called standard handles. There are three of them: standard input, standard output and standard error. Standard input handle is used to read/retrieve the information from the console and standard output handle is used to output/print the information to the console. Standard error handle is used to report error condition since its output cannot be redirected.A console application can retrieve those three standard handles by calling GetStdHandle function, specifying the handle it wants to obtain. A GUI application doesn't have a console. If you call GetStdHandle, it will return error. If you really want to use a console, you can call AllocConsole to allocate a new console. However, don't forget to call FreeConsole when you're done with the console.Anonymous pipe is most frequently used to redirect input and/or output of a child console application. The parent process may be a console or a GUI application but the child must be a console app. for this to work. As you know, a console application uses standard handles for its input and output. If we want to redirect the input and/or output of a console application, we can replace the handle with a handle to one end of a pipe. A console application will not know that it's using a handle to one end of a pipe. It'll use it as a standard handle. This is a kind of polymorphism, in OOP jargon. This approach is powerful since we need not modify the child process in anyway.Another thing you should know about a console application is where it gets those standard handles from. When a console application is created, the parent process has two choices: it can create a new console for the child or it can let the child inherit its own console. For the second approach to work, the parent process must be a console application or if it's a GUI application, it must call AllocConsole first to allocate a console.Let's begin the work. In order to create an anonymous pipe you need to call CreatePipe. CreatePipe has the following prototype:CreatePipe proto pReadHandle:DWORD, \

pWriteHandle:DWORD,\pPipeAttributes:DWORD,\nBufferSize:DWORD

� pReadHandle is a pointer to a dword variable that will receive the handle to the read end of the pipe� pWriteHandle is a pointer to a dword variable that will receive the handle to the write end of the pipe.� pPipeAttributes points to a SECURITY_ATTRIBUTES structure that determines whether the returned read

& write handles are inheritable by child processes� nBufferSize is the suggested size of the buffer the pipe will reserve for use. This is a suggested size only.

You can use NULL to tell the function to use the default size.

Page 112: 052 Iczelions_Win32_Assembly_Tutorials.pdf

112

If the call is successful, the return value is nonzero. If it failed, the return value is zero.After the call is successful, you will get two handles, one to read end of the pipe and the other to the write end. Now I will outline the steps needed for redirecting the standard output of a child console program to your own process.Note that my method differs from the one in Borland's win32 api reference. The method in win32 api reference assumes the parent process is a console application and thus the child can inherit the standard handles from it. But most of the time, we will need to redirect output from a console application to a GUI one.

1. Create an anonymous pipe with CreatePipe. Don't forget to set the bInheritable member of SECURITY_ATTRIBUTES to TRUE so the handles are inheritable.

2. Now we must prepare the parameters we will pass to CreateProcess since we will use it to load the child console application. One important structure is the STARTUPINFO structure. This structure determines the appearance of the main window of the child process when it first appears. This structure is vital to our purpose. You can hide the main window and pass the pipe handle to the child console process with it. Below is the members you must fill:

o cb : the size of STARTUPINFO structureo dwFlags : the binary bit flags that determine which members of the structure are valid also it

governs the show/hide state of the main window. For our purpose, you should use a combination of STARTF_USESHOWWINDOW and STARTF_USESTDHANDLES

o hStdOutput and hStdError : the handles you want the child process to use as standard output/error handles. For our purpose, we will pass write handle of the pipe as the standard output and error of the child. So when the child outputs something to the standard output/error, it actually passes the info via the pipe to the parent process.

o wShowWindow governs the show/hide state of the main window. For our purpose, we don't want the console window of the child to show so we put SW_HIDE into this member.

3. Call CreateProcess to load the child application. After CreateProcess is successful, the child is still dormant. It is loaded into memory but it doesn't run immediately

4. Close the write pipe handle. This is necessary. Because the parent process has no use for the write pipe handle, and the pipe won't work if there are more than one write end, we MUST close it before reading the data from the pipe. However, don't close the write handle before calling CreateProcess, your pipe will be broken. You should close it just after CreateProcess returns and before you read data from the read end of the pipe.

5. Now you can read data from the read end of the pipe with ReadFile. With ReadFile, you kick the child process into running mode. It will start execution and when it writes something to the standard output handle (which is actually the handle to the write end of the pipe), the data are sent through the pipe to the read end. You can think of ReadFile as sucking data from the read end of the pipe. You must call ReadFile repeatedly until it returns 0 which means there are no more data to be read. You can do anything with the data you read from the pipe. In our example, I put them into an edit control.

6. Close the read pipe handle.

Example:

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\gdi32.incincludelib \masm32\lib\gdi32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.constIDR_MAINMENU equ 101 ; the ID of the main menuIDM_ASSEMBLE equ 40001

Page 113: 052 Iczelions_Win32_Assembly_Tutorials.pdf

113

.dataClassName db "PipeWinClass",0AppName db "One-way Pipe Example",0 EditClass db "EDIT",0CreatePipeError db "Error during pipe creation",0CreateProcessError db "Error during process creation",0CommandLine db "ml /c /coff /Cp test.asm",0

.data?hInstance HINSTANCE ?hwndEdit dd ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_APPWORKSPACEmov wc.lpszMenuName,IDR_MAINMENUmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\hInst,NULL

mov hwnd,eax.while TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endwmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL rect:RECTLOCAL hRead:DWORDLOCAL hWrite:DWORDLOCAL startupinfo:STARTUPINFOLOCAL pinfo:PROCESS_INFORMATIONLOCAL buffer[1024]:byteLOCAL bytesRead:DWORD

Page 114: 052 Iczelions_Win32_Assembly_Tutorials.pdf

114

LOCAL hdc:DWORDLOCAL sat:SECURITY_ATTRIBUTES.if uMsg==WM_CREATE

invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL

mov hwndEdit,eax.elseif uMsg==WM_CTLCOLOREDIT

invoke SetTextColor,wParam,Yellowinvoke SetBkColor,wParam,Blackinvoke GetStockObject,BLACK_BRUSHret

.elseif uMsg==WM_SIZEmov edx,lParammov ecx,edxshr ecx,16and edx,0ffffhinvoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE

.elseif uMsg==WM_COMMAND.if lParam==0

mov eax,wParam.if ax==IDM_ASSEMBLE

mov sat.nLength,sizeof SECURITY_ATTRIBUTESmov sat.lpSecurityDescriptor,NULLmov sat.bInheritHandle,TRUEinvoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL.if eax==NULL

invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK.else

mov startupinfo.cb,sizeof STARTUPINFOinvoke GetStartupInfo,addr startupinfomov eax, hWritemov startupinfo.hStdOutput,eaxmov startupinfo.hStdError,eaxmov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLESmov startupinfo.wShowWindow,SW_HIDEinvoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr

startupinfo, addr pinfo.if eax==NULL

invoke MessageBox,hWnd,addr CreateProcessError,addrAppName,MB_ICONERROR+MB_OK

.elseinvoke CloseHandle,hWrite.while TRUE

invoke RtlZeroMemory,addr buffer,1024invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL.if eax==NULL

.break.endifinvoke SendMessage,hwndEdit,EM_SETSEL,-1,0invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer

.endw.endifinvoke CloseHandle,hRead

.endif.endif

.endif.elseif uMsg==WM_DESTROY

invoke PostQuitMessage,NULL.else

invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret

Page 115: 052 Iczelions_Win32_Assembly_Tutorials.pdf

115

.endifxor eax,eaxret

WndProc endpend start

Analysis:

The example will call ml.exe to assemble a file named test.asm and redirect the output of ml.exe to the edit control in its client area.When the program is loaded, it registers the window class and creates the main window as usual. The first thing it does during main window creation is to create an edit control which will be used to display the output of ml.exe.Now the interesting part, we will change the text and background color of the edit control. When an edit control is going to paint its client area, it sends WM_CTLCOLOREDIT message to its parent.wParam contains the handle to the device context that the edit control will use to write its own client area. We can use this opportunity to modify the characteristics of the HDC.

.elseif uMsg==WM_CTLCOLOREDITinvoke SetTextColor,wParam,Yellowinvoke SetTextColor,wParam,Blackinvoke GetStockObject,BLACK_BRUSHret

SetTextColor changes the text color to yellow. SetTextColor changes the background color of the text to black. Andlastly, we obtain the handle to the black brush which we return to Windows. With WM_CTLCOLOREDIT message, you must return a handle to a brush which Windows will use to paint the background of the edit control. In our example, I want the background to be black so I return the handle to the black brush to Windows.Now when the user selects Assemble menuitem, it creates an anonymous pipe.

.if ax==IDM_ASSEMBLEmov sat.nLength,sizeof SECURITY_ATTRIBUTESmov sat.lpSecurityDescriptor,NULLmov sat.bInheritHandle,TRUE

Prior to calling CreatePipe, we must fill the SECURITY_ATTRIBUTES structure first. Note that we can use NULL in lpSecurityDescriptor member if we don't care about security. And the bInheritHandle member must be TRUE so that the pipe handles are inheritable to the child process.

invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL

After that, we call CreatePipe which, if successful, will fill hRead and hWrite variables with the handles to read and write ends of the pipe respectively.

mov startupinfo.cb,sizeof STARTUPINFOinvoke GetStartupInfo,addr startupinfomov eax, hWritemov startupinfo.hStdOutput,eaxmov startupinfo.hStdError,eaxmov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLESmov startupinfo.wShowWindow,SW_HIDE

Next we must fill the STARTUPINFO structure. We call GetStartupInfo to fill the STARTUPINFO structure with default values of the parent process. You MUST fill the STARTUPINFO structure with this call if you intend your code to work under both win9x and NT. After GetStartupInfo call returns, you can modify the members that are important. We copy the handle to the write end of the pipe into hStdOutput and hStdError since we want the child process to use it instead of the default standard output/error handles. We also want to hide the console window of the child process, so we put SW_HIDE value into wShowWidow member. And lastly, we must indicate that hStdOutput, hStdError and wShowWindow members are valid and must be used by specifying the flags STARTF_USESHOWWINDOW and STARTF_USESTDHANDLES in dwFlags member.

Page 116: 052 Iczelions_Win32_Assembly_Tutorials.pdf

116

invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo

We now create the child process with CreateProcess call. Note that the bInheritHandles parameter must be set to TRUE for the pipe handle to work.

invoke CloseHandle,hWrite

After we successfully create the child process, we must close the write end of the pipe. Remember that we passed the write handle to the child process via STARTUPINFO structure. If we don't close the write handle from our end, there will be two write ends. And that the pipe will not work. We must close the write handle after CreateProcess but before we read data from the read end of the pipe.

.while TRUEinvoke RtlZeroMemory,addr buffer,1024invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL.if eax==NULL

.break.endifinvoke SendMessage,hwndEdit,EM_SETSEL,-1,0invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer

.endw

Now we are ready to read the data from the standard output of the child process. We will stay in an infinite loop until there are no more data left to read from the pipe. We call RtlZeroMemory to fill the buffer with zeroes then call ReadFile, passing the read handle of the pipe in place of a file handle. Note that we only read a maximum of 1023 bytes since we need the data to be an ASCIIZ string which we can pass on to the edit control.When ReadFile returns with the data in the buffer, we fill the data into the edit control. However, there is a slight problem here. If we use SetWindowText to put the data into the edit control, the new data will overwrite existing data! We want the data to append to the end of the existing data.To achieve that goal, we first move the caret to the end of the text in the edit control by sending EM_SETSEL message with wParam==-1. Next, we append the data at that point with EM_REPLACESEL message.

invoke CloseHandle,hRead

When ReadFile returns NULL, we break out of the loop and close the read handle.

Page 117: 052 Iczelions_Win32_Assembly_Tutorials.pdf

117

Tutorial 22: SuperclassingIn this tutorial, we will learn about superclassing, what it is and what it is for. You will also learn how to provide Tab key navigation to the controls in your own window.Download the example here

Theory:

In your programming career, you will surely encounter a situation where you need several controls with *slightly* different behavior. For example, you may need 10 edit controls which accept only number. There are several ways to achieve that goal:

� Create your own class and instantiate the controls� Create those edit control and then subclass all of them� Superclass the edit control

The first method is too tedious. You have to implement every functionality of the edit control yourself. Hardly a task to be taken lightly. The second method is better than the first one but still too much work. It is ok if you subclass only a few controls but it's going to be a nightmare to subclass a dozen or so controls. Superclassing is the technique you should use for this occasion.Subclassing is the method you use to *take control* of a particular window class. By *taking control*, I mean you can modify the properties of the window class to suit your purpose then then create the bunch of controls.The steps in superclassing is outlined below:

� call GetClassInfoEx to obtain the information about the window class you want to superclass. GetClassInfoEx requires a pointer to a WNDCLASSEX structure which will be filled with the information if the call returns successfully.

� Modify the WNDCLASSEX members that you want. However, there are two members which you MUST modify:

o hInstance You must put the instance handle of your program into this member.o lpszClassName You must provide it with a pointer to a new class name.

You need not modify lpfnWndProc member but most of the time, you need to do it. Just remember to save the original lpfnWndProc member if you want to call it with CallWindowProc.

� Register the modifed WNDCLASSEX structure. You'll have a new window class which has several characteristics of the old window class.

� Create windows from the new class

Superclassing is better than subclassing if you want to create many controls with the same characteristics.

Example:

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WM_SUPERCLASS equ WM_USER+5WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORDEditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.dataClassName db "SuperclassWinClass",0

Page 118: 052 Iczelions_Win32_Assembly_Tutorials.pdf

118

AppName db "Superclassing Demo",0EditClass db "EDIT",0OurClass db "SUPEREDITCLASS",0Message db "You pressed the Enter key in the text box!",0

.data?hInstance dd ?hwndEdit dd 6 dup(?)OldWndProc dd ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_APPWORKSPACEmov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR

AppName,\

WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\

CW_USEDEFAULT,350,220,NULL,NULL,\hInst,NULL

mov hwnd,eax

.while TRUEinvoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endwmov eax,msg.wParam

retWinMain endp

Page 119: 052 Iczelions_Win32_Assembly_Tutorials.pdf

119

WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL wc:WNDCLASSEX.if uMsg==WM_CREATE

mov wc.cbSize,sizeof WNDCLASSEXinvoke GetClassInfoEx,NULL,addr EditClass,addr wcpush wc.lpfnWndProcpop OldWndProcmov wc.lpfnWndProc, OFFSET EditWndProcpush hInstancepop wc.hInstancemov wc.lpszClassName,OFFSET OurClassinvoke RegisterClassEx, addr wcxor ebx,ebxmov edi,20.while ebx<6

invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\WS_CHILD+WS_VISIBLE+WS_BORDER,20,\edi,300,25,hWnd,ebx,\hInstance,NULL

mov dword ptr [hwndEdit+4*ebx],eaxadd edi,25inc ebx

.endwinvoke SetFocus,hwndEdit

.elseif uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD.if uMsg==WM_CHAR

mov eax,wParam.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK

.if al>="a" && al<="f"sub al,20h

.endifinvoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParamret

.endif.elseif uMsg==WM_KEYDOWN

mov eax,wParam.if al==VK_RETURN

invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATIONinvoke SetFocus,hEdit

.elseif al==VK_TABinvoke GetKeyState,VK_SHIFTtest eax,80000000.if ZERO?

invoke GetWindow,hEdit,GW_HWNDNEXT.if eax==NULL

invoke GetWindow,hEdit,GW_HWNDFIRST.endif

.elseinvoke GetWindow,hEdit,GW_HWNDPREV

Page 120: 052 Iczelions_Win32_Assembly_Tutorials.pdf

120

.if eax==NULLinvoke GetWindow,hEdit,GW_HWNDLAST

.endif.endifinvoke SetFocus,eaxxor eax,eaxret

.elseinvoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParamret

.endif.else

invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParamret

.endifxor eax,eaxret

EditWndProc endpend start

Analysis:

The program will create a simple window with 6 "modified" edit controls in its client area. The edit controls will accept only hex digits. Actually, I modified the subclassing example to do superclassing. The program starts normally and the interesting part is when the main window is created:

.if uMsg==WM_CREATEmov wc.cbSize,sizeof WNDCLASSEX

invoke GetClassInfoEx,NULL,addr EditClass,addr wc

We must first fill the WNDCLASSEX structure with the data from the class which we want to superclass, in this case, it's EDIT class. Remember that you must set the cbSize member of the WNDCLASSEX structure before you call GetClassInfoEx else the WNDCLASSEX structure will not be filled properly. After GetClassInfoEx returns, wc is filled with all information we need to create a new window class.

push wc.lpfnWndProcpop OldWndProcmov wc.lpfnWndProc, OFFSET EditWndProcpush hInstancepop wc.hInstancemov wc.lpszClassName,OFFSET OurClass

Now we must modify some members of wc. The first one is the pointer to the window procedure. Since we need to chain our own window procedure with the original one, we have to save it into a variable so we can call it with CallWindowProc. This technique is identical to subclassing except that you modify the WNDCLASSEX structure directly without having to call SetWindowLong. The next two members must be changed else you will not be able to register your new window class, hInstance and lpsClassName. You must replace original hInstance value with hInstance of your own program. And you must choose a new name for the new class.

invoke RegisterClassEx, addr wc

When all is ready, register the new class. You will get a new class with some characteristics of the old class.

xor ebx,ebxmov edi,20.while ebx<6

Page 121: 052 Iczelions_Win32_Assembly_Tutorials.pdf

121

invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\WS_CHILD+WS_VISIBLE+WS_BORDER,20,\edi,300,25,hWnd,ebx,\hInstance,NULL

mov dword ptr [hwndEdit+4*ebx],eaxadd edi,25inc ebx

.endwinvoke SetFocus,hwndEdit

Now that we registered the class, we can create windows based on it. In the above snippet, I use ebx as the counter of the number of windows created. edi is used as the y coordinate of the left upper corner of the window. When a window is created, its handle is stored in the array of dwords. When all windows are created, set input focus to the first window.At this point, you got 6 edit controls which accept only hex digits. The substituted window proc handles the filter. Actually, it's identical to the window proc in subclassing example. As you can see, you don't have to do extra work of subclassing them.

I throw in a code snippet to handle control navigation with tabs to make this example more juicy. Normally, if you put controls on a dialog box, the dialog box manager handles the navigation keys for you so you can tab to go to the next control or shift-tab to go back to the previous control. Alas, such feature is not available if you put your controls on a simple window. You have to subclass them so you can handle the Tab keys yourself. In our example, we need not subclass the controls one by one because we already superclassed them, so we can provide a "central control navigation manager" for them.

.elseif al==VK_TABinvoke GetKeyState,VK_SHIFTtest eax,80000000.if ZERO?

invoke GetWindow,hEdit,GW_HWNDNEXT.if eax==NULL

invoke GetWindow,hEdit,GW_HWNDFIRST.endif

.elseinvoke GetWindow,hEdit,GW_HWNDPREV.if eax==NULL

invoke GetWindow,hEdit,GW_HWNDLAST.endif

.endifinvoke SetFocus,eaxxor eax,eaxret

The above code snippet is from EditWndClass procedure. It checks if the user press Tab key, if so, it call GetKeyState to check if the SHIFT key is also pressed. GetKeyState returns a value in eax that determines whether the specified key is pressed or not. If the key is pressed, the high bit of eax is set. If not, the high bit is clear. So we test the return value against 80000000h. If the high bit is set, it means the user pressed shift+tab which we must handle separately.If the user press Tab key alone, we call GetWindow to retrieve the handle of the next control. We use GW_HWNDNEXT flag to tell GetWindow to obtain the handle to the window that is next in line to the current hEdit. If this function returns NULL, we interpret it as no more handle to obtain so the current hEdit is the last control in the line. We will "wrap around" to the first control by calling GetWindow with GW_HWNDFIRST flag. Similar to the Tab case, shift-tab just works in reverse.

Page 122: 052 Iczelions_Win32_Assembly_Tutorials.pdf

122

Tutorial 23: Tray IconIn this tutorial, we will learn how to put icons into system tray and how to create/use a popup menu.Download the example here.

Theory:

System tray is the rectangular region in the taskbar where several icons reside. Normally, you'll see at least a digital clock in it. You can also put icons in the system tray too. Below are the steps you have to perform to put an icon into the system tray:

1. Fill a NOTIFYICONDATA structure which has the following members:o cbSize The size of this structure.o hwnd Handle of the window that will receive notification when a mouse event occurs over the

tray icon.o uID A constant that is used as the icon's identifier. You are the one who decides on this value.

In case you have more than one tray icons, you will be able to check from what tray icon the mouse notification is from.

o uFlags Specify which members of this structure are valid� NIF_ICON The hIcon member is valid.� NIF_MESSAGE The uCallbackMessage member is valid.� NIF_TIP The szTip member is valid.

o uCallbackMessage The custom message that Windows will send to the window specified by the hwnd member when mouse events occur over the tray icon. You create this message yourself.

o hIcon The handle of the icon you want to put into the system trayo szTip A 64-byte array that contains the string that will be used as the tooltip text when the

mouse hovers over the tray icon.2. Call Shell_NotifyIcon which is defined in shell32.inc. This function has the following prototype:

Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

dwMessage is the type of message to send to the shell.NIM_ADD Adds an icon to the status area.NIM_DELETE Deletes an icon from the status area.NIM_MODIFY Modifies an icon in the status area.

pnid is the pointer to a NOTIFYICONDATA structure filled with proper valuesIf you want to add an icon to the tray, use NIM_ADD message, if you want to remove the icon, use NIM_DELETE.

That's all there is to it. But most of the time, you're not content in just putting an icon there. You need to be able to respond to the mouse events over the tray icon. You can do this by processing the message you specified in uCallbackMessage member of NOTIFYICONDATA structure. This message has the following values in wParam and lParam (special thanks to s__d for the info):

� wParam contains the ID of the icon. This is the same value you put into uID member of NOTIFYICONDATA structure.

� lParam The low word contains the mouse message. For example, if the user right-clicked at the icon, lParam will contain WM_RBUTTONDOWN.

Most tray icon, however, displays a popup menu when the user right-click on it. We can implement this feature by creating a popup menu and then call TrackPopupMenu to display it. The steps are described below:

1. Create a popup menu by calling CreatePopupMenu. This function creates an empty menu. It returns the menu handle in eax if successful.

2. Add menu items to it with AppendMenu, InsertMenu or InsertMenuItem.

Page 123: 052 Iczelions_Win32_Assembly_Tutorials.pdf

123

3. When you want to display the popup menu where the mouse cursor is, call GetCursorPos to obtain the screen coordinate of the cursor and then call TrackPopupMenu to display the menu. When the user selects a menu item from the popup menu, Windows sends WM_COMMAND message to your window procedure just like normal menu selection.

Note: Beware of two annoying behaviors when you use a popup menu with a tray icon:

1. When the popup menu is displayed, if you click anywhere outside the menu, the popup menu will not disappear immediately as it should be. This behavior occurs because the window that will receive the notifications from the popup menu MUST be the foreground window. Just call SetForegroundWindow will correct it.

2. After calling SetForegroundWindow, you will find that the first time the popup menu is displayed, it works ok but on the subsequent times, the popup menu will show up and close immediately. This behavior is "intentional", to quote from MSDN. The task switch to the program that is the owner of the tray icon in the near future is necessary. You can force this task switch by posting any message to the window of the program. Just use PostMessage, not SendMessage!

Example:

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\shell32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\shell32.lib

WM_SHELLNOTIFY equ WM_USER+5IDI_TRAY equ 0IDM_RESTORE equ 1000IDM_EXIT equ 1010WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.dataClassName db "TrayIconWinClass",0AppName db "TrayIcon Demo",0RestoreString db "&Restore",0ExitString db "E&xit Program",0

.data?hInstance dd ?note NOTIFYICONDATA <>hPopupMenu dd ?

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSG

Page 124: 052 Iczelions_Win32_Assembly_Tutorials.pdf

124

LOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKSmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_APPWORKSPACEmov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcinvoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\

CW_USEDEFAULT,350,200,NULL,NULL,\hInst,NULL

mov hwnd,eax.while TRUE

invoke GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endwmov eax,msg.wParamret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL pt:POINT.if uMsg==WM_CREATE

invoke CreatePopupMenumov hPopupMenu,eaxinvoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreStringinvoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString

.elseif uMsg==WM_DESTROYinvoke DestroyMenu,hPopupMenuinvoke PostQuitMessage,NULL

.elseif uMsg==WM_SIZE.if wParam==SIZE_MINIMIZED

mov note.cbSize,sizeof NOTIFYICONDATApush hWndpop note.hwndmov note.uID,IDI_TRAYmov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIPmov note.uCallbackMessage,WM_SHELLNOTIFYinvoke LoadIcon,NULL,IDI_WINLOGOmov note.hIcon,eaxinvoke lstrcpy,addr note.szTip,addr AppNameinvoke ShowWindow,hWnd,SW_HIDEinvoke Shell_NotifyIcon,NIM_ADD,addr note

.endif.elseif uMsg==WM_COMMAND

.if lParam==0

Page 125: 052 Iczelions_Win32_Assembly_Tutorials.pdf

125

invoke Shell_NotifyIcon,NIM_DELETE,addr notemov eax,wParam.if ax==IDM_RESTORE

invoke ShowWindow,hWnd,SW_RESTORE.else

invoke DestroyWindow,hWnd.endif

.endif.elseif uMsg==WM_SHELLNOTIFY

.if wParam==IDI_TRAY.if lParam==WM_RBUTTONDOWN

invoke GetCursorPos,addr ptinvoke SetForegroundWindow,hWndinvoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULLinvoke PostMessage,hWnd,WM_NULL,0,0

.elseif lParam==WM_LBUTTONDBLCLKinvoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0

.endif.endif

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endp

end start

Analysis:

The program will display a simple window. When you press the minimize button, it will hide itself and put an icon into the system tray. When you double-click on the icon, the program will restore itself and remove the icon from the system tray. When you right-click on it, a popup menu is displayed. You can choose to restore the program or exit it.

.if uMsg==WM_CREATEinvoke CreatePopupMenumov hPopupMenu,eaxinvoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreStringinvoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString

When the main window is created, it creates a popup menu and append two menu items. AppendMenu has the following syntax:

AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD

� hMenu is the handle of the menu you want to append the item to� uFlags tells Windows about the menu item to be appended to the menu whether it is a bitmap or a

string or an owner-draw item, enabled, grayed or disable etc. You can get the complete list from win32 api reference. In our example, we use MF_STRING which means the menu item is a string.

� uIDNewItem is the ID of the menu item. This is a user-defined value that is used to represent the menu item.

Page 126: 052 Iczelions_Win32_Assembly_Tutorials.pdf

126

� lpNewItem specifies the content of the menu item, depending on what you specify in uFlags member. Since we specify MF_STRING in uFlags member, lpNewItem must contain the pointer to the string to be displayed in the popup menu.

After the popup menu is created, the main window waits patiently for the user to press minimize button.When a window is minimized, it receives WM_SIZE message with SIZE_MINIMIZED value in wParam.

.elseif uMsg==WM_SIZE.if wParam==SIZE_MINIMIZED

mov note.cbSize,sizeof NOTIFYICONDATApush hWndpop note.hwndmov note.uID,IDI_TRAYmov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIPmov note.uCallbackMessage,WM_SHELLNOTIFYinvoke LoadIcon,NULL,IDI_WINLOGOmov note.hIcon,eaxinvoke lstrcpy,addr note.szTip,addr AppNameinvoke ShowWindow,hWnd,SW_HIDEinvoke Shell_NotifyIcon,NIM_ADD,addr note

.endif

We use this opportunity to fill NOTIFYICONDATA structure. IDI_TRAY is just a constant defined at the beginning of the source code. You can set it to any value you like. It's not important because you have only one tray icon. But if you will put several icons into the system tray, you need unique IDs for each tray icon. We specify all flags in uFlags member because we specify an icon (NIF_ICON), we specify a custom message (NIF_MESSAGE) and we specify the tooltip text (NIF_TIP). WM_SHELLNOTIFY is just a custom message defined as WM_USER+5. The actual value is not important so long as it's unique. I use the winlogo icon as the tray icon here but you can use any icon in your program. Just load it from the resource with LoadIcon and put the returned handle in hIcon member. Lastly, we fill the szTip with the text we want the shell to display when the mouse is over the icon.We hide the main window to give the illusion of "minimizing-to-tray-icon" appearance.Next we call Shell_NotifyIcon with NIM_ADD message to add the icon to the system tray.

Now our main window is hidden and the icon is in the system tray. If you move the mouse over it, you will see a tooltip that displays the text we put into szTip member. Next, if you double-click at the icon, the main window will reappear and the tray icon is gone.

.elseif uMsg==WM_SHELLNOTIFY.if wParam==IDI_TRAY

.if lParam==WM_RBUTTONDOWNinvoke GetCursorPos,addr ptinvoke SetForegroundWindow,hWndinvoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULLinvoke PostMessage,hWnd,WM_NULL,0,0

.elseif lParam==WM_LBUTTONDBLCLKinvoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0

.endif.endif

When a mouse event occurs over the tray icon, your window receives WM_SHELLNOTIFY message which is the custom message you specified in uCallbackMessage member. Recall that on receiving this message, wParam contains the tray icon's ID and lParam contains the actual mouse message. In the code above, we check first if this message comes from the tray icon we are interested in. If it does, we check the actual mouse message. Since we are only interested in right mouse click and double-left-click, we process only WM_RBUTTONDOWN and WM_LBUTTONDBLCLK messages.If the mouse message is WM_RBUTTONDOWN, we call GetCursorPos to obtain the current screen coordinate of the mouse cursor. When the function returns, the POINT structure is filled with the screen coordinate of the mouse cursor. By screen coordinate, I mean the coordinate of the entire screen without regarding to any window boundary.

Page 127: 052 Iczelions_Win32_Assembly_Tutorials.pdf

127

For example, if the screen resolution is 640*480, the right-lower corner of the screen is x==639 and y==479. If you want to convert the screen coordinate to window coordinate, use ScreenToClient function.However, for our purpose, we want to display the popup menu at the current mouse cursor position with TrackPopupMenu call and it requires screen coordinates, we can use the coordinates filled by GetCursorPos directly.TrackPopupMenu has the following syntax:

TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD

� hMenu is the handle of the popup menu to be displayed� uFlags specifies the options of the function. Like where to position the menu relative to the coordinates

specified later and which mouse button will be used to track the menu. In our example, we use TPM_RIGHTALIGN to position the popup menu to the left of the coordinates.

� x and y specify the location of the menu in screen coordinates.� nReserved must be NULL� hWnd is the handle of the window that will receive the messages from the menu.� prcRect is the rectangle in the screen where it is possible to click without dismissing the menu. Normally we

put NULL here so when the user clicks anywhere outside the popup menu, the menu is dismissed.

When the user double-clicks at the tray icon, we send WM_COMMAND message to our own window specifying IDM_RESTORE to emulate the user selects Restore menu item in the popup menu thereby restoring the main window and removing the icon from the system tray. In order to be able to receive double click message, the main window must have CS_DBLCLKS style.

invoke Shell_NotifyIcon,NIM_DELETE,addr notemov eax,wParam.if ax==IDM_RESTORE

invoke ShowWindow,hWnd,SW_RESTORE.else

invoke DestroyWindow,hWnd.endif

When the user selects Restore menu item, we remove the tray icon by calling Shell_NotifyIcon again, this time we specify NIM_DELETE as the message. Next, we restore the main window to its original state. If the user selects Exit menu item, we also remove the icon from the tray and destroy the main window by calling DestroyWindow.

Page 128: 052 Iczelions_Win32_Assembly_Tutorials.pdf

128

Tutorial 24: Windows HooksWe will learn about Windows hooks in this tutorial. Windows hooks are very powerful. With them, you can poke inside other processes and sometimes alter their behaviors.Download the example here.

Theory:

Windows hooks can be considered one of the most powerful features of Windows. With them, you can trap events that will occur, either in your own process or in other processes. By "hooking", you tell Windows about a function, filter function also called hook procedure, that will be called everytime an event you're interested in occurs. There are two types of them: local and remote hooks.

� Local hooks trap events that will occur in your own process.� Remote hooks trap events that will occur in other process(es). There are two types of remote hooks

o thread-specific traps events that will occur in a specific thread in other process. In short, you want to observe events in a specific thread in a specific process.

o system-wide traps all events destined for all threads in all processes in the system.

When you install hooks, remember that they affect system performance. System-wide hooks are the most notorious. Since ALL related events will be routed through your filter function, your system may slow down noticeably. So if you use a system-wide hook, you should use it judiciously and unhook it as soon as you don't need it. Also, you have a higher chance of crashing the other processes since you can meddle with other processes and if something is wrong in your filter function, it can pull the other processes down to oblivion with it. Remember: Power comes with responsibility.You have to understand how a hook works before you can use it efficiently. When you create a hook, Windows creates a data structure in memory, containing information about the hook, and adds it to a linked list of existing hooks. New hook is added in front of old hooks. When an event occurs, if you install a local hook, the filter function in your process is called so it's rather straightforward. But if it's a remote hook, the system must inject the code for the hook procedure into the address space(s) of the other process(es). And the system can do that only if the function resides in a DLL. Thus , if you want to use a remote hook, your hook procedure must reside in a DLL. There is two exceptions to this rule: journal record and journal playback hooks. The hook procedures for those two hooks must reside in the thread that installs the hooks. The reason why it must be so is that: both hooks deal with the low-level interception of hardware input events. The input events must be recorded/playbacked in the order they appeared. If the code of those two hooks is in a DLL, the input events may scatter among several threads and it is impossible to know the order of them. So the solution: the hook procedure of those two hooks must be in a single thread only i.e. the thread that installs the hooks.There are 14 types of hooks:

� WH_CALLWNDPROC called when SendMessage is called� WH_CALLWNDPROCRET called when SendMessage returns� WH_GETMESSAGE called when GetMessage or PeekMessage is called� WH_KEYBOARD called when GetMessage or PeekMessage retrieves WM_KEYUP or WM_KEYDOWN

from the message queue� WH_MOUSE called when GetMessage or PeekMessage retrieves a mouse message from the message

queue� WH_HARDWARE called when GetMessage or PeekMessage retrieves some hardware message that is not

related to keyboard or mouse.� WH_MSGFILTER called when a dialog box, menu or scrollbar is about to process a message. This hook is

local. It's specifically for those objects which have their own internal message loops.� WH_SYSMSGFILTER same as WH_MSGFILTER but system-wide� WH_JOURNALRECORD called when Windows retrieves message from the hardware input queue� WH_JOURNALPLAYBACK called when an event is requested from the system's hardware input queue.� WH_SHELL called when something interesting about the shell occurs such as when the task bar needs to

redraw its button.� WH_CBT used specifically for computer-based training (CBT).

Page 129: 052 Iczelions_Win32_Assembly_Tutorials.pdf

129

� WH_FOREGROUNDIDLE used internally by Windows. Little use for general applications� WH_DEBUG used to debug the hooking procedure

Now that we know some theory, we can move on to how to install/uninstall the hooks.To install a hook, you call SetWindowsHookEx which has the following syntax:SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD

� HookType is one of the values listed above, e.g., WH_MOUSE, WH_KEYBOARD� pHookProc is the address of the hook procedure that will be called to process the messages for the

specified hook. If the hook is a remote one, it must reside in a DLL. If not, it must be in your process.� hInstance is the instance handle of the DLL in which the hook procedure resides. If the hook is a local one,

this value must be NULL� ThreadID is the ID of the thread you want to install the hook to spy on. This parameter is the one that

determines whether a hook is local or remote. If this parameter is NULL, Windows will interpret the hook as a system-wide remote hook that affects all threads in the system. If you specify the thread ID of a thread in your own process, this hook is a local one. If you specify the thread ID from other process, the hook is a thread-specific remote one. There are two exceptions to this rule: WH_JOURNALRECORD and WH_JOURNALPLAYBACK are always local system-wide hooks that are not required to be in a DLL. And WH_SYSMSGFILTER is always a system-wide remote hook. Actually it is identical to WH_MSGFILTERhook with ThreadID==0.

If the call is successful, it returns the hook handle in eax. If not, NULL is returned. You must save the hook handle for unhooking later.You can uninstall a hook by calling UnhookWindowsHookEx which accepts only one parameter, the handle of the hook you want to uninstall. If the call succeeds, it returns a non-zero value in eax. Otherwise, it returns NULL.Now that you know how to install/uninstall hooks, we can examine the hook procedure.The hook procedure will be called whenever an event that is associated with the type of hook you have installed occurs. For example, if you install WH_MOUSE hook, when a mouse event occurs, your hook procedure will be called. Regardless of the type of hook you installed, the hook procedure always has the following prototype:

HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD

o nCode specifies the hook code.o wParam and lParam contain additional information about the event

HookProc is actually a placeholder for the function name. You can name it anything you like so long as it has the above prototype. The interpretation of nCode, wParam and lParam is dependent on the type of hook you install. So as the return value from the hook procedure. For example:WH_CALLWNDPROC

� nCode can be only HC_ACTION which means there is a message sent to a window� wParam contains the message being sent, if it's not zero� lParam points to a CWPSTRUCT structure� return value: not used, return zero

WH_MOUSE

� nCode can be HC_ACTION or HC_NOREMOVE� wParam contains the mouse message� lParam points to a MOUSEHOOKSTRUCT structure� return value: zero if the message should be processed. 1 if the message should be discarded.

The bottom line is: you must consult your win32 api reference for details about the meanings of the parameters and return value of the hook you want to install.Now there is a little catch about the hook procedure. Remember that the hooks are chained in a linked list with the most recently installed hook at the head of the list. When an event occurs, Windows will call only the first hook in the chain. It's your hook procedure's responsibility to call the next hook in the chain. You can choose not to call the

Page 130: 052 Iczelions_Win32_Assembly_Tutorials.pdf

130

next hook but you'd better know what you're doing. Most of the time, it's a good practice to call the next procedure so other hooks can have a shot at the event. You can call the next hook by calling CallNextHookEx which has the following prototype:CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD

� hHook is your own hook handle. The function uses this handle to traverse the linked list and search for the hook procedure it should call next.

� nCode, wParam and lParam you can just pass those three values you receive from Windows to CallNextHookEx.

An important note about remote hooks: the hook procedure must reside in a DLL which will be mapped into other processes. When Windows maps the DLL into other processes, it will not map the data section(s) into the other processes. In short, all processes share a single copy of code but they will have their own private copy of the DLL's data section! This can be a big surprise to the unwary. You may think that when you store a value into a variable in the data section of a DLL, that value will be shared among all processes that load the DLL into their process address space. It's simply not true. In normal situation, this behavior is desirable since it provides the illusion that each process has its own copy of the DLL. But not when Windows hook is concerned. We want the DLL to be identical in all processes, including the data. The solution: you must mark the data section as shared. You can do this by specifying the section(s) attribute in the linker switch. For MASM, you need to use this switch:/SECTION:<section name>, SThe name of the initialized data section is .data and the uninitialized data is .bss. For example if you want to assemble a DLL which contains a hook procedure and you want the uninitialized data section to be shared amoung processes, you must use the following line:link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........S attribute marks the section as shared.

Example:

There are two modules: one is the main program which will do the GUI part and the other is the DLL that will install/uninstall the hook.

;--------------------------------------------- This is the source code of the main program --------------------------------------.386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude mousehook.incincludelib mousehook.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

wsprintfA proto C :DWORD,:DWORD,:VARARGwsprintf TEXTEQU <wsprintfA>

.constIDD_MAINDLG equ 101IDC_CLASSNAME equ 1000IDC_HANDLE equ 1001IDC_WNDPROC equ 1002IDC_HOOK equ 1004IDC_EXIT equ 1005WM_MOUSEHOOK equ WM_USER+6

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD

Page 131: 052 Iczelions_Win32_Assembly_Tutorials.pdf

131

.dataHookFlag dd FALSEHookText db "&Hook",0UnhookText db "&Unhook",0template db "%lx",0

.data?hInstance dd ?hHook dd ?.codestart:

invoke GetModuleHandle,NULLmov hInstance,eaxinvoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULLinvoke ExitProcess,NULL

DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORDLOCAL hLib:DWORDLOCAL buffer[128]:byteLOCAL buffer1[128]:byteLOCAL rect:RECT.if uMsg==WM_CLOSE

.if HookFlag==TRUEinvoke UninstallHook

.endifinvoke EndDialog,hDlg,NULL

.elseif uMsg==WM_INITDIALOGinvoke GetWindowRect,hDlg,addr rectinvoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right, rect.bottom,

SWP_SHOWWINDOW.elseif uMsg==WM_MOUSEHOOK

invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128invoke wsprintf,addr buffer,addr template,wParaminvoke lstrcmpi,addr buffer,addr buffer1.if eax!=0

invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer.endifinvoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128invoke GetClassName,wParam,addr buffer,128invoke lstrcmpi,addr buffer,addr buffer1.if eax!=0

invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer.endifinvoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128invoke GetClassLong,wParam,GCL_WNDPROCinvoke wsprintf,addr buffer,addr template,eaxinvoke lstrcmpi,addr buffer,addr buffer1.if eax!=0

invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer.endif

.elseif uMsg==WM_COMMAND.if lParam!=0

mov eax,wParammov edx,eaxshr edx,16.if dx==BN_CLICKED

.if ax==IDC_EXITinvoke SendMessage,hDlg,WM_CLOSE,0,0

.else

Page 132: 052 Iczelions_Win32_Assembly_Tutorials.pdf

132

.if HookFlag==FALSEinvoke InstallHook,hDlg.if eax!=NULL

mov HookFlag,TRUEinvoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText

.endif.else

invoke UninstallHookinvoke SetDlgItemText,hDlg,IDC_HOOK,addr HookTextmov HookFlag,FALSEinvoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULLinvoke SetDlgItemText,hDlg,IDC_HANDLE,NULLinvoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL

.endif.endif

.endif.endif

.elsemov eax,FALSEret

.endifmov eax,TRUEret

DlgFunc endp

end start

;----------------------------------------------------- This is the source code of the DLL --------------------------------------.386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\kernel32.libinclude \masm32\include\user32.incincludelib \masm32\lib\user32.lib

.constWM_MOUSEHOOK equ WM_USER+6

.datahInstance dd 0

.data?hHook dd ?hWnd dd ?

.codeDllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD

.if reason==DLL_PROCESS_ATTACHpush hInstpop hInstance

.endifmov eax,TRUEret

DllEntry Endp

Page 133: 052 Iczelions_Win32_Assembly_Tutorials.pdf

133

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORDinvoke CallNextHookEx,hHook,nCode,wParam,lParammov edx,lParamassume edx:PTR MOUSEHOOKSTRUCTinvoke WindowFromPoint,[edx].pt.x,[edx].pt.yinvoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0assume edx:nothingxor eax,eaxret

MouseProc endp

InstallHook proc hwnd:DWORDpush hwndpop hWndinvoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULLmov hHook,eaxret

InstallHook endp

UninstallHook procinvoke UnhookWindowsHookEx,hHookret

UninstallHook endp

End DllEntry

;---------------------------------------------- This is the makefile of the DLL ----------------------------------------------

NAME=mousehook$(NAME).dll: $(NAME).obj

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj$(NAME).obj: $(NAME).asm

ml /c /coff /Cp $(NAME).asm

Analysis:

The example will display a dialog box with three edit controls that will be filled with the class name, window handle and the address of the window procedure associated with the window under the mouse cursor. There are two buttons, Hook and Exit. When you press the Hook button, the program hooks the mouse input and the text on the button changes to Unhook. When you move the mouse cursor over a window, the info about that window will be displayed in the main window of the example. When you press Unhook button, the program removes the mouse hook.The main program uses a dialog box as its main window. It defines a custom message, WM_MOUSEHOOK which will be used between the main program and the hook DLL. When the main program receives this message, wParam contains the handle of the window that the mouse cursor is on. Of course, this is an arbitrary arrangement. I decide to send the handle in wParam for the sake of simplicity. You can choose your own method of communication between the main program and the hook DLL.

.if HookFlag==FALSEinvoke InstallHook,hDlg.if eax!=NULL

mov HookFlag,TRUEinvoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText

.endif

Page 134: 052 Iczelions_Win32_Assembly_Tutorials.pdf

134

The program maintains a flag, HookFlag, to monitor the state of the hook. It's FALSE if the hook is not installed and TRUE if the hook is installed.When the user presses Hook button, the program checks if the hook is already installed. If it is not, it call InstallHook function in the hook DLL to install it. Note that we pass the handle of the main dialog as the parameter of the function so the hook DLL can send the WM_MOUSEHOOK messages to the right window i.e. our own.When the program is loaded, the hook DLL is loaded too. Actually, DLLs are loaded immediately after the program is in memory. The DLL entrypoint function is called before the first instruction in the main program is execute even. So when the main program executes the DLL(s) is/are initialized. We put the following code in the DLL entrypoint function of the hook DLL:

.if reason==DLL_PROCESS_ATTACHpush hInstpop hInstance

.endif

The code just saves the instance handle of the hook DLL itself to a global variable named hInstance for use within the InstallHook function. Since the DLL entrypoint function is called before other functions in the DLL are called , hInstance is always valid. We put hInstance in .data section so that this value is kept on per-process basis. Since when the mouse cursor hovers over a window, the hook DLL is mapped into the process. Imagine that there is already a DLL that occupies the intended load address of the hook DLL, the hook DLL would be remapped to another address. The value of hInstance will be updated to those of the new load address. When the user presses Unhook button and then Hook button, SetWindowsHookEx will be called again. However, this time, it will use the new load address as the instance handle which will be wrong because in the example process, the hook DLL's load address hasn't been changed. The hook will be a local one where you can hook only the mouse events that occur in your own window. Hardly desirable.

InstallHook proc hwnd:DWORDpush hwndpop hWndinvoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULLmov hHook,eaxret

InstallHook endp

The InstallHook function itself is very simple. It saves the window handle passed as its parameter to a global variable named hWnd for future use. It then calls SetWindowsHookEx to install a mouse hook. The return value of SetWindowsHookEx is stored in a global variable named hHook for use with UnhookWindowsHookEx.After SetWindowsHookEx is called, the mouse hook is functional. Whenever a mouse event occurs in the system, MouseProc ( your hook procedure) is called.

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORDinvoke CallNextHookEx,hHook,nCode,wParam,lParammov edx,lParamassume edx:PTR MOUSEHOOKSTRUCTinvoke WindowFromPoint,[edx].pt.x,[edx].pt.yinvoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0assume edx:nothingxor eax,eaxret

MouseProc endp

The first thing it does is to call CallNextHookEx to give the other hooks the chance to process the mouse event. After that, it calls WindowFromPoint function to retrieve the handle of the window at the specified screen coordinate. Note that we use the POINT structure in the MOUSEHOOKSTRUCT structure pointed to by lParam as the current mouse coordinate. After that we send the window handle to the main program via PostMessage with WM_MOUSEHOOK message. One thing you should remember is that: you should not use SendMessage inside the hook procedure, it can cause message deadlock. PostMessage is recommended. The MOUSEHOOKSTRUCT structure is defined below:

Page 135: 052 Iczelions_Win32_Assembly_Tutorials.pdf

135

MOUSEHOOKSTRUCT STRUCT DWORDpt POINT <>hwnd DWORD ?wHitTestCode DWORD ?dwExtraInfo DWORD ?

MOUSEHOOKSTRUCT ENDS

� pt is the current screen coordinate of the mouse cursor� hwnd is the handle of the window that will receive the mouse message. It's usually the window under the

mouse cursor but not always. If a window calls SetCapture, the mouse input will be redirected to that window instead. Because of this reason, I don't use the hwnd member of this structure but choose to call WindowFromPoint instead.

� wHitTestCode specifies the hit-test value. The hit-test value gives more information about the current mouse cursor position. It specifies on what part of window the mouse cursor is. For complete list, check your win32 api reference under WM_NCHITTEST message.

� dwExtraInfo contains the extra information associated with the message. Normally this value is set by calling mouse_event and retrieved by calling GetMessageExtraInfo.

When the main window receives WM_MOUSEHOOK message, it uses the window handle in wParam to retrieve the information about the window.

.elseif uMsg==WM_MOUSEHOOKinvoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128invoke wsprintf,addr buffer,addr template,wParaminvoke lstrcmpi,addr buffer,addr buffer1.if eax!=0

invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer.endifinvoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128invoke GetClassName,wParam,addr buffer,128invoke lstrcmpi,addr buffer,addr buffer1.if eax!=0

invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer.endifinvoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128invoke GetClassLong,wParam,GCL_WNDPROCinvoke wsprintf,addr buffer,addr template,eaxinvoke lstrcmpi,addr buffer,addr buffer1.if eax!=0

invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer.endif

To avoid flickers, we check the text already in the edit controls and the text we will put into them if they are identical. If they are, we skip them.We retrieve the class name by calling GetClassName, the address of the window procedure by calling GetClassLong with GCL_WNDPROC and then format them into strings and put them into the appropriate edit controls.

invoke UninstallHookinvoke SetDlgItemText,hDlg,IDC_HOOK,addr HookTextmov HookFlag,FALSEinvoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULLinvoke SetDlgItemText,hDlg,IDC_HANDLE,NULLinvoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL

When the user presses Unhook button, the program calls UninstallHook function in the hook DLL. UninstallHook just calls UnhookWindowsHookEx. After that, it changes the text of the button back to "Hook", HookFlag to FALSE

Page 136: 052 Iczelions_Win32_Assembly_Tutorials.pdf

136

and clears the content of the edit controls.Note the linker switch in the makefile.

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

It specifies .bss section as a shared section to make all processes share the same uninitialized data section of the hook DLL. Without this switch, your hook DLL will not function correctly.

Page 137: 052 Iczelions_Win32_Assembly_Tutorials.pdf

137

Tutorial 25: Simple BitmapIn this tutorial, we will learn how to use bitmap in our program. To be exact, we will learn how to display a bitmap in the client area of our window. Download the example.

Theory

Bitmaps can be thought of as pictures stored in computer. There are many picture formats used with computers but Windows only natively supports Windows Bitmap Graphics files (.bmp). The bitmaps I'll refer to in this tutorial are Windows bitmap graphics files. The easiest way to use a bitmap is to use it as a resource. There are two ways to do that. You can include the bitmap in the resource definition file (.rc) as follows:

#define IDB_MYBITMAP 100IDB_MYBITMAP BITMAP "c:\project\example.bmp"This method uses a constant to represent the bitmap. The first line just creates a constant named IDB_MYBITMAP which has the value of 100. We will use this label to refer to the bitmap in the program. The next line declares a bitmap resource. It tells the resource compiler where to find the actual bmp file.The other method uses a name to represent the bitmap as follows:MyBitMap BITMAP "c:\project\example.bmp"This method requires that you refer to the bitmap in your program by the string "MyBitMap" instead of a value.Either method works fine as long as you know which method you're using.Now that we put the bitmap in the resource file, we can go on with the steps in displaying it in the client area of our window.

1. call LoadBitmap to get the bitmap handle. LoadBitmap has the following definition:

LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR

This function returns a bitmap handle. hInstance is the instance handle of our program. lpBitmapName is a pointer to the string that is the name of the bitmap (incase you use the second method to refer to the bitmap). If you use a constant to refer to the bitmap (like IDB_MYBITMAP), you can put its value here. (In the example above it would be 100). A short example is in order:

First Method:

.386

.model flat, stdcall

................

.constIDB_MYBITMAP equ 100................data?hInstance dd ?...............code.............

invoke GetModuleHandle,NULLmov hInstance,eax

............invoke LoadBitmap,hInstance,IDB_MYBITMAP

...........

Second Method:

Page 138: 052 Iczelions_Win32_Assembly_Tutorials.pdf

138

.386

.model flat, stdcall

................

.dataBitmapName db "MyBitMap",0................data?hInstance dd ?...............code.............

invoke GetModuleHandle,NULLmov hInstance,eax

............invoke LoadBitmap,hInstance,addr BitmapName

...........

1. Obtain a handle to device context (DC). You can obtain this handle by calling BeginPaint in response to WM_PAINT message or by calling GetDC anywhere.

2. Create a memory device context which has the same attribute as the device context we just obtained. The idea here is to create a kind of "hidden" drawing surface which we can draw the bitmap on. When we are finished with the operation, we just copy the content of the hidden drawing surface to the actual device context in one function call. It's an example of double-buffer technique used for fast display of pictures on the screen. You can create this "hidden" drawing surface by calling CreateCompatibleDC.

CreateCompatibleDC proto hdc:HDC

If this function succeeds, it returns the handle of the memory device context in eax. hdc is the handle to the device context that you want the memory DC to be compatible with.

1. Now that you got a hidden drawing surface, you can draw on it by selecting the bitmap into it. This is done by calling SelectObject with the handle to the memory DC as the first parameter and the bitmap handle as the second parameter. SelectObject has the following definition:

SelectObject proto hdc:HDC, hGdiObject:DWORD

1. The bitmap is drawn on the memory device context now. All we need to do here is to copy it to the actual display device, namely the true device context. There are several functions that can perform this operation such as BitBlt and StretchBlt. BitBlt just copies the content of one DC to another so it's fast while StretchBlt can stretch or compress the bitmap to fit the output area. We will use BitBlt here for simplicity. BitBlt has the following definition:

BitBlt proto hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD, hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD

hdcDest is the handle of the device context that serves as the destination of bitmap transfer operationnxDest, nyDest are the coordinate of the upper left corner of the output areanWidth, nHeight are the width and height of the output areahdcSrc is the handle of the device context that serves as the source of bitmap transfer operationnxSrc, nySrc are the coordinate of the upper left corner of the source rectangle.dwROP is the raster-operation code (hence the acronym ROP) that governs how to combine the color data of the bitmap to the existing color data on the output area to achieve the final result. Most of the time, you only want to overwrite the existing color data with the new one.

1. When you're done with the bitmap, delete it with DeleteObject API call.

Page 139: 052 Iczelions_Win32_Assembly_Tutorials.pdf

139

That's it! To recapitulate, you need to put the bitmap into the resource scipt. Then load it from the resource with LoadBitmap. You'll get the bitmap handle. Next you obtain the handle to the device context of the area you want to paint the bitmap on. Then you create a memory device context that is compatible with the device context you just obtained. Select the bitmap into the memory DC then copy the content of the memory DC to the real DC.

Example Code:

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\gdi32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\gdi32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORDIDB_MAIN equ 1

.dataClassName db "SimpleWin32ASMBitmapClass",0AppName db "Win32ASM Simple Bitmap Example",0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?hBitmap dd ?

.codestart:invoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstancepop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wc

Page 140: 052 Iczelions_Win32_Assembly_Tutorials.pdf

140

INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.while TRUEinvoke GetMessage, ADDR msg,NULL,0,0.break .if (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg.endwmov eax,msg.wParamretWinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAMLOCAL ps:PAINTSTRUCTLOCAL hdc:HDCLOCAL hMemDC:HDCLOCAL rect:RECT.if uMsg==WM_CREATE

invoke LoadBitmap,hInstance,IDB_MAINmov hBitmap,eax

.elseif uMsg==WM_PAINTinvoke BeginPaint,hWnd,addr psmov hdc,eaxinvoke CreateCompatibleDC,hdcmov hMemDC,eaxinvoke SelectObject,hMemDC,hBitmapinvoke GetClientRect,hWnd,addr rectinvoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPYinvoke DeleteDC,hMemDCinvoke EndPaint,hWnd,addr ps

.elseif uMsg==WM_DESTROYinvoke DeleteObject,hBitmapinvoke PostQuitMessage,NULL.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret.ENDIFxor eax,eaxretWndProc endpend start

;---------------------------------------------------------------------; The resource script;---------------------------------------------------------------------#define IDB_MAIN 1IDB_MAIN BITMAP "tweety78.bmp"

Analysis:

There is not much to analyze in this tutorial ;)

#define IDB_MAIN 1IDB_MAIN BITMAP "tweety78.bmp"

Page 141: 052 Iczelions_Win32_Assembly_Tutorials.pdf

141

Define a constant named IDB_MAIN, assign 1 as its value. And then use that constant as the bitmap resource identifier. The bitmap file to be included in the resource is "tweety78.bmp" which resides in the same folder as the resource script.

.if uMsg==WM_CREATEinvoke LoadBitmap,hInstance,IDB_MAINmov hBitmap,eax

In response to WM_CREATE, we call LoadBitmap to load the bitmap from the resource, passing the bitmap's resource identifier as the second parameter to the API. We get the handle to the bitmap when the function returns.Now that the bitmap is loaded, we can paint it in the client area of our main window.

.elseif uMsg==WM_PAINTinvoke BeginPaint,hWnd,addr psmov hdc,eaxinvoke CreateCompatibleDC,hdcmov hMemDC,eaxinvoke SelectObject,hMemDC,hBitmapinvoke GetClientRect,hWnd,addr rectinvoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPYinvoke DeleteDC,hMemDCinvoke EndPaint,hWnd,addr ps

We choose to paint the bitmap in response to WM_PAINT message. We first call BeginPaint to obtain the handle to the device context. Then we create a compatible memory DC with CreateCompatibleDC. Next select the bitmap into the memory DC with SelectObject. Determine the dimension of the client area with GetClientRect. Now we can display the bitmap in the client area by calling BitBlt which copies the bitmap from the memory DC to the real DC. When the painting is done, we have no further need for the memory DC so we delete it with DeleteDC. End painting session with EndPaint.

.elseif uMsg==WM_DESTROYinvoke DeleteObject,hBitmapinvoke PostQuitMessage,NULL

When we don't need the bitmap anymore, we delete it with DeleteObject

Page 142: 052 Iczelions_Win32_Assembly_Tutorials.pdf

142

Tutorial 26: Splash ScreenNow that we know how to use a bitmap, we can progress to a more creative use of it. Splash screen. Download the example.

Theory

A splash screen is a window that has no title bar, no system menu box, no border that displays a bitmap for a while and then disappears automatically. It's usually used during program startup, to display the program's logo or to distract the user's attention while the program does some lengthy initialization. We will implement a splash screen in this tutorial.The first step is to include the bitmap in the resource file. However, if you think of it, it's a waste of precious memory to load a bitmap that will be used only once and keep it in memory till the program is closed. A better solution is to create a *resource* DLL which contains the bitmap and has the sole purpose of displaying the splash screen. This way, you can load the DLL when you want to display the splash screen and unload it when it's not necessary anymore. So we will have two modules: the main program and the splash DLL. We will put the bitmap into the DLL's resource.The general scheme is as follows:

1. Put the bitmap into the DLL as a bitmap resource2. The main program calls LoadLibrary to load the dll into memory3. The DLL entrypoint function of the DLL is called. It will create a timer and set the length of time that the

splash screen will be displayed. Next it will register and create a window without caption and border and display the bitmap in the client area.

4. When the specified length of time elapsed, the splash screen is removed from the screen and the control is returned to the main program

5. The main program calls FreeLibrary to unload the DLL from memory and then goes on with whatever task it is supposed to do.

We will examine the mechanisms in detail.

Load/Unload DLL

You can dynamically load a DLL with LoadLibrary function which has the following syntax:LoadLibrary proto lpDLLName:DWORDIt takes only one parameter: the address of the name of the DLL you want to load into memory. If the call is successful, it returns the module handle of the DLL else it returns NULL.To unload a DLL, call FreeLibrary:FreeLibrary proto hLib:DWORDIt takes one parameter: the module handle of the DLL you want to unload. Normally, you got that handle from LoadLibrary

How to use a timer

First, you must create a timer first with SetTimer:SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

hWnd is the handle of a window that will receive the timer notification message. This parameter can be NULL to specify that there is no window that's associated with the timer.TimerID is a user-defined value that is used as the ID of the timer.uElapse is the time-out value in milliseconds.lpTimerFunc is the address of a function that will process the timer notification messages. If you pass NULL, the timer messages will be sent to the window specified by hWnd parameter.

Page 143: 052 Iczelions_Win32_Assembly_Tutorials.pdf

143

SetTimer returns the ID of the timer if successful. Otherwise it returns NULL. So it's best not to use the timer ID of 0.

You can create a timer in two ways:

� If you have a window and you want the timer notification messages to go to that window, you must pass all four parameters to SetTimer (the lpTimerFunc must be NULL)

� If you don't have a window or you don't want to process the timer messages in the window procedure, you must pass NULL to the function in place of a window handle. You must also specify the address of the timer function that will process the timer messages.

We will use the first approach in this example.When the time-out period elapses, WM_TIMER message is sent to the window that is associated with the timer. For example, if you specify uElapse of 1000, your window will receive WM_TIMER every second.When you don't need the timer anymore, destroy it with KillTimer:KillTimer proto hWnd:DWORD, TimerID:DWORD

Example:

;-----------------------------------------------------------------------; The main program;-----------------------------------------------------------------------.386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.dataClassName db "SplashDemoWinClass",0AppName db "Splash Screen Example",0Libname db "splash.dll",0

.data?hInstance HINSTANCE ?CommandLine LPSTR ?.codestart:invoke LoadLibrary,addr Libname.if eax!=NULL

invoke FreeLibrary,eax.endifinvoke GetModuleHandle, NULLmov hInstance,eaxinvoke GetCommandLinemov CommandLine,eaxinvoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULTinvoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEX

Page 144: 052 Iczelions_Win32_Assembly_Tutorials.pdf

144

mov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstancepop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcINVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.while TRUEinvoke GetMessage, ADDR msg,NULL,0,0.break .if (!eax)invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg.endwmov eax,msg.wParamretWinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM.IF uMsg==WM_DESTROYinvoke PostQuitMessage,NULL.ELSEinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret.ENDIFxor eax,eaxretWndProc endpend start

;--------------------------------------------------------------------; The Bitmap DLL;--------------------------------------------------------------------.386.model flat, stdcallinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\kernel32.incinclude \masm32\include\gdi32.incincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libincludelib \masm32\lib\gdi32.lib.dataBitmapName db "MySplashBMP",0ClassName db "SplashWndClass",0

Page 145: 052 Iczelions_Win32_Assembly_Tutorials.pdf

145

hBitMap dd 0TimerID dd 0

.datahInstance dd ?

.code

DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD.if reason==DLL_PROCESS_ATTACH ; When the dll is loaded

push hInstpop hInstancecall ShowBitMap

.endifmov eax,TRUEret

DllEntry EndpShowBitMap proc

LOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:HWNDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstancepop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,NULLmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,0invoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcINVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\

WS_POPUP,CW_USEDEFAULT,\CW_USEDEFAULT,250,250,NULL,NULL,\hInstance,NULL

mov hwnd,eaxINVOKE ShowWindow, hwnd,SW_SHOWNORMAL.WHILE TRUE

INVOKE GetMessage, ADDR msg,NULL,0,0.BREAK .IF (!eax)INVOKE TranslateMessage, ADDR msgINVOKE DispatchMessage, ADDR msg

.ENDWmov eax,msg.wParamret

ShowBitMap endpWndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

LOCAL ps:PAINTSTRUCTLOCAL hdc:HDCLOCAL hMemoryDC:HDCLOCAL hOldBmp:DWORDLOCAL bitmap:BITMAPLOCAL DlgHeight:DWORDLOCAL DlgWidth:DWORD

Page 146: 052 Iczelions_Win32_Assembly_Tutorials.pdf

146

LOCAL DlgRect:RECTLOCAL DesktopRect:RECT

.if uMsg==WM_DESTROY.if hBitMap!=0

invoke DeleteObject,hBitMap.endifinvoke PostQuitMessage,NULL

.elseif uMsg==WM_CREATEinvoke GetWindowRect,hWnd,addr DlgRectinvoke GetDesktopWindowmov ecx,eaxinvoke GetWindowRect,ecx,addr DesktopRectpush 0mov eax,DlgRect.bottomsub eax,DlgRect.topmov DlgHeight,eaxpush eaxmov eax,DlgRect.rightsub eax,DlgRect.leftmov DlgWidth,eaxpush eaxmov eax,DesktopRect.bottomsub eax,DlgHeightshr eax,1push eaxmov eax,DesktopRect.rightsub eax,DlgWidthshr eax,1push eaxpush hWndcall MoveWindowinvoke LoadBitmap,hInstance,addr BitmapNamemov hBitMap,eaxinvoke SetTimer,hWnd,1,2000,NULLmov TimerID,eax

.elseif uMsg==WM_TIMERinvoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULLinvoke KillTimer,hWnd,TimerID

.elseif uMsg==WM_PAINTinvoke BeginPaint,hWnd,addr psmov hdc,eaxinvoke CreateCompatibleDC,hdcmov hMemoryDC,eaxinvoke SelectObject,eax,hBitMapmov hOldBmp,eaxinvoke GetObject,hBitMap,sizeof BITMAP,addr bitmapinvoke StretchBlt,hdc,0,0,250,250,\

hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPYinvoke SelectObject,hMemoryDC,hOldBmpinvoke DeleteDC,hMemoryDCinvoke EndPaint,hWnd,addr ps

.elseif uMsg==WM_LBUTTONDOWNinvoke DestroyWindow,hWnd

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eax

Page 147: 052 Iczelions_Win32_Assembly_Tutorials.pdf

147

retWndProc endp

End DllEntry

Analysis:

We will examine the code in the main program first.invoke LoadLibrary,addr Libname.if eax!=NULL

invoke FreeLibrary,eax.endif

We call LoadLibrary to load the DLL named "splash.dll". And after that, unload it from memory with FreeLibrary. LoadLibrary will not return until the DLL is finished with its initialization.That's all the main program does. The interesting part is in the DLL.

.if reason==DLL_PROCESS_ATTACH ; When the dll is loadedpush hInstpop hInstancecall ShowBitMap

When the DLL is loaded, Windows calls its entrypoint function with DLL_PROCESS_ATTACH flag. We take this opportunity to display the splash screen. First we store the instance handle of the DLL for future use. Then call a function named ShowBitMap to do the real job. ShowBitMap registers a window class, creates a window and enters the message loop as usual. The interesting part is in the CreateWindowEx call:

INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\WS_POPUP,CW_USEDEFAULT,\CW_USEDEFAULT,250,250,NULL,NULL,\hInstance,NULL

Note that the window style is only WS_POPUP which will make the window borderless and without caption. We also limit the width and height of the window to 250x250 pixels.Now when the window is created, in WM_CREATE message handler we move the window to the center of the screen with the following code.

invoke GetWindowRect,hWnd,addr DlgRectinvoke GetDesktopWindowmov ecx,eaxinvoke GetWindowRect,ecx,addr DesktopRectpush 0mov eax,DlgRect.bottomsub eax,DlgRect.topmov DlgHeight,eaxpush eaxmov eax,DlgRect.rightsub eax,DlgRect.leftmov DlgWidth,eaxpush eaxmov eax,DesktopRect.bottomsub eax,DlgHeightshr eax,1push eaxmov eax,DesktopRect.rightsub eax,DlgWidthshr eax,1push eax

Page 148: 052 Iczelions_Win32_Assembly_Tutorials.pdf

148

push hWndcall MoveWindow

It retrieves the dimensions of the desktop and the window then calculates the appropriate coordinate of the left upper corner of the window to make it center.

invoke LoadBitmap,hInstance,addr BitmapNamemov hBitMap,eaxinvoke SetTimer,hWnd,1,2000,NULLmov TimerID,eax

Next it loads the bitmap from the resource with LoadBitmap and creates a timer with the timer ID of 1 and the time interval 2 seconds. The timer will send WM_TIMER messages to the window every 2 seconds.

.elseif uMsg==WM_PAINTinvoke BeginPaint,hWnd,addr psmov hdc,eaxinvoke CreateCompatibleDC,hdcmov hMemoryDC,eaxinvoke SelectObject,eax,hBitMapmov hOldBmp,eaxinvoke GetObject,hBitMap,sizeof BITMAP,addr bitmapinvoke StretchBlt,hdc,0,0,250,250,\

hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPYinvoke SelectObject,hMemoryDC,hOldBmpinvoke DeleteDC,hMemoryDCinvoke EndPaint,hWnd,addr ps

When the window receives WM_PAINT message, it creates a memory DC, select the bitmap into the memory DC, obtain the size of the bitmap with GetObject and then put the bitmap on the window by calling StretchBlt which performs like BitBlt but it can stretch or compress the bitmap to the desired dimension. In this case, we want the bitmap to fit into the window so we use StretchBlt instead of BitBlt. We delete the memory DC after that.

.elseif uMsg==WM_LBUTTONDOWNinvoke DestroyWindow,hWnd

It would be frustrating to the user if he has to wait until the splash screen to disappear. We can provide the user with a choice. When he clicks on the splash screen, it will disappear. That's why we need to process WM_LBUTTONDOWN message in the DLL. Upon receiving this message, the window is destroyed by DestroyWindow call.

.elseif uMsg==WM_TIMERinvoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULLinvoke KillTimer,hWnd,TimerID

If the user chooses to wait, the splash screen will disappear when the specified time has elapsed (in our example, it's 2 seconds). We can do this by processing WM_TIMER message. Upon receiving this message, we closes the window by sending WM_LBUTTONDOWN message to the window. This is to avoid code duplication. We don't have further use for the timer so we destroy it with KillTimer.When the window is closed, the DLL will return control to the main program.

Page 149: 052 Iczelions_Win32_Assembly_Tutorials.pdf

149

Tutorial 27: Tooltip Control

We will learn about the tooltip control: What it is and how to create and use it. Download the example.

Theory:

A tooltip is a small rectangular window that is displayed when the mouse pointer hovers over some specific area. A tooltip window contains some text that the programmer wants to be displayed. In this regard, a tooltip servers the same role as the status window but it disappears when the user clicks or moves the mouse pointer away from the designated area. You'll probably be familiar with the tooltips that are associated with toolbar buttons. Those "tooltips" are conveniencies provided by the toolbar control. If you want tooltips for other windows/controls, you need to create your own tooltip control.Now that you know what a tooltip is, let's go on to how we can create and use it. The steps are outlined below:

1. Create a tooltip control with CreateWindowEx2. Define a region that the tooltip control will monitor for mouse pointer movement.3. Submit the region to the tooltip control4. Relay mouse messages of the submitted region to the tooltip control (this step may occur

earlier, depending on the method used to relay the messages)

We wll next examine each step in detail.

Tooltip CreationA tooltip control is a common control. As such, you need to call InitCommonControlssomewhere in your source code so that MASM implicitly links your program to comctl32.dll. You create a tooltip control with CreateWindowEx. The typical scenario would be like this:.dataTooltipClassName db "Tooltips_class32",0.code.....invoke InitCommonControlsinvoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULLNote the window style: TIS_ALWAYSTIP. This style specifies that the tooltip will be shown when the mouse pointer is over the designated area regardless of the status of the window that contains the area. Put simply, if you use this flag, when the mouse pointer hovers over the area you register to the tooltip control, the tooltip window will appear even if the window under the mouse pointer is inactive.You don't have to include WS_POPUP and WS_EX_TOOLWINDOW styles in CreateWindowEx because the tooltip control's window procedure adds them automatically. You also don't need to specify the coordinate, the height and width of the tooltip window: the tooltip control will adjust them automatically to fit the tooltip text that will be displayed, thus we supply CW_USEDEFAULT in all four parameters. The remaining parameters are not remarkable.

Page 150: 052 Iczelions_Win32_Assembly_Tutorials.pdf

150

Specifying the toolThe tooltip control is created but it's not shown immediately. We want the tooltip window to show up when the mouse pointer hovers over some area. Now is the time to specify that area. We call such area "tool". A tool is a rectangular area on the client area of a window which the tooltip control will monitor for mouse pointer. If the mouse pointer hovers over the tool, the tooltip window will appear. The rectangular area can cover the whole client area or only a part of it. So we can divided tool into two types: one that is implemented as a window and another that is implemented as a rectangular area in the client area of some window. Both has their uses. The tool that covers the whole client area of a window is most frequently used with controls such as buttons, edit controls and so on. You don't need to specify the coordinate and the dimensions of the tool: it's assumed to be the whole client area of the window. The tool that is implemented as a rectangular area on the client area is useful when you want to divide the client area of a window into several regions without using child windows. With this type of tool, you need to specify the coordinate of the upper left corner and the width and height of the tool.You specify the tool with the TOOLINFO structure which has the following definition:TOOLINFO STRUCTcbSize DWORD ?uFlags DWORD ?hWnd DWORD ?uId DWORD ?rect RECT <>hInst DWORD ?lpszText DWORD ?lParam LPARAM ?

TOOLINFO ENDS

Field Name Explanation

cbSizeThe size of the TOOLINFO structure. You MUST fill this member. Windows will not flag error if this field is not filled properly but you will receive strange, unpredictable results.

uFlags

The bit flags that specifies the characteristics of the tool. This value can be a combination of the following flags:

� TTF_IDISHWND "ID is hWnd". If you specify this flag, it means you want to use a tool that covers the whole client area of a window (the first type of tool above). If you use this flag, you must fill the uId member of this structure with the handle of the window you want to use. If you don't specify this flag, it means you want to use the second type of tool, the one that is implemented as the rectangular area on the client window. In that case, you need to fill the rect member with the dimension of the rectangle.

� TTF_CENTERTIP Normally the tooltip window will appear to the right and below the mouse pointer. If you specify this flag, the tooltip window will always appear directly below the tool and is centered regardless of the position of the mouse pointer.

� TTF_RTLREADING You can forget about this flag if your program is not designed specifically for Arabic or Hebrew systems. This flag displays the tooltip text with right-to-left reading order. Doesn't work under other systems.

� TTF_SUBCLASS If you use this flag, it means you tell the tooltip control to

Page 151: 052 Iczelions_Win32_Assembly_Tutorials.pdf

151

subclass the window that the tool is on so that the tooltip control can intercept mouse messages that are sent to the window. This flag is very handy. If you don't use this flag, you have to do more work to relay the mouse messages to the tooltip control.

hWnd

Handle to the window that contains the tool. If you specify TTF_IDISHWND flag, this field is ignored since Windows will use the value in uId member as the window handle. You need to fill this field if:

� You don't use TTF_IDISHWND flag (in other words, you use a rectangular tool)

� You specify the value LPSTR_TEXTCALLBACK in lpszText member. This value tells the tooltip control that, when it needs to display the tooltip window, it must ask the window that contains the tool for the text to be displayed. This is a kind of dynamic realtime tooltip text update. If you want to change your tooltip text dynamically, you should specify LPSTR_TEXTCALLBACK value in lpszText member. The tooltip control will send TTN_NEEDTEXT notification message to the window identified by the handle in hWnd field.

uId

The value in this field can have two meanings, depending on whether the uFlagsmember contains the flag TTF_IDISHWND.

� Application-defined tool ID if the TTF_IDISHWND flag is not specified. Since this means you use a tool which covers only a part of the client area, it's logical that you can have many such tools on the same client area (without overlap). The tooltip control needs a way to differentiate between them. In this case, the window handle in hWnd member is not enough since all tools are on the same window. The application-defined IDs are thus necessary. The IDs can be any value so long as they are unique among themselves.

� The handle to the window whose whole client area is used as the tool if the TTF_IDISHWND flag is specified. You may wonder why this field is used to store the window handle instead of the hWnd field above. The answer is: the hWnd member may already be filled if the value LPSTR_TEXTCALLBACK is specified in the lpszText member and the window that is responsible for supplying the tooltip text and the window that contains the tool may NOT be the same ( You can design your program so that a single window can serve both roles but this is too restrictive. In this case, Microsoft gives you more freedom. Cheers.)

rect

A RECT structure that specifies the dimension of the tool. This structure defines a rectangle relative to the upper left corner of the client area of the window specified by the hWnd member. In short, you must fill this structure if you want to specify a tool that covers only a part of the client area. The tooltip control will ignore this field if you specify TTF_IDISHWND flag (you choose to use a tool that covers the whole client area)

hInst The handle of the instance that contains the string resource that will be used as the

Page 152: 052 Iczelions_Win32_Assembly_Tutorials.pdf

152

tooltip text if the value in the lpszText member specifies the string resource identifier. This may sound confusing. Read the explanation of the lpszText member first and you will understand what this field is used for. The tooltip control ignores this field if the lpszText field doesn't contain a string resource identifier.

lpszText

This field can have several values:

� If you specify the value LPSTR_TEXTCALLBACK in this field, the tooltip control will send TTN_NEEDTEXT notification message to the window identified by the handle in hWnd field for the text string to be displayed in the tooltip window. This is the most dynamic method of tooltip text update: you can change the tooltip text each time the tooltip window is displayed.

� If you specify a string resource identifier in this field, when the tooltip control needs to display the tooltip text in the tooltip window, it searches for the string in the string table of the instance specified by hInst member. The tooltip control identifies a string resource identifier by checking the high word of this field. Since a string resource identifier is a 16-bit value, the high word of this field will always be zero. This method is useful if you plan to port your program to other languages. Since the string resource is defined in a resource script, you don't need to modify the source code.You only have to modify the string table and the tooltip texts will change without the risk of introducing bugs into your program.

� If the value in this field is not LPSTR_TEXTCALLBACK and the high word is not zero, the tooltip control interprets the value as the pointer to a text string that will be used as the tooltip text. This method is the easiest to use but the least flexible.

To recapitulate, you need to fill the TOOLINFO structure prior to submitting it to the tooltip control. This structure describes the characteristics of the tool you desire.

Register the tool with the tooltip controlAfter you fill the TOOLINFO structure, you must submit it to tooltip control. A tooltip control can service many tools so it is usually unnecessary to create more than one tooltip control for a window. To register a tool with a tooltip control, you send the TTM_ADDTOOL message to the tooltip control. The wParam is not used and the lParam must contain the address of the TOOLINFO structure you want to register..data?ti TOOLINFO <>........code.......<fill the TOOLINFO structure>.......invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti

Page 153: 052 Iczelions_Win32_Assembly_Tutorials.pdf

153

SendMessage for this message will return TRUE if the tool is successfully registered with the tooltip control, FALSE otherwise.You can unregister the tool by sending TTM_DELTOOL message to the tooltip control.

Relaying Mouse Messages to the Tooltip ControlWhen the above step is completed, the tooltip control knows which area it should monitor for mouse messages and what text it should display in the tooltip window. The only thing it lacks is the *trigger* for that action. Think about it: the area specified by the tool is on the client area of the other window. How can the tooltip control intercept the mouse messages destined for that window? It needs to do so in order that it can measure the amount of time the mouse pointer hovers over a point in the tool so that when the specified amount of time elapses, the tooltip control shows the tooltip window. There are two methods of accomplishing this goal, one that requires the cooperation of the window that contains the tool and the other without the cooperation on the part of the window.

� The window that contains the tool must relay the mouse messages to the tooltip control by sending TTM_RELAYEVENT messages to the control. The lParam of this message must contain the address of a MSG structure that specifies the message to be relayed to the tooltip control. A tooltip control processes only the following mouse messages:

o WM_LBUTTONDOWNo WM_MOUSEMOVEo WM_LBUTTONUPo WM_RBUTTONDOWNo WM_MBUTTONDOWNo WM_RBUTTONUPo WM_MBUTTONUP

All other messages are ignored. Thus in the window procedure of the window that contains the tool, there must be a switch that does something like this:

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD.......

if uMsg==WM_CREATE.............

elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP

invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg..........

� You can specify TTF_SUBCLASS flag in the uFlags member of the TOOLINFOstructure. This flag tells the tooltip control to subclass the window that contains the tool so it can intercept the mouse messages without the cooperation of the window. This method is easier to use since it doesn't require more coding than specifying TTF_SUBCLASS flag and the tooltip control handles all the message interception itself.

Page 154: 052 Iczelions_Win32_Assembly_Tutorials.pdf

154

That's it. At this step, your tooltip control is fully functional. There are several useful tooltip-related messages you should know about.

� TTM_ACTIVATE. If you want to disable/enable the tooltip control dynamically, this message is for you. If the wParam value is TRUE, the tooltip control is enabled. If the wParam value is FALSE, the tooltip control is disabled. A tooltip control is enabled when it first created so you don't need to send this message to activate it.

� TTM_GETTOOLINFO and TTM_SETTOOLINFO. If you want to obtain/change the values in the TOOLINFO structure after it was submitted to the tooltip control, use these messages. You need to specify the tool you need to change with the correct uId and hWnd values. If you only want to change the rect member, use TTM_NEWTOOLRECTmessage. If you only want to change the tooltip text, use TTM_UPDATETIPTEXT.

� TTM_SETDELAYTIME. With this message, you can specify the time delay the tooltip control uses when it's displaying the tooltip text and much more.

Example:

The following example is a simple dialog box with two buttons. The client area of the dialog box is divided into 4 areas: upper left, upper right, lower left and lower right. Each area is specified as a tool with its own tooltip text. The two buttons also has their own tooltip texts..386.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\kernel32.incinclude \masm32\include\user32.incinclude \masm32\include\comctl32.incincludelib \masm32\lib\comctl32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.libDlgProc proto :DWORD,:DWORD,:DWORD,:DWORDEnumChild proto :DWORD,:DWORDSetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD.constIDD_MAINDIALOG equ 101.dataToolTipsClassName db "Tooltips_class32",0MainDialogText1 db "This is the upper left area of the dialog",0MainDialogText2 db "This is the upper right area of the dialog",0MainDialogText3 db "This is the lower left area of the dialog",0MainDialogText4 db "This is the lower right area of the dialog",0.data?hwndTool dd ?hInstance dd ?.codestart:

invoke GetModuleHandle,NULLmov hInstance,eaxinvoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULLinvoke ExitProcess,eax

DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORDLOCAL ti:TOOLINFOLOCAL id:DWORDLOCAL rect:RECT

Page 155: 052 Iczelions_Win32_Assembly_Tutorials.pdf

155

.if uMsg==WM_INITDIALOGinvoke InitCommonControlsinvoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\

TTS_ALWAYSTIP,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInstance,NULL

mov hwndTool,eaxmov id,0mov ti.cbSize,sizeof TOOLINFOmov ti.uFlags,TTF_SUBCLASSpush hDlgpop ti.hWndinvoke GetWindowRect,hDlg,addr rectinvoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rectinc idinvoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rectinc idinvoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rectinc idinvoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rectinvoke EnumChildWindows,hDlg,addr EnumChild,addr ti

.elseif uMsg==WM_CLOSEinvoke EndDialog,hDlg,NULL

.elsemov eax,FALSEret

.endifmov eax,TRUEret

DlgProc endp

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORDLOCAL buffer[256]:BYTEmov edi,lParamassume edi:ptr TOOLINFOpush hwndChildpop [edi].uIdor [edi].uFlags,TTF_IDISHWNDinvoke GetWindowText,hwndChild,addr buffer,255lea eax,buffermov [edi].lpszText,eaxinvoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,ediassume edi:nothingret

EnumChild endp

SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD

mov edi,lptimov esi,lprectassume esi:ptr RECTassume edi:ptr TOOLINFO.if id==0

mov [edi].rect.left,0mov [edi].rect.top,0mov eax,[esi].rightsub eax,[esi].leftshr eax,1mov [edi].rect.right,eaxmov eax,[esi].bottom

Page 156: 052 Iczelions_Win32_Assembly_Tutorials.pdf

156

sub eax,[esi].topshr eax,1mov [edi].rect.bottom,eax

.elseif id==1mov eax,[esi].rightsub eax,[esi].leftshr eax,1inc eaxmov [edi].rect.left,eaxmov [edi].rect.top,0mov eax,[esi].rightsub eax,[esi].leftmov [edi].rect.right,eaxmov eax,[esi].bottomsub eax,[esi].topmov [edi].rect.bottom,eax

.elseif id==2mov [edi].rect.left,0mov eax,[esi].bottomsub eax,[esi].topshr eax,1inc eaxmov [edi].rect.top,eaxmov eax,[esi].rightsub eax,[esi].leftshr eax,1mov [edi].rect.right,eaxmov eax,[esi].bottomsub eax,[esi].topmov [edi].rect.bottom,eax

.elsemov eax,[esi].rightsub eax,[esi].leftshr eax,1inc eaxmov [edi].rect.left,eaxmov eax,[esi].bottomsub eax,[esi].topshr eax,1inc eaxmov [edi].rect.top,eaxmov eax,[esi].rightsub eax,[esi].leftmov [edi].rect.right,eaxmov eax,[esi].bottomsub eax,[esi].topmov [edi].rect.bottom,eax

.endifpush lpTextpop [edi].lpszTextinvoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lptiassume edi:nothingassume esi:nothingret

SetDlgToolArea endpend start

Analysis:

Page 157: 052 Iczelions_Win32_Assembly_Tutorials.pdf

157

After the main dialog window is created, we create the tooltip control with CreateWindowEx.invoke InitCommonControlsinvoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\

TTS_ALWAYSTIP,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInstance,NULL

mov hwndTool,eaxAfter that, we proceed to define four tools for each corner of the dialog box.

mov id,0 ; used as the tool IDmov ti.cbSize,sizeof TOOLINFOmov ti.uFlags,TTF_SUBCLASS ; tell the tooltip control to subclass the dialog

window.push hDlgpop ti.hWnd ; handle to the window that contains the toolinvoke GetWindowRect,hDlg,addr rect ; obtain the dimension of the client areainvoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

We initialize the members of TOOLINFO structure. Note that we want to divide the client area into 4 tools so we need to know the dimension of the client area. That's why we call GetWindowRect. We don't want to relay mouse messages to the tooltip control ourselves so we specify TIF_SUBCLASS flag.SetDlgToolArea is a function that calculates the bounding rectangle of each tool and registers the tool to the tooltip control. I won't go into gory detail on the calculation, suffice to say that it divides the client area into 4 areas with the same sizes. Then it sends TTM_ADDTOOLmessage to the tooltip control, passing the address of the TOOLINFO structure in the lParam parameter.

invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

After all 4 tools are registered, we can go on to the buttons on the dialog box. We can handle each button by its ID but this is tedious. Instead, we will use EnumChildWindows API call to enumerate all controls on the dialog box and then registers them to the tooltip control. EnumChildWindows has the following syntax:

EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORDhWnd is the handle to the parent window. lpEnumFunc is the address of the EnumChildProcfunction that will be called for each control enumerated. lParam is the application-defined value that will be passed to the EnumChildProc function. The EnumChildProc function has the following definition:EnumChildProc proto hwndChild:DWORD, lParam:DWORDhwndChild is the handle to a control enumerated by EnumChildWindows. lParam is the same lParam value you pass to EnumChildWindows.In our example, we call EnumChildWindows like this:invoke EnumChildWindows,hDlg,addr EnumChild,addr tiWe pass the address of the TOOLINFO structure in the lParam parameter because we will register each child control to the tooltip control in the EnumChild function. If we don't use this method, we need to declare ti as a global variable which can introduce bugs.When we call EnumChildWindows, Windows will enumerate the child controls on our dialog box and call the EnumChild function once for each control enumerated. Thus if our dialog box has two controls, EnumChild will be called twice.

Page 158: 052 Iczelions_Win32_Assembly_Tutorials.pdf

158

The EnumChild function fills the relevant members of the TOOLINFO structure and then registers the tool with the tooltip control.EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD

LOCAL buffer[256]:BYTEmov edi,lParamassume edi:ptr TOOLINFOpush hwndChildpop [edi].uId ; we use the whole client area of the control as the toolor [edi].uFlags,TTF_IDISHWNDinvoke GetWindowText,hwndChild,addr buffer,255lea eax,buffer ; use the window text as the tooltip textmov [edi].lpszText,eaxinvoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,ediassume edi:nothingret

EnumChild endpNote that in this case, we use a different type of tool: one that covers the whole client area of the window. We thus need to fill the uID field with the handle to the window that contains the tool. Also we must specify TTF_IDISHWND flag in the uFlags member.

Page 159: 052 Iczelions_Win32_Assembly_Tutorials.pdf

159

Tutorial 28: Win32 Debug API Part 1In this tutorial, you'll learn what Win32 offers to developers regarding debugging primitives. You'll know how to debug a process when you're finished with this tutorial. Download the example.

Theory:

Win32 has several APIs that allow programmers to use some of the powers of a debugger. They are called Win32 Debug APIs or primitives. With them, you can:

� Load a program or attach to a running program for debugging� Obtain low-level information about the program you're debugging, such as process ID, address of entrypoint, image

base and so on.� Be notified of debugging-related events such as when a process/thread starts/exits, DLLs are loaded/unloaded etc.� Modify the process/thread being debugged

In short, you can code a simple debugger with those APIs. Since this subject is vast, I divide it into several managable parts: this tutorial being the first part. I'll explain the basic concepts and general framework for using Win32 Debug APIs in this tutorial.The steps in using Win32 Debug APIs are:

1. Create a process or attach your program to a running process. This is the first step in using Win32 Debug APIs. Since your program will act as a debugger, you need a program to debug. The program being debugged is called a debuggee. You can acquire a debuggee in two ways:

o You can create the debuggee process yourself with CreateProcess. In order to create a process for debugging, you must specify the DEBUG_PROCESS flag. This flag tells Windows that we want to debug the process. Windows will send notifications of important debugging-related events (debug events) that occur in the debuggee to your program. The debuggee process will be immediately suspended until your program is ready. If the debuggee also creates child processes, Windows will also send debug events that occur in all those child processes to your program as well. This behavior is usually undesirable. You can disable this behavior by specifying DEBUG_ONLY_THIS_PROCESS flag in combination of DEBUG_PROCESSflag.

o You can attach your program to a running process with DebugActiveProcess.2. Wait for debugging events. After your program acquired a debuggee, the debuggee's primary thread is suspended and

will continue to be suspended until your program calls WaitForDebugEvent. This function works like other WaitForXXX functions, ie. it blocks the calling thread until the waited-for event occurs. In this case, it waits for debug events to be sent by Windows. Let's see its definition:

WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

lpDebugEvent is the address of a DEBUG_EVENT structure that will be filled with information about the debug event that occurs within the debuggee.

dwMilliseconds is the length of time in milliseconds this function will wait for the debug event to occur. If this period elapses and no debug event occurs, WaitForDebugEvent returns to the caller. On the other hand, if you specifyINFINITE constant in this argument, the function will not return until a debug event occurs.

Now let's examine the DEBUG_EVENT structure in more detail.

DEBUG_EVENT STRUCT dwDebugEventCode dd ? dwProcessId dd ? dwThreadId dd ? u DEBUGSTRUCT <>

DEBUG_EVENT ENDS

Page 160: 052 Iczelions_Win32_Assembly_Tutorials.pdf

160

dwDebugEventCode contains the value that specifies what type of debug event occurs. In short, there can be many types of events, your program needs to check the value in this field so it knows what type of event occurs and responds appropriately. The possible values are:

Value Meanings

CREATE_PROCESS_DEBUG_EVENT

A process is created. This event will be sent when the debuggee process is just created (and not yet running) or when your program just attaches itself to a running process with DebugActiveProcess. This is the first event your program will receive.

EXIT_PROCESS_DEBUG_EVENT A process exits.

CREATE_THEAD_DEBUG_EVENTA new thread is created in the debuggee process or when your program first attaches itself to a running process. Note that you'll not receive this notification when the primary thread of the debuggee is created.

EXIT_THREAD_DEBUG_EVENT

A thread in the debuggee process exits. Your program will not receive this event for the primary thread. In short, you can think of the primary thread of the debuggee as the equivalent of the debuggee process itself. Thus, when your program sees CREATE_PROCESS_DEBUG_EVENT, it's actually the CREATE_THREAD_DEBUG_EVENT for the primary thread.

LOAD_DLL_DEBUG_EVENTThe debuggee loads a DLL. You'll receive this event when the PE loader first resolves the links to DLLs (you call CreateProcess to load the debuggee) and when the debuggee calls LoadLibrary.

UNLOAD_DLL_DEBUG_EVENT A DLL is unloaded from the debuggee process.

EXCEPTION_DEBUG_EVENT

An exception occurs in the debuggee process. Important: This event will occur once just before the debuggee starts executing its first instruction. The exception is actually a debug break (int 3h). When you want to resume the debuggee, call ContinueDebugEvent withDBG_CONTINUE flag. Don't use DBG_EXCEPTION_NOT_HANDLED flag else the debuggee will refuse to run under NT (on Win98, it works fine).

OUTPUT_DEBUG_STRING_EVENT This event is generated when the debuggee calls DebugOutputStringfunction to send a message string to your program.

RIP_EVENT System debugging error occurs

dwProcessId and dwThreadId are the process and thread Ids of the process that the debug event occurs. You can use these values as identifiers of the process/thread you're interested in. Remember that if you use CreateProcess to load the debuggee, you also get the process and thread IDs of the debuggee in the PROCESS_INFO structure. You can use these values to differentiate between the debug events occurring in the debuggee and its child processes (in case you didn't specify DEBUG_ONLY_THIS_PROCESS flag).

u is a union that contains more information about the debug event. It can be one of the following structures depending on the value of dwDebugEventCode above.

value in dwDebugEventCode Interpretation of u

CREATE_PROCESS_DEBUG_EVENT A CREATE_PROCESS_DEBUG_INFO structure named CreateProcessInfo

EXIT_PROCESS_DEBUG_EVENT An EXIT_PROCESS_DEBUG_INFO structure named ExitProcess

CREATE_THREAD_DEBUG_EVENT A CREATE_THREAD_DEBUG_INFO structure named CreateThread

Page 161: 052 Iczelions_Win32_Assembly_Tutorials.pdf

161

EXIT_THREAD_DEBUG_EVENT An EXIT_THREAD_DEBUG_EVENT structure named ExitThread

LOAD_DLL_DEBUG_EVENT A LOAD_DLL_DEBUG_INFO structure named LoadDll

UNLOAD_DLL_DEBUG_EVENT An UNLOAD_DLL_DEBUG_INFO structure named UnloadDll

EXCEPTION_DEBUG_EVENT An EXCEPTION_DEBUG_INFO structure named Exception

OUTPUT_DEBUG_STRING_EVENT An OUTPUT_DEBUG_STRING_INFO structure named DebugString

RIP_EVENT A RIP_INFO structure named RipInfo

I won't go into detail about all those structures in this tutorial, only the CREATE_PROCESS_DEBUG_INFO structure will be covered here. Assuming that our program calls WaitForDebugEvent and it returns. The first thing we should do is to examine the value in dwDebugEventCode to see which type of debug event occured in the debuggee process. For example, if the value in dwDebugEventCode is CREATE_PROCESS_DEBUG_EVENT, you can interpret the member in u as CreateProcessInfo and access it with u.CreateProcessInfo.

3. Do whatever your program want to do in response to the debug event. When WaitForDebugEvent returns, it means a debug event just occurred in the debuggee process or a timeout occurs. Your program needs to examine the value in dwDebugEventCode in order to react to the event appropriately. In this regard, it's like processing Windows messages: you choose to handle some and ignore some.

4. Let the debuggee continues execution. When a debug event occurs, Windows suspends the debuggee. When you're finished with the event handling, you need to kick the debuggee into moving again. You do this by calling ContinueDebugEvent function.

ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

This function resumes the thread that was previously suspended because a debug event occurred.dwProcessId and dwThreadId are the process and thread IDs of the thread that will be resumed. You usually take these two values from the dwProcessId and dwThreadId members of the DEBUG_EVENT structure.dwContinueStatus specifies how to continue the thread that reported the debug event. There are two possible values:DBG_CONTINUE and DBG_EXCEPTION_NOT_HANDLED. For all other debug events, those two values do the same thing: resume the thread. The exception is the EXCEPTION_DEBUG_EVENT. If the thread reports an exception debug event, it means an exception occurred in the debuggee thread. If you specify DBG_CONTINUE, the thread will ignore its own exception handling and continue with the execution. In this scenario, your program must examine and resolve the exception itself before resuming the thread with DBG_CONTINUE else the exception will occur again and again and again.... If you specify DBG_EXCEPTION_NOT_HANDLED, your program is telling Windows that it didn't handle the exception: Windows should use the default exception handler of the debuggee to handle the exception. In conclusion, if the debug event refers to an exception in the debuggee process, you should call ContinueDebugEvent with DBG_CONTINUE flag if your program already removed the cause of exception. Otherwise, your program must call ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED flag. Except in one case which you must always use DBG_CONTINUE flag: the first EXCEPTION_DEBUG_EVENT which has the value EXCEPTION_BREAKPOINT in the ExceptionCode member. When the debuggee is going to execute its very first instruction, your program will receive the exception debug event. It's actually a debug break (int 3h). If you respond by calling ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED flag, Windows NT will refuse to run the debuggee (because no one cares for it). You must always use DBG_CONTINUE flag in this case to tell Windows that you want the thread to go on.

5. Continue this cycle in an infinite loop until the debuggee process exits. Your program must be in an infinite loop much like a message loop until the debuggee exits. The loop looks like this:

.while TRUEinvoke WaitForDebugEvent, addr DebugEvent, INFINITE.break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT<Handle the debug events>invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId,

Page 162: 052 Iczelions_Win32_Assembly_Tutorials.pdf

162

DBG_EXCEPTION_NOT_HANDLED .endw

Here's the catch: Once you start debugging a program, you just can't detach from the debuggee until it exits.

Let's summarize the steps again:

1. Create a process or attach your program to a running process.2. Wait for debugging events3. Do whatever your program want to do in response to the debug event.4. Let the debuggee continues execution.5. Continue this cycle in an infinite loop until the debuggee process exits

Example:

This example debugs a win32 program and shows important information such as the process handle, process Id, image base and so on.

.386

.model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.1",0 ofn OPENFILENAME <> FilterString db "Executable Files",0,"*.exe",0

db "All Files",0,"*.*",0,0 ExitProc db "The debuggee exits",0 NewThread db "A new thread is created",0 EndThread db "A thread is destroyed",0 ProcessInfo db "File Handle: %lx ",0dh,0Ah

db "Process Handle: %lx",0Dh,0Ah db "Thread Handle: %lx",0Dh,0Ah db "Image Base: %lx",0Dh,0Ah db "Start Address: %lx",0

.data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> .code start: mov ofn.lStructSize,sizeof ofn mov ofn.lpstrFilter, offset FilterString mov ofn.lpstrFile, offset buffer mov ofn.nMaxFile,512mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS,

Page 163: 052 Iczelions_Win32_Assembly_Tutorials.pdf

163

NULL, NULL, addr startinfo, addr pi .while TRUE

invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT

invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break

.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile,

DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress

invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT

.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE

.continue .endif

.elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION

.elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION

.endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED

.endw invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread .endif invoke ExitProcess, 0 end start

Analysis:

The program fills the OPENFILENAME structure and then calls GetOpenFileName to let the user choose a program to be debugged.

invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

When the user chose one, it calls CreateProcess to load the program. It calls GetStartupInfo to fill the STARTUPINFOstructure with its default values. Note that we use DEBUG_PROCESS combined with DEBUG_ONLY_THIS_PROCESSflags in order to debug only this program, not including its child processes.

.while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE

When the debuggee is loaded, we enter the infinite debug loop, calling WaitForDebugEvent. WaitForDebugEvent will not return until a debug event occurs in the debuggee because we specify INFINITE as its second parameter. When a debug event occurred, WaitForDebugEvent returns and DBEvent is filled with information about the debug event.

.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break

We first check the value in dwDebugEventCode. If it's EXIT_PROCESS_DEBUG_EVENT, we display a message box saying "The debuggee exits" and then get out of the debug loop.

Page 164: 052 Iczelions_Win32_Assembly_Tutorials.pdf

164

.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile,

DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress

invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

If the value in dwDebugEventCode is CREATE_PROCESS_DEBUG_EVENT, then we display several interesting information about the debuggee in a message box. We obtain those information from u.CreateProcessInfo. CreateProcessInfo is a structure of type CREATE_PROCESS_DEBUG_INFO. You can get more info about this structure from Win32 API reference.

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue

.endif

If the value in dwDebugEventCode is EXCEPTION_DEBUG_EVENT, we must check further for the exact type of exception. It's a long line of nested structure reference but you can obtain the kind of exception from ExceptionCode member. If the value in ExceptionCode is EXCEPTION_BREAKPOINT and it occurs for the first time (or if we are sure that the debuggee has no embedded int 3h), we can safely assume that this exception occured when the debuggee was going to execute its very first instruction. When we are done with the processing, we must call ContinueDebugEvent with DBG_CONTINUEflag to let the debuggee run. Then we go back to wait for the next debug event.

.elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION

.elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION

.endif

If the value in dwDebugEventCode is CREATE_THREAD_DEBUG_EVENT or EXIT_THREAD_DEBUG_EVENT, we display a message box saying so.

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw

Except for the EXCEPTION_DEBUG_EVENT case above, we call ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED flag to resume the debuggee.

invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread

When the debuggee exits, we are out of the debug loop and must close both process and thread handles of the debuggee. Closing the handles doesn't mean we are killing the process/thread. It just means we don't want to use those handles to refer to the process/thread anymore.

Page 165: 052 Iczelions_Win32_Assembly_Tutorials.pdf

165

Tutorial 29: Win32 Debug API Part 2We continue with the subject of win32 debug API. In this tutorial, we will learn how to modify the debuggee process.Download the example

Theory:

In the previous tutorial, we know how to load the debuggee and handle debug events that occur in its process. In order to be useful, our program must be able to modify the debuggee process. There are several APIs just for this purpose.

� ReadProcessMemory This function allows you to read memory in the specified process. The function prototype is as follows:

ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

hProcess is the handle to the process you want to read.lpBaseAddress is the address in the target process you want to start reading. For example, if you want to read 4 bytes from the debuggee process starting at 401000h, the value in this parameter must be 401000h.lpBuffer is the address of the buffer to receive the bytes read from the process. nSize is the number of bytes you want to readlpNumberOfBytesRead is the address of the variable of dword size that receives the number of bytes actually read. If you don't care about it, you can use NULL.

� WriteProcessMemory is the counterpart of ReadProcessMemory. It enables you to write memory of the target process. Its parameters are exactly the same as those of ReadProcessMemory

The next two API functions need a little background on context. Under a multitasking OS like Windows, there can be several programs running at the same time. Windows gives each thread a timeslice. When that timeslice expires, Windows freezes the present thread and switches to the next thread that has the highest priority. Just before switching to the other thread, Windows saves values in registers of the present thread so that when the time comes to resume the thread, Windows can restore the last *environment* of that thread. The saved values of the registers are collectively called a context. Back to our subject. When a debug event occurs, Windows suspends the debuggee. The debuggee's context is saved. Since the debuggee is suspended, we can be sure that the values in the context will remain unchanged . We can get the values in the context with GetThreadContext and we can change them with SetThreadContext.These two APIs are very powerful. With them, you have at your fingertips the VxD-like power over the debuggee: you can alter the saved register values and just before the debuggee resumes execution, the values in the context will be written back into the registers. Any change you made to the context is reflected back to the debuggee. Think about it: you can even alter the value of the eip register and divert the flow of execution to anywhere you like! You won't be able to do that under normal circumstance.

GetThreadContext proto hThread:DWORD, lpContext:DWORD

hThread is the handle to the thread that you want to obtain the context fromlpContext is the address of the CONTEXT structure that will be filled when the function returns successfully.

SetThreadContext has exactly the same parameters. Let's see what a CONTEXT structure looks like:

� CONTEXT STRUCT � ContextFlags dd ?

;----------------------------------------------------------------------------------------------------------; This section is returned if ContextFlags contains the value CONTEXT_DEBUG_REGISTERS

� ;-----------------------------------------------------------------------------------------------------------iDr0 dd ? iDr1 dd ?

Page 166: 052 Iczelions_Win32_Assembly_Tutorials.pdf

166

iDr2 dd ? iDr3 dd ? iDr6 dd ? iDr7 dd ?

� ;----------------------------------------------------------------------------------------------------------; This section is returned if ContextFlags contains the value CONTEXT_FLOATING_POINT

� ;-----------------------------------------------------------------------------------------------------------� FloatSave FLOATING_SAVE_AREA <> � ;----------------------------------------------------------------------------------------------------------

; This section is returned if ContextFlags contains the value CONTEXT_SEGMENTS� ;-----------------------------------------------------------------------------------------------------------� regGs dd ?

regFs dd ? regEs dd ? regDs dd ?

� ;----------------------------------------------------------------------------------------------------------; This section is returned if ContextFlags contains the value CONTEXT_INTEGER

� ;-----------------------------------------------------------------------------------------------------------� regEdi dd ?

regEsi dd ? regEbx dd ? regEdx dd ? regEcx dd ? regEax dd ?

� ;----------------------------------------------------------------------------------------------------------; This section is returned if ContextFlags contains the value CONTEXT_CONTROL

� ;-----------------------------------------------------------------------------------------------------------� regEbp dd ?

regEip dd ? regCs dd ? regFlag dd ? regEsp dd ? regSs dd ?

� ;----------------------------------------------------------------------------------------------------------; This section is returned if ContextFlags contains the value CONTEXT_EXTENDED_REGISTERS

� ;-----------------------------------------------------------------------------------------------------------� ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

As you can observe, the members of this structures are mimics of the real processor's registers. Before you can use this structure, you need to specify which groups of registers you want to read/write in ContextFlags member. For example, if you want to read/write all registers, you must specify CONTEXT_FULL in ContextFlags. If you want only to read/write regEbp, regEip, regCs, regFlag, regEsp or regSs, you must specify CONTEXT_CONTROL in ContextFlags.

One thing you must remember when using the CONTEXT structure: it must be aligned on dword boundary else you'd get strange results under NT. You must put "align dword" just above the line that declares it, like this:

align dwordMyContext CONTEXT <>

Example:

The first example demonstrates the use of DebugActiveProcess. First, you need to run a target named win.exe which goes in an infinite loop just before the window is shown on the screen. Then you run the example, it will attach itself to win.exe and modify the code of win.exe such that win.exe exits the infinite loop and shows its own window.

Page 167: 052 Iczelions_Win32_Assembly_Tutorials.pdf

167

.386

.model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib

.data AppName db "Win32 Debug Example no.2",0 ClassName db "SimpleWinClass",0 SearchFail db "Cannot find the target process",0 TargetPatched db "Target patched!",0 buffer dw 9090h

.data? DBEvent DEBUG_EVENT <> ProcessId dd ? ThreadId dd ? align dword context CONTEXT <>

.code start: invoke FindWindow, addr ClassName, NULL .if eax!=NULL

invoke GetWindowThreadProcessId, eax, addr ProcessId mov ThreadId, eax invoke DebugActiveProcess, ProcessId .while TRUE

invoke WaitForDebugEvent, addr DBEvent, INFINITE .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT

mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULLinvoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE .continue

.endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED

.endw .else

invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif invoke ExitProcess, 0 end start

;--------------------------------------------------------------------; The partial source code of win.asm, our debuggee. It's actually; the simple window example in tutorial 2 with an infinite loop inserted; just before it enters the message loop.;----------------------------------------------------------------------

Page 168: 052 Iczelions_Win32_Assembly_Tutorials.pdf

168

......mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax jmp $ <---- Here's our infinite loop. It assembles to EB FEinvoke 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

Analysis:

invoke FindWindow, addr ClassName, NULL

Our program needs to attach itself to the debuggee with DebugActiveProcess which requires the process Id of the debuggee. We can obtain the process Id by calling GetWindowThreadProcessId which in turn needs the window handle as its parameter. So we need to obtain the window handle first. With FindWindow, we can specify the name of the window class we need. It returns the handle to the window created by that window class. If it returns NULL, no window of that class is present.

.if eax!=NULL invoke GetWindowThreadProcessId, eax, addr ProcessId mov ThreadId, eax invoke DebugActiveProcess, ProcessId

After we obtain the process Id, we can call DebugActiveProcess. Then we enter the debug loop waiting for the debug events.

.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context

When we get CREATE_PROCESS_DEBUG_INFO, it means the debuggee is suspended, ready for us to do surgery upon its process. In this example, we will overwrite the infinite loop instruction in the debuggee (0EBh 0FEh) with NOPs ( 90h 90h). First, we need to obtain the address of the instruction. Since the debuggee is already in the loop by the time our program attached to it, eip will always point to the instruction. All we need to do is obtain the value of eip. We use GetThreadContextto achieve that goal. We set the ContextFlags member to CONTEXT_CONTROL so as to tell GetThreadContext that we want it to fill the "control" register members of the CONTEXT structure.

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

Page 169: 052 Iczelions_Win32_Assembly_Tutorials.pdf

169

Now that we get the value of eip, we can call WriteProcessMemory to overwrite the "jmp $" instruction with NOPs, thus effectively help the debuggee exit the infinite loop. After that we display the message to the user and then call ContinueDebugEvent to resume the debuggee. Since the "jmp $" instruction is overwritten by NOPs, the debuggee will be able to continue with showing its window and enter the message loop. The evidence is we will see its window on screen.

The other example uses a slightly different approach to break the debuggee out of the infinite loop.

.......

.......

.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context add context.regEip,2 invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION

.......

.......

It still calls GetThreadContext to obtain the current value of eip but instead of overwriting the "jmp $" instruction, it increments the value of regEip by 2 to "skip over" the instruction. The result is that when the debuggee regains control , it resumes execution at the next instruction after "jmp $".

Now you can see the power of Get/SetThreadContext. You can also modify the other register images as well and their values will be reflected back to the debuggee. You can even insert int 3h instruction to put breakpoints in the debuggee process.

Page 170: 052 Iczelions_Win32_Assembly_Tutorials.pdf

170

Tutorial 30: Win32 Debug API part 3In this tutorial, we continue the exploration of win32 debug api. Specifically, we will learn how to trace the debuggee.Download the example.

Theory:

If you have used a debugger before, you would be familiar with tracing. When you "trace" a program, the program stops after executing each instruction, giving you the chance to examine the values of registers/memory. Single-stepping is the official name of tracing.The single-step feature is provided by the CPU itself. The 8th bit of the flag register is called trap flag. If this flag(bit) is set, the CPU executes in single-step mode. The CPU will generate a debug exception after each instruction. After the debug exception is generated, the trap flag is cleared automatically.We can also single-step the debuggee, using win32 debug api. The steps are as follows:

1. Call GetThreadContext, specifying CONTEXT_CONTROL in ContextFlags, to obtain the value of the flag register.

2. Set the trap bit in regFlag member of the CONTEXT structure3. call SetThreadContext 4. Wait for the debug events as usual. The debuggee will execute in single-step mode. After it executes each instruction,

we will get EXCEPTION_DEBUG_EVENT with EXCEPTION_SINGLE_STEP value in u.Exception.pExceptionRecord.ExceptionCode

5. If you need to trace the next instruction, you need to set the trap bit again.

Example:

.386

.model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib

.data AppName db "Win32 Debug Example no.4",0 ofn OPENFILENAME <> FilterString db "Executable Files",0,"*.exe",0

db "All Files",0,"*.*",0,0 ExitProc db "The debuggee exits",0Dh,0Ah

db "Total Instructions executed : %lu",0 TotalInstruction dd 0

.data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <>

Page 171: 052 Iczelions_Win32_Assembly_Tutorials.pdf

171

context CONTEXT <>

.code start: mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE

invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+

DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi .while TRUE

invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT

invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .break

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue

.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP inc TotalInstruction invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue

.endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,

DBG_EXCEPTION_NOT_HANDLED .endw

.endif invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread invoke ExitProcess, 0 end start

Analysis:

The program shows the openfile dialog box. When the user chooses an executable file, it executes the program in single-step mode, couting the number of instructions executed until the debuggee exits.

Page 172: 052 Iczelions_Win32_Assembly_Tutorials.pdf

172

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

We take this opportunity to set the debuggee into single-step mode. Remember that Windows sends an EXCEPTION_BREAKPOINT just before it executes the first instruction of the debuggee.

mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context

We call GetThreadContext to fill the CONTEXT structure with the current values in the registers of the debuggee. Morespecifically, we need the current value of the flag register.

or context.regFlag,100h

We set the trap bit (8th bit) in the flag register image.

invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue

Then we call SetThreadContext to overwrite the values in the CONTEXT structure with the new one(s) and call ContinueDebugEvent with DBG_CONTINUE flag to resume the debuggee.

.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP inc TotalInstruction

When an instruction is executed in the debuggee, we receive an EXCEPTION_DEBUG_EVENT. We must examine the value of u.Exception.pExceptionRecord.ExceptionCode. If the value is EXCEPTION_SINGLE_STEP, then this debug event is generated because of the single-step mode. In this case, we can increment the variable TotalInstruction by one because we know that exactly one instruction was executed in the debuggee.

invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue

Since the trap flag is cleared after the debug exception is generated, we must set the trap flag again if we want to continue in single-step mode.Warning: Don't use the example in this tutorial with a large program: tracing is SLOW. You may have to wait for ten minutes before you can close the debuggee.

Page 173: 052 Iczelions_Win32_Assembly_Tutorials.pdf

173

Tutorial 31: Listview ControlWe will learn how to create and use the listview control in this tutorial.

Download the example.

Theory:

A listview control is one of the common controls like treeview, richedit etc. You are familiar with it even if you may not know it by its name. For example, the right pane of Windows Explorer is a listview control. A listview control is good for displaying items. In this regard, it's like a listbox but with enhanced capabilities.You can create a listview control in two ways. The first method is also the easiest one: create it with a resource editor. Just don't forget to call InitCommonControls within your asm source code. The other method is to call CreateWindowEx in your source code. You must specify the correct window class name for the control, ie. SysListView32. The window class "WC_LISTVIEW" is incorrect. There are four methods of viewing data in a listview control: icon, small icon, list and report views. You can see examples of these views by selecting View->Large Icons (icon view), Small Icons (small icon view), List (list view) and Details (report view). Views are just data representation methods:they only affect the appearances of data. For example, you can have a lot of data in the listview control, but if you want, you can view only some of them. Report view is the most informative one while the remaining views give less info. You can specify the view you want when you create a listview control. You can later change the view by calling SetWindowLong, specifying GWL_STYLE flag.

Now that we know how to create a listview control, we will continue on how to use it. I'll focus on report view which can demonstrate many features of listview control. The steps in using a listview control are as follows:

1. Create a listview control with CreateWindowEx, specifying SysListView32 as the class name. You can specify the initial view at this time.

2. (if exists) Create and initialize image lists to be used with the listview items.3. Insert column(s) into the listview control. This step is necessary if the listview control will use report view.4. Insert items and subitems into the listview control.

Columns

In report view, there are one or more columns. You can think of the arrangement of data in the report view as a table: the data are arranged in rows and columns. You must have at least one column in your listview control (only in report view). In views other than report, you need not insert a column because there can be one and only one column in those views.You can insert a column by sending LVM_INSERTCOLUMN to the listview control.

LVM_INSERTCOLUMNwParam = iCollParam = pointer to a LV_COLUMN structure

iCol is the column number, starting from 0.LV_COLUMN contains information about the column to be inserted. It has the following definition:

LV_COLUMN STRUCT imask dd ? fmt dd ? lx dd ? pszText dd ? cchTextMax dd ?iSubItem dd ? iImage dd ?

Page 174: 052 Iczelions_Win32_Assembly_Tutorials.pdf

174

iOrder dd ? LV_COLUMN ENDS

Field name Meanings

imask

A collection of flags that governs which members in this structure are valid. The reason behind this member is that not all members in this structure are used at the same time. Some members are used in some situations. And this structure is used both for input and output. Thus it's important that you *mark* the members that are used in this call to Windows so Windows knows which members are valid. The available flags are:

LVCF_FMT = The fmt member is valid. LVCF_SUBITEM = The iSubItem member is valid. LVCF_TEXT = The pszText member is valid. LVCF_WIDTH = The lx member is valid.

You can combine the above flags. For example, if you want to specify the text label of the column, you must supply the pointer to the string in pszText member. And you must tell Windows that pszText member contains data by specifying LVCF_TEXT flag in this field else Windows will ignore the value in pszText.

fmt

Specify the alignment of items/subitems in the column. The available values are:

LVCFMT_CENTER = Text is centered. LVCFMT_LEFT = Text is left-aligned. LVCFMT_RIGHT = Text is right-aligned.

lx The width of the column, in pixels. You can later change the width of the column with LVM_SETCOLUMNWIDTH.

pszText

Contains a pointer to the name of the column if this structure is used to set the column's properties. If this structure is used to receive the properties of a column, this field contains a pointer to a buffer large enough to receive the name of the column that will be returned. In that case, you must give the size of the buffer in cchTextMax below. You can ignore cchTextMax if you want to set the name of the column because the name must be an ASCIIZ string which Windows can determine the length.

cchTextMaxThe size, in bytes, of the buffer specified in pszText above. This member is used only when you use this structure to receive info about a column. If you use this structure to set the properties of a column, this field is ignored.

iSubItem

Specify the index of subitem associated with this column. This value is used as a marker which subitem this column is associated with. If you want, you can specify an absurd number in this field and your listview control will still run like a breeze. The use of this field is best demonstrated when you have the column number and need to know with which subitem this column is associated. You can query the listview control by sending LVM_GETCOLUMN message to it, specifying LVCF_SUBITEM in the imask member. The listview control will fill the iSubItem member with whatever value you specify in this field when the column is inserted. In order for this method to work, you need to input the correct subitem index into this field.

iImage and iOrder Used with Internet Explorer 3.0 upwards. I don't have info about them.

So after the listview control is created, you should insert one or more columns into it. Columns are not necessary if you don't plan to switch the listview control into report view. In order to insert a column, you need to create a LV_COLUMN structure, fill it with necessary information, specify the column number and then send the structure to the listview control with LVM_INSERTCOLUMN message.

LOCAL lvc:LV_COLUMNmov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

Page 175: 052 Iczelions_Win32_Assembly_Tutorials.pdf

175

The above code snippet demonstrates the process. It specifies the column header text and its width then send LVM_INSERTCOLUMN message to the listview control. It's that simple.

Items and subitems

Items are the main entries in the listview control. In views other than report view, you will only see items in the listview control. Subitems are details of the items. An item may have one or more associated subitems. For example, if the item is the name of a file, then you can have the file attributes, its size, the date of file creation as subitems. In report view, the leftmost column contains items and the remaining columns contain subitems. You can think of an item and its subitems as a database record. The item is the primary key of the record and the subitems are fields in the record. At the bare minimum, you need some items in your listview control: subitems are not necessary. However, if you want to give the user more information about the items, you can associate items with subitems so the user can see the details in the report view.You insert an item into a listview control by sending LVM_INSERTITEM message to it. You also need to pass the address of an LV_ITEM structure to it in lParam. LV_ITEM has the following definition:

LV_ITEM STRUCT imask dd ? iItem dd ? iSubItem dd ? state dd ? stateMask dd ? pszText dd ? cchTextMax dd ? iImage dd ? lParam dd ? iIndent dd ?

LV_ITEM ENDS

Field name Meanings

imaskA collection of flags indicating which members in this structure are valid for this call. In general, this field is similar to imask member of LV_COLUMN above. Check your win32 api reference for more detail on the available flags.

iItem The index of the item this structure refers to. The index is zero-based. You can think of this field as containing the "row" number of a table.

iSubItem

The index of the subitem associated with the item specified by iItem above. You can think of this field as containing the "column" of a table. For example, if you want to insert an item into a newly created listview control, the value in iItem would be 0 (because this item is the first one), and the value in iSubItem would also be 0 (we want to insert the item into the first column). If you want to specify a subitem associated with this item, the iItem would be the index of the item you want to associate with (in the above example, it's 0), the iSubItemwould be 1 or greater, depending on which column you want to insert the subitem into. For example, if your listview control has 4 columns, the first column will contain the items. The remaining 3 columns are for subitems. If you want to insert a subitem into the 4th column, you need to specify the value 3 in iSubItem.

state

This member contains flags that reflect the status of the item. The state of an item can change because of the user's actions or it can be modified by our program. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.

stateMask Since the state member above can contain the state flags, overlay image index , and state image index, we need to tell Windows which value we want to set or retrieve. The value in this field is for such use.

pszTextThe address of an ASCIIZ string that will be used as the label of the item in the case we want to set/insert the item. In the case that we use this structure to retrieve the item's property, this member must contain the address of a buffer that will be filled with the label of the item.

Page 176: 052 Iczelions_Win32_Assembly_Tutorials.pdf

176

cchTextMax This field is used only when you use this structure to receive info about an item. In this case, this field contains the size in bytes of the buffer specified in pszText above.

iImage The index into the imagelist containing the icons for the listview control. This index points to the icon to be used with this item.

lParam

A user-defined value that will be used when you sort items in the listview control. In short, when you tell the listview control to sort the items, the listview control will compare the items in pairs. It will send the lParamvalues of both items to you so you can decide which of the two should be listed first. If you're still hazy about this, don't worry. You'll learn more about sorting later.

Let's summarize the steps in inserting an item/subitem into a listview control.

1. Create a variable of type LV_ITEM structure2. Fill it with necessary information3. Send LVM_INSERTITEM message to the listview control if you want to insert an item. Or if you want to *insert* a

subitem, send LVM_SETITEM instead. This is rather confusing if you don't understand the relationship between an item and its subitems. Subitems are considered as properties of an item. Thus you can insert items but not subitems and you can't have a subitem without an associated item. That's why you need to send LVM_SETITEM message to add a subitem instead of LVM_INSERTITEM.

ListView Messages/Notifications

Now that you know how to create and populate a listview control, the next step is to communicate with it. A listview control communicates with the parent window via messages and notifications. The parent window can control the listview control by sending messages to it. The listview control notifies the parent of important/interesting events via WM_NOTIFY message, just like other common controls.

Sorting items/subitems

You can specify the default sorting order of a listview control by specifying LVS_SORTASCENDING or LVS_SORTDESCENDING styles in CreateWindowEx. These two styles order the items using their labels only. If you want to sort the items in other ways, you need to send LVM_SORTITEMS message to the listview control.

LVM_SORTITEMSwParam = lParamSortlParam = pCompareFunction

lParamSort is a user-defined value that will be passed to the compare function. You can use this value in any way you want.pCompareFunction is the address of the user-defined function that will decide the outcome of the comparison of items in the listview control. The function has the following prototype:

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

lParam1 and lParam2 are the values in lParam member of LV_ITEM that you specify when you insert the items into the listview control.lParamSort is the value in wParam you sent with LVM_SORTITEMS

When the listview control receives LVM_SORTITEMS message, it calls the compare function specified in lParam of the message when it needs to ask us for the result of comparison between two items. In short, the comparison function will decide which of the two items sent to it will precede the other. The rule is simple: if the function returns a negative value, the first item (represented by lParam1) should precede the other. If the function returns a positive value, the second item (represented by lParam2) should precede the first one. If both items are equal, it must return zero.

What makes this method work is the value in lParam of LV_ITEM structure. If you need to sort the items (such as when the user clicks on a column header), you need to think of a sorting scheme that makes use of the values in lParam member. In the

Page 177: 052 Iczelions_Win32_Assembly_Tutorials.pdf

177

example, I put the index of the item in this field so I can obtain other information about the item by sending LVM_GETITEMmessage. Note that when the items are rearranged, their indexes also change. So when the sorting is done in my example, I need to update the values in lParam to reflect the new indexes. If you want to sort the items when the user clicks on a column header, you need to process LVN_COLUMNCLICK notification message in your window procedure. LVN_COLUMNCLICK is passed to your window proc via WM_NOTIFY message.

Example:

This example creates a listview control and fills it with the names and sizes of the files in the current folder. The default view is the report one. In the report view, you can click on the column heads and the items will be sorted in ascending/descending order. You can select the view you want via the menu. When you double-click on an item, a message box showing the label of the item is displayed.

.386

.model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

IDM_MAINMENU equ 10000 IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT

RGB macro red,green,blue xor eax,eax mov ah,blue shl eax,8 mov ah,green mov al,red

endm

.data ClassName db "ListViewWinClass",0 AppName db "Testing a ListView Control",0 ListViewClassName db "SysListView32",0 Heading1 db "Filename",0 Heading2 db "Size",0 FileNamePattern db "*.*",0 FileNameSortOrder dd 0 SizeSortOrder dd 0 template db "%lu",0

Page 178: 052 Iczelions_Win32_Assembly_Tutorials.pdf

178

.data? hInstance HINSTANCE ? hList dd ? hMenu dd ?

.code start:

invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls

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, NULL mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,IDM_MAINMENU mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 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

InsertColumn proc

Page 179: 052 Iczelions_Win32_Assembly_Tutorials.pdf

179

LOCAL lvc:LV_COLUMN

mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvcor lvc.imask,LVCF_FMTmov lvc.fmt,LVCFMT_RIGHT mov lvc.pszText,offset Heading2 mov lvc.lx,100invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc ret

InsertColumn endp

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0 lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret

ShowFileInfo endp

FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD

invoke FindFirstFile,addr FileNamePattern,addr finddata .if eax!=INVALID_HANDLE_VALUE

mov FHandle,eax xor edi,edi .while eax!=0 test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO?

invoke ShowFileInfo,edi, addr finddata

Page 180: 052 Iczelions_Win32_Assembly_Tutorials.pdf

180

inc edi .endif invoke FindNextFile,FHandle,addr finddata

.endw invoke FindClose,FHandle

.endif ret

FillFileInfo endp

String2Dword proc uses ecx edi edx esi String:DWORD LOCAL Result:DWORD

mov Result,0 mov edi,String invoke lstrlen,String .while eax!=0

xor edx,edx mov dl,byte ptr [edi] sub dl,"0" mov esi,eax dec esi push eax mov eax,edx push ebx mov ebx,10 .while esi > 0

mul ebx dec esi

.endw pop ebx add Result,eax pop eax inc edi dec eax

.endw mov eax,Result ret

String2Dword endp

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM

mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 .if SortType==1

mov lvi.iSubItem,1

Page 181: 052 Iczelions_Win32_Assembly_Tutorials.pdf

181

invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi

.elseif SortType==2 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub eax,edi

.elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer

.else mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer,addr buffer1

.endif ret

CompareFunc endp

UpdatelParam proc uses edi LOCAL lvi:LV_ITEM

invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0 mov edi,eax mov lvi.imask,LVIF_PARAM mov lvi.iSubItem,0 mov lvi.iItem,0 .while edi>0 push lvi.iItem pop lvi.lParam invoke SendMessage,hList, LVM_SETITEM,0,addr lvi inc lvi.iItem dec edi

.endw ret

UpdatelParam endp

ShowCurrentFocus proc

Page 182: 052 Iczelions_Win32_Assembly_Tutorials.pdf

182

LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE

invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSEDmov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi invoke MessageBox,0, addr buffer,addr AppName,MB_OK ret

ShowCurrentFocus endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg==WM_CREATE

invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL

mov hList, eax invoke InsertColumn invoke FillFileInfo RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

.elseif uMsg==WM_COMMAND .if lParam==0

invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK mov edx,wParam and edx,0FFFFh push edx or eax,edx invoke SetWindowLong,hList,GWL_STYLE,eax pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED

.endif .elseif uMsg==WM_NOTIFY

push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList .if [edi].code==LVN_COLUMNCLICK

Page 183: 052 Iczelions_Win32_Assembly_Tutorials.pdf

183

assume edi:ptr NM_LISTVIEW .if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2

invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,1

.else invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,2

.endif .else .if FileNameSortOrder==0 || FileNameSortOrder==4

invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,3

.else invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,4

.endif .endif assume edi:ptr NMHDR

.elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus

.endif .endif pop edi

.elseif uMsg==WM_SIZEmov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

.elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL

.else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret

.endif xor eax,eax ret

WndProc endp end start

Analysis:

The first thing the program does when the main window is created is to create a listview control.

Page 184: 052 Iczelions_Win32_Assembly_Tutorials.pdf

184

.if uMsg==WM_CREATE invoke CreateWindowEx, NULL, addr ListViewClassName, NULL,

LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL mov hList, eax

We call CreateWindowEx, passing itthe name of the window class "SysListView32". The default view is the report view as specified by LVS_REPORT style.

invoke InsertColumn

After the listview control is created, we insert columns into it.

LOCAL lvc:LV_COLUMN

mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

We specify the label and the width of the first column, for storing the names of the files, in LV_COLUMN structure thus we need to set imask with LVCF_TEXT and LVCF_WIDTH flags. We fill pszText with the address of the label and lx with the width of the column, in pixels. When all is done, we send LVM_INSERTCOLUMN message to the listview control, passing the structure to it.

or lvc.imask,LVCF_FMTmov lvc.fmt,LVCFMT_RIGHT

When we are done with the insertion of the first column, we insert another column for storing the sizes of the files. Since we need the sizes to right-align in the column, we need to specify a flag in fmt member, LVCFMT_RIGHT. We must also specify LVCF_FMT flag in imask, in addition to LVCF_TEXT and LVCF_WIDTH.

mov lvc.pszText,offset Heading2 mov lvc.lx,100invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

The remaining code is simple. Put the address of the label in pszText and the width in lx. Then send LVM_INSERTCOLUMN message to the listview control, specifying the column number and the address of the structure.

When the columns are inserted, we can fill items in the listview control.

invoke FillFileInfo

FillFileInfo has the following code.

FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD

invoke FindFirstFile,addr FileNamePattern,addr finddata

We call FindFirstFile to obtain the information of the first file that matches the search criteria. FindFirstFile has the following prototype:

Page 185: 052 Iczelions_Win32_Assembly_Tutorials.pdf

185

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName is the address of the filename to search for. This string can contain wildcards. In our example, we use *.*, which amounts to search for all the files in the current folder.pWin32_Find_Data is the address of the WIN32_FIND_DATA structure that will be filled with information about the file (if found).

This function returns INVALID_HANDLE_VALUE in eax if no matching file is found. Otherwise it returns a search handle that will be used in subsequent FindNextFile calls.

.if eax!=INVALID_HANDLE_VALUE mov FHandle,eax xor edi,edi

If a file is found, we store the search handle in a variable and then zero out edi which will be used as the index into the items (row number).

.while eax!=0test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO?

In this tutorial, I don't want to deal with the folders yet so I filter them out by checking dwFileAttributes for files which have FILE_ATTRIBUTE_DIRECTORY flag set. If they are found, I skip to call FindNextFile.

invoke ShowFileInfo,edi, addr finddata inc edi

.endifinvoke FindNextFile,FHandle,addr finddata

.endw

We insert the name and size of the file into the listview control by calling ShowFileInfo function. Then we increase the current row number in edi. Lastly we proceed to call FindNextFile to search for the next file in the current folder until FindNextFilereturns 0 (meaning no more file is found).

invoke FindClose,FHandle .endif ret

FillFileInfo endp

When all files in the current folder are enumerated, we must close the search handle.

Now let's look at the ShowFileInfo function. This function accepts two parameters, the index of the item (row number) and the address of WIN32_FIND_DATA structure.

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA

Store the address of WIN32_FIND_DATA structure in edi.

Page 186: 052 Iczelions_Win32_Assembly_Tutorials.pdf

186

mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0

We will supply the label of the item and the value in lParam so we put LVIF_TEXT and LVIF_PARAM flags into imask. Next we set the iItem to the row number passed to the function and since this is the main item, we must filliSubItem with 0 (column 0).

lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam

Next we put the address of the label, in this case, the name of the file in WIN32_FIND_DATA structure, into pszText. Because we will implement sorting in the listview control, we must fill lParam with a value. I choose to put the row number into this member so I can retrieve the item info by its index.

invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

When all necessary fields in LV_ITEM are filled, we send LVM_INSERTITEM message to the listview control to insert the item into it.

mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax

We will set the subitem associated with the item just inserted into the second column. A subitem can only have a label. Thus we specify LVIF_TEXT in imask. Then we specify the column that the subitem should reside in iSubItem. In this case, we set it to 1 by incrementing iSubItem. The label we will use is the size of the file. However, we must convert it to a string first by calling wsprintf. Then we put the address of the string into pszText.

invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret

ShowFileInfo endp

When all necessary fields in LV_ITEM are filled, we send LVM_SETITEM message to the listview control, passing to it the address of the LV_ITEM structure. Note that we use LVM_SETITEM, not LVM_INSERTITEM because a subitem is considered as a property of an item. Thus we *set* the property of the item, not inserting a new item.

When all items are inserted into the listview control, we set the text and background colors of the listview control.

RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

Page 187: 052 Iczelions_Win32_Assembly_Tutorials.pdf

187

I use RGB macro to convert the red, green,blue values into eax and use it to specify the color we need. We set the foreground and background colors of the text with LVM_SETTEXTCOLOR and LVM_SETTEXTBKCOLOR messages. We set the background color of the listview control by sending LVM_SETBKCOLOR message to the listview control.

invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

We will let the user chooses the views he wants via the menu. Thus we must obtain the menu handle first. To help the user track the current view, we put a radio button system in our menu. The menu item that reflects the current view will be preceded by a radio button. That's why we call CheckMenuRadioItem. This function will put a radio button before a menu item.

Note that we create the listview control with width and height equal to 0. It will be resized later whenever the parent window is resized. This way, we can ensure that the size of the listview control will always match that of the parent window. In our example, we want the listview control to fill the whole client area of the parent window.

.elseif uMsg==WM_SIZEmov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

When the parent window receives WM_SIZE message, the low word of lParam contains the new width of the client area and the high word the new height. Then we call MoveWindow to resize the listview control to cover the whole client area of the parent window.

When the user selects a view in the menu. We must change the view in the listview control accordingly. We accomplish this by setting a new style in the listview control with SetWindowLong.

.elseif uMsg==WM_COMMAND .if lParam==0

invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK

The first thing we do is to obtain the current styles of the listview control. Then we clear the old view style from the returned style flags. LVS_TYPEMASK is a constant that is the combined value of all 4 view style constants (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Thus when we perform and operation on the current style flags with the value "not LVS_TYPEMASK", it amounts to clearing away the current view style.

In designing the menu, I cheat a little. I use the view style constants as the menu IDs.

IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT

Thus when the parent window receives WM_COMMAND message, the desired view style is in the low word of wParam as the menu ID.

mov edx,wParam and edx,0FFFFh

Page 188: 052 Iczelions_Win32_Assembly_Tutorials.pdf

188

We have the desired view style in the low word of wParam. All we have to do is to zero out the high word.

push edx or eax,edx

And add the desired view style to the existing styles (minus the current view style) of the listview control.

invoke SetWindowLong,hList,GWL_STYLE,eax

And set the new styles with SetWindowLong.

pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED

.endif

We also need to put the radio button in front of the selected view menu item. Thus we call CheckMenuRadioItem, passing the current view style (double as menu ID) to it.

When the user clicks on the column headers in the report view, we want to sort the items in the listview control. We must respond to WM_NOTIFY message.

.elseif uMsg==WM_NOTIFY push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList

When we receive WM_NOTIFY message, lParam contains the pointer to an NMHDR structure. We can check if this message is from the listview control by comparing the hwndFrom member of NMHDR to the handle to the listview control. If they match, we can assume that the notification came from the listview control.

.if [edi].code==LVN_COLUMNCLICK assume edi:ptr NM_LISTVIEW

If the notification is from the listview control, we check if the code is LVN_COLUMNCLICK. If it is, it means the user clicks on a column header. In the case that the code is LVN_COLUMNCLICK, we can assume that lParam contains the pointer to an NM_LISTVIEW structure which is a superset of the NMHDR structure. We then need to know on which column header the user clicks. Examination of iSubItem member reveals this info. The value in iSubItem can be treated as the column number, starting from 0.

.if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2

In the case iSubItem is 1, it means the user clicks on the second column, size. We use state variables to keep the current status of the sorting order. 0 means "no sorting yet", 1 means "sort ascending", 2 means "sort descending". If the items/subitems in the column are not sorted before, or sorted descending, we set the sorting order to ascending.

invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

We send LVM_SORTITEMS message to the listview control, passing 1 in wParam and the address of our comparison function in lParam. Note that the value in wParam is user-defined, you can use it in any way you like. I use it as the sorting method in this example. We will take a look at the comparison function first.

Page 189: 052 Iczelions_Win32_Assembly_Tutorials.pdf

189

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM

mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256

In the comparison function, the listview control will pass lParams (in LV_ITEM) of the two items it needs to compare to us in lParam1 and lParam2. You'll recall that we put the index of the item in lParam. Thus we can obtain information about the items by querying the listview control using the indexes. The info we need is the labels of the items/subitems being sorted. Thus we prepare an LV_ITEM structure for such purpose, specifying LVIF_TEXT in imask and the address of the buffer in pszTextand the size of the buffer in cchTextMax.

.if SortType==1 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

If the value in SortType is 1 or 2, we know that the size column is clicked. 1 means sort the items according to their sizes in ascending order. 2 means the reverse. Thus we specify iSubItem as 1 ( to specify the size column) and send LVM_GETITEMTEXT message to the listview control to obtain the label (size string) of the subitem.

invoke String2Dword,addr buffer mov edi,eax

Covert the size string into a dword value with String2Dword which is the function I wrote. It returns the dword value in eax. We store it in edi for comparison later.

invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi

Do likewise with the value in lParam2. When we have the sizes of the two files, we can then compare them.The rule of the comparison function is as follows:

� If the first item should precede the other, you must return a negative value in eax� If the second item should precede the first one, you must return a positive value in eax� If both items are equal, you must return zero in eax.

In this case, we want to sort the items according to their sizes in ascending order. Thus we can simply subtract the size of the first item with that of the second one and return the result in eax.

.elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer

Page 190: 052 Iczelions_Win32_Assembly_Tutorials.pdf

190

In case the user clicks the filename column, we must compare the names of the files. We first obtain the filenames and then compare them with lstrcmpi function. We can return the return value of lstrcmpi without any modification since it also uses the same rule of comparison, eg. negative value in eax if the first string is less than the second string.

When the items were sorted, we need to update the lParam values of all items to reflect the new indexes by calling UpdatelParam function.

invoke UpdatelParam mov SizeSortOrder,1

This function simply enumerates all items in the listview control and updates the values in lParam with the new indexes. We need to do this else the next sort will not work as expected because our assumption is that the value in lParam is the index of the item.

.elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus

.endif

When the user double-clicks at an item, we want to display a message box with the label of the item on it. We must check if the code in NMHDR is NM_DBLCLK. If it is, we can proceed to obtain the label and display it in a message box.

ShowCurrentFocus proc LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE

invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

How do we know which item is double-clicked? When an item is clicked or double-clicked, its state is set to "focused". Even if many items are hilited (selected), only one of them has got the focus. Our job than is to find the item that has the focus. We do this by sending LVM_GETNEXTITEM message to the listview control, specifying the desired state in lParam. -1 in wParam means search all items. The index of the item is returned in eax.

mov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi

We then proceed to obtain the label by sending LVM_GETITEM message to the listview control.

invoke MessageBox,0, addr buffer,addr AppName,MB_OK

Lastly, we display the label in a message box.

If you want to know how to use icons in the listview control, you can read about it in my treeview tutorial. The steps are just about the same.

Page 191: 052 Iczelions_Win32_Assembly_Tutorials.pdf

191

Tutorial 32: Multiple Document Interface (MDI)This tutorial shows you how to create MDI application. It's actually not too difficult to do. Download the example.

Theory:

Multiple Document Interface (MDI) is a specification for applications that handle multple documents at the same time. You are familiar with Notepad: It's an example of Single Document Interface (SDI). Notepad can handle only one document at a time. If you want to open another document, you have to close the previous one first. As you can imagine, it's rather cumbersome. Contrast it with Microsoft Word: Word can open arbitrary documents at the same time and let the user choose which document to use. Microsoft Word is an example of Multiple Document Interface (MDI).

MDI application has several characteristics that are distinctive. I'll list some of them:

� Within the main window, there can be multiple child windows in the client area. All child windows are clipped to the client area.

� When you minimize a child window, it minimizes to the lower left corner of the client area of the main window.� When you maximize achild window, its title merges with that of the main window.� You can close a child window by pressing Ctrl+F4 and switch the focus between the child windows by pressing

Ctrl+Tab

The main window that contains the child windows is called a frame window. Its client area is where the child windows live, hence the name "frame". Its job is a little more elaborate than a usual window because it needs to handle some coordination for MDI.

To control an arbitrary number of child windows in your client area, you need a special window called client window. You can think of this client window as a transparent window that covers the whole client area of the frame window. It's this client window that is the actual parent of those MDI child windows. The client window is the real supervisor of the MDI child windows.

Frame Window

|

Client Window

|

| | | | |

MDI Child 1 MDI Child 2 MDI Child 3 MDI Child 4 MDI Child n

Figure 1. The hierachy of an MDI application

Creating the Frame Window

Now we can turn our attention to the detail. First of all you need to create a frame window. It's created the same way as the normal window: by calling CreateWindowEx. There are two major differences from a normal window.

The first difference is that you MUST call DefFrameProc instead of DefWindowProc to process the Windows messages your window don't want to handle. This is one way to let Windows do the dirty job of maintaining MDI application for you. If you forget to use DefFrameProc, your application won't get the MDI feature. Period. DefFrameProc has the following syntax:

DefFrameProc proc hwndFrame:DWORD,

Page 192: 052 Iczelions_Win32_Assembly_Tutorials.pdf

192

hwndClient:DWORD,uMsg:DWORD, wParam:DWORD, lParam:DWORD

If you compare DefFrameProc with DefWindowProc, you'll notice that the only difference between them is that DefFrameProc has 5 parameters while DefWindowProc has only 4. The extra parameter is the handle to the client window. This handle is necessary so Windows can send MDI-related messages to the client window.

The second difference is that, you must call TranslateMDISysAccel in the message loop of your frame window. This is necessary if you want Windows to handle MDI-related accelerator key strokes such as Ctrl+F4, Ctrl+Tab for you. It has the following syntax:

TranslateMDISysAccel proc hwndClient:DWORD,lpMsg:DWORD

The first parameter is the handle to the client window. This should not come as a surprise to you because it's the client window that is the parent of all MDI child windows. The second parameter is the address of the MSG structure you filled by calling GetMessage. The idea is to pass the MSG structure to the client window so it could examine if the MSG structure contains the MDI-related keypresses. If so, it processes the message itself and returns a non-zero value, otherwise it returns FALSE.

The steps in creating the frame window can be summarized as follows:

1. Fill in the WNDCLASSEX structure as usual2. Register the frame window class by calling RegisterClassEx3. Create the frame window by calling CreateWindowEx4. Within the message loop, call TranslateMDISysAccel.5. Within the window procedure, pass the unprocessed messages to DefFrameProc instead of DefWindowProc.

Creating the Client Window

Now that we have the frame window, we can create the client window. The client window class is pre-registered by Windows. The class name is "MDICLIENT". You also need to pass the address of a CLIENTCREATESTRUCT structure to CreateWindowEx. This structure has the following definition:

CLIENTCREATESTRUCT structhWindowMenu dd ?idFirstChild dd ?

CLIENTCREATESTRUCT ends

hWindowMenu is the handle to the submenu that Windows will append the list of MDI child window names. This feature requires a little explanation. If you ever use an MDI application like Microsoft Word before, you'll notice that there is a submenu named "window" which, on activation, displays various menuitems related to window management and at the bottom, the list of the MDI child window currently opened. That list is internally maintained by Windows itself: you don't have to do anything special for it. Just pass the handle of the submenu you want the list to appear in hWindowMenu and Windows will handle the rest. Note that the submenu can be ANY submenu:it doesn't have to be the one that is named "window". The bottom line is that, you should pass the handle to the submenu you want the window list to appear. If you don't want the list, just put NULL in hWindowMenu. You get the handle to the submenu by calling GetSubMenu.

idFirstChild is the ID of the first MDI child window. Windows increments the ID for each new MDI child window the application created. For example, if you pass 100 to this field, the first MDI child window will have the ID of 100, the second one will have the ID of 101 and so on. This ID is sent to the frame window via WM_COMMAND when the MDI child window is selected from the window list. Normally you'll pass this "unhandled" WM_COMMAND messages to DefFrameProc. I use the word "unhandled" because the menuitems in the window list are not created by your application thus your application doesn't know their IDs and doesn't have the handler for them. This is another special case for the MDI frame window: if you have the window list, you must modify your WM_COMMAND handler a bit like this:

Page 193: 052 Iczelions_Win32_Assembly_Tutorials.pdf

193

.elseif uMsg==WM_COMMAND.if lParam==0 ; this message is generated from a menu

mov eax,wParam.if ax==IDM_CASCADE

......elseif ax==IDM_TILEVERT

......else

invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, lParamret

.endif

Normally, you would just ignore the messages from unhandled cases. But In the MDI case, if you ignore them, when the user clicks on the name of an MDI child window in the window list, that window won't become active. You need to pass them to DefFrameProc so they can be handled properly.

A caution on the value of idFirstChild: you should not use 0. Your window list will not behave properly, ie. the check mark will not appear in front of the name of the first MDI child even though it's active. Choose a safe value such as 100 or above.

Having filled in the CLIENTCREATESTRUCT structure, you can create the client window by calling CreateWindowEx with the predefined class name,"MDICLIENT", and passing the address of the CLIENTCREATESTRUCT structure in lParam. You must also specify the handle to the frame window in the hWndParent parameter so Windows knows the parent-child relationship between the frame window and the client window. The window styles you should use are: WS_CHILD ,WS_VISIBLE and WS_CLIPCHILDREN. If you forget WS_VISIBLE, you won't see the MDI child windows even if they were created successfully.

The steps in creating the client window are as follows:

1. Obtain the handle to the submenu that you want to append the window list to.2. Put the value of the menu handle along with the value you want to use as the ID of the first MDI child window in a

CLIENTCREATESTRUCT structure3. call CreateWindowEx with the class name "MDICLIENT", passing the address of the CLIENTCREATESTRUCT

structure you just filled in in lParam.

Creating the MDI Child Window

Now you have both the frame window and the client window. The stage is now ready for the creation of the MDI child window. There are two ways to do that.

� You can send WM_MDICREATE message to the client window, passing the address of a structure of typeMDICREATESTRUCT in wParam. This is the easiest and the usual method of MDI child window creation.

� .data?mdicreate MDICREATESTRUCT <>....

.code.....[fill the members of mdicreate]......invoke SendMessage, hwndClient, WM_MDICREATE,addr mdicreate,0

SendMessage will return the handle of the newly created MDI child window if successful. You don't need to save the handle though. You can obtain it by other means if you want to. MDICREATESTRUCT has the following definition.

MDICREATESTRUCT STRUCTszClass DWORD ?szTitle DWORD ?hOwner DWORD ?

Page 194: 052 Iczelions_Win32_Assembly_Tutorials.pdf

194

x DWORD ?y DWORD ?lx DWORD ?ly DWORD ?style DWORD ?lParam DWORD ?MDICREATESTRUCT ENDS

szClass the address of the window class you want to use as the template for the MDI child window.

szTitle the address of the text you want to appear in the title bar of the child window

hOwner the instance handle of the application

x,y,lx,ly the upper left coordinate and the width and height of the child window

style child window style. If you create the client window with MDIS_ALLCHILDSTYLES, you can use any window style.

lParam an application-defined 32-bit value. This is a way of sharing values among MDI windows. If you don't need to use it, set it to NULL

� You can call CreateMDIWindow. This function has the following syntax:

CreateMDIWindow proto lpClassName:DWORDlpWindowName:DWORDdwStyle:DWORDx:DWORDy:DWORDnWidth:DWORDnHeight:DWORDhWndParent:DWORDhInstance:DWORDlParam:DWORD

If you look closely at the parameters, you'll find that they are identical to the members of MDICREATESTRUCT structure, except for the hWndParent. Essentially it's the same number of parameters you pass with WM_MDICREATE. MDICREATESTRUCT doesn't have the hWndParent field because you must pass the whole structure to the correct client window with SendMessage anyway.

At this point, you may have some questions: which method should I use? What is the difference between the two? Here is the answer:

The WM_MDICREATE method can only create the MDI child window in the same thread as the calling code. For example, if your application has 2 threads, and the first thread creates the MDI frame window, if the second thread wants to create an MDI child, it must do so with CreateMDIChild: sending WM_MDICREATE message to the first thread won't work. If your application is single-threaded, you can use either method. (Thanks yap for the correction - 04/24/2002)

A little more detail needs to be covered about the window procedure of the MDI child. As with the frame window case, you must not call DefWindowProc to handle the unprocessed messages. Instead, you must use DefMDIChildProc. This function has exactly the same parameters as DefWindowProc.

In addition to WM_MDICREATE, there are other MDI-related window messages. I'll list them below:

WM_MDIACTIVATE

This message can be sent by the application to the client window to instruct the client window to activate the selected MDI child. When the client window receives the message, it activates the selected MDI child window and sends WM_MDIACTIVATE to the child being deactivated and activated. The use of this message is two-fold: it can be used by the application

Page 195: 052 Iczelions_Win32_Assembly_Tutorials.pdf

195

to activate the desired child window. And it can be used by the MDI child window itself as the indicator that it's being activated/deactivated. For example, if each MDI child window has different menu, it can use this opportunity to change the menu of the frame window when it's activated/deactivated.

WM_MDICASCADEWM_MDITILE

WM_MDIICONARRANGE

These messages handle the arrangement of the MDI child windows. For example, if you want the MDI child windows to arrange themselves in cascading style, send WM_MDICASCADE to the client window.

WM_MDIDESTROY

Send this message to the client window to destroy an MDI child window. You should use this message instead of calling DestroyWindow because if the MDI child window is maxmized, this message will restore the tile of the frame window. If you use DestroyWindow, the title of the frame window will not be restored.

WM_MDIGETACTIVE Send this message to retrieve the handle of the currently active MDI child window.

WM_MDIMAXIMIZEWM_MDIRESTORE

Send WM_MDIMAXIMIZE to maximize the MDI child window and WM_MDIRESTORE to restore it to previous state. Always use these messages for the operations. If you use ShowWindow with SW_MAXIMIZE, the MDI child window will maximize fine but it will have the problem when you try to restore it to previous size. You can minimize the MDI child window with ShowWindow without problem, however.

WM_MDINEXT Send this message to the client window to activate the next or the previous MDI child window according to the values in wParam and lParam.

WM_MDIREFRESHMENU Send this message to the client window to refresh the menu of the frame window. Note that you must call DrawMenuBar to update the menu bar after sending this message.

WM_MDISETMENU

Send this message to the client window to replace the whole menu of the frame window or just the window submenu. You must use this message instead of SetMenu. After sending this message, you must call DrawMenuBar to update the menu bar. Normally you will use this message when the active MDI child window has its own menu and you want it to replace the menu of the frame window while the MDI child window is active.

I'll review the steps in creating an MDI application for you again below.

1. Register the window classes, both the frame window class and the MDI child window class2. Create the frame window with CreateWindowEx.3. Within the message loop, call TranslateMDISysAccel to process the MDI-related accelerator keys4. Within the window procedure of the frame window, call DefFrameProc to handle ALL messages unhandled by your

code.5. Create the client window by calling CreateWindowEx using the name of the predefined window class,

"MDICLIENT", passing the address of a CLIENTCREATESTRUCT structure in lParam. Normally, you would create the client window within the WM_CREATE handler of the frame window proc

6. You can create an MDI child window by sending WM_MDICREATE to the client window or, alternatively, by calling CreateMDIWindow.

7. Within the window proc of the MDI child window, pass all unhandled messages to DefMDIChildProc.8. Use MDI version of the messages if it exists. For example, use WM_MDIDESTROY instead of calling

DestroyWindow

Example:

.386

.model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc

Page 196: 052 Iczelions_Win32_Assembly_Tutorials.pdf

196

include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const IDR_MAINMENU equ 101 IDR_CHILDMENU equ 102 IDM_EXIT equ 40001 IDM_TILEHORZ equ 40002 IDM_TILEVERT equ 40003IDM_CASCADE equ 40004 IDM_NEW equ 40005 IDM_CLOSE equ 40006

.data ClassName db "MDIASMClass",0 MDIClientName db "MDICLIENT",0 MDIChildClassName db "Win32asmMDIChild",0 MDIChildTitle db "MDI Child",0 AppName db "Win32asm MDI Demo",0 ClosePromptMessage db "Are you sure you want to close this window?",0

.data? hInstance dd ? hMainMenu dd ? hwndClient dd ? hChildMenu dd ? mdicreate MDICREATESTRUCT <> hwndFrame dd ?

.code start:

invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG ;============================================= ; Register the frame window class ;============================================= 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 hInstancepop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE mov wc.lpszMenuName,IDR_MAINMENUmov wc.lpszClassName,OFFSET ClassNameinvoke 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 ;================================================

Page 197: 052 Iczelions_Win32_Assembly_Tutorials.pdf

197

; Register the MDI child window class ;================================================ mov wc.lpfnWndProc,offset ChildProc mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszClassName,offset MDIChildClassName invoke RegisterClassEx,addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,\hInst,NULL

mov hwndFrame,eax invoke LoadMenu,hInstance, IDR_CHILDMENU mov hChildMenu,eax invoke ShowWindow,hwndFrame,SW_SHOWNORMAL invoke UpdateWindow, hwndFrame .while TRUE

invoke GetMessage,ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMDISysAccel,hwndClient,addr msg .if !eax

invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg

.endif .endw invoke DestroyMenu, hChildMenu mov eax,msg.wParam ret

WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL ClientStruct:CLIENTCREATESTRUCT .if uMsg==WM_CREATE

invoke GetMenu,hWnd mov hMainMenu,eax invoke GetSubMenu,hMainMenu,1 mov ClientStruct.hWindowMenu,eax mov ClientStruct.idFirstChild,100 INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,\

WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\

CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\hInstance,addr ClientStruct

mov hwndClient,eax ;======================================= ; Initialize the MDICREATESTRUCT ;======================================= mov mdicreate.szClass,offset MDIChildClassName mov mdicreate.szTitle,offset MDIChildTitle push hInstance pop mdicreate.hOwner mov mdicreate.x,CW_USEDEFAULT mov mdicreate.y,CW_USEDEFAULT mov mdicreate.lx,CW_USEDEFAULT mov mdicreate.ly,CW_USEDEFAULT

.elseif uMsg==WM_COMMAND .if lParam==0

mov eax,wParam .if ax==IDM_EXIT

invoke SendMessage,hWnd,WM_CLOSE,0,0 .elseif ax==IDM_TILEHORZ

Page 198: 052 Iczelions_Win32_Assembly_Tutorials.pdf

198

invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0

.elseif ax==IDM_TILEVERT invoke

SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0 .elseif ax==IDM_CASCADE

invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0

.elseif ax==IDM_NEW invoke SendMessage,hwndClient,WM_MDICREATE,0,addr

mdicreate .elseif ax==IDM_CLOSE

invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0 invoke SendMessage,eax,WM_CLOSE,0,0

.else invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParamret

.endif .endif

.elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL

.else invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam ret

.endif xor eax,eax ret

WndProc endp

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_MDIACTIVATE

mov eax,lParam .if eax==hChild

invoke GetSubMenu,hChildMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx

.else invoke GetSubMenu,hMainMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx

.endif invoke DrawMenuBar,hwndFrame

.elseif uMsg==WM_CLOSE invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO .if eax==IDYES

invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0 .endif

.else invoke DefMDIChildProc,hChild,uMsg,wParam,lParam ret

.endif xor eax,eax ret

ChildProc endp end start

Analysis:

Page 199: 052 Iczelions_Win32_Assembly_Tutorials.pdf

199

The first thing the program does is to register the window classes of the frame window and the MDI child window. After that, it calls CreateWindowEx to create the frame window. Within the WM_CREATE handler of the frame window, we create the client window:

LOCAL ClientStruct:CLIENTCREATESTRUCT .if uMsg==WM_CREATE

invoke GetMenu,hWnd mov hMainMenu,eax invoke GetSubMenu,hMainMenu,1 mov ClientStruct.hWindowMenu,eax mov ClientStruct.idFirstChild,100 invoke CreateWindowEx,NULL,ADDR MDIClientName,NULL,\

WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\hInstance,addr ClientStruct

mov hwndClient,eax

It calls GetMenu to obtain the handle to the menu of the frame window, to be used in the GetSubMenu call. Note that we pass the value 1 to GetSubMenu because the submenu we want the window list to appear is the second submenu. Then we fill the members of the CLIENTCREATESTRUCT structure.Next, we initialize the MDICLIENTSTRUCT structure. Note that we don't need to do it here. It's only convenient to do it in WM_CREATE.

mov mdicreate.szClass,offset MDIChildClassName mov mdicreate.szTitle,offset MDIChildTitle push hInstance pop mdicreate.hOwner mov mdicreate.x,CW_USEDEFAULT mov mdicreate.y,CW_USEDEFAULT mov mdicreate.lx,CW_USEDEFAULT mov mdicreate.ly,CW_USEDEFAULT

After the frame window is created (and also the client window), we call LoadMenu to load the child window menu from the resource. We need to get this menu handle so we can replace the menu of the frame window with it when an MDI child window is present. Don't forget to call DestroyMenu on the handle before the application exits to Windows. Normally Windows will free the menu associated with a window automatically when the application exits but in this case, the child window menu is not associated with any window thus it will still occupy valuable memory even after the application exits.

invoke LoadMenu,hInstance, IDR_CHILDMENU mov hChildMenu,eax ........invoke DestroyMenu, hChildMenu

Within the message loop, we call TranslateMDISysAccel.

.while TRUE invoke GetMessage,ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMDISysAccel,hwndClient,addr msg.if !eax

invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg

.endif .endw

If TranslateMDISysAccel returns a non-zero value, it means the message was already handled by Windows itself so you don't need to do anything to the message. If it returns 0, the message is not MDI-related and thus should be handled as usual.

Page 200: 052 Iczelions_Win32_Assembly_Tutorials.pdf

200

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM ......else

invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam ret

.endifxor eax,eaxret

WndProc endp

Note that within the window procedure of the frame window, we call DefFrameProc to handle the messages we are not interested in.

The bulk of the window procedure is the WM_COMMAND handler. When the user selects "New" from the File menu, we create a new MDI child window.

.elseif ax==IDM_NEW invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate

In our example, we create the MDI child window by sending WM_MDICREATE to the client window, passing the address of the MDICREATESTRUCT structure in lParam.

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_MDIACTIVATE

mov eax,lParam .if eax==hChild

invoke GetSubMenu,hChildMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx

.else invoke GetSubMenu,hMainMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx

.endif invoke DrawMenuBar,hwndFrame

When the MDI child window is created, it monitors WM_MDIACTIVATE to see if it's the active window. It does this by comparing the value of the lParam which contains the handle of the active child window with its own handle. If they match, it's the active window and the next step is to replace the menu of the frame window to its own. Since the original menu will be replaced, you have to tell Windows again in which submenu the window list should appear. That's why we must call GetSubMenu again to retrieve the handle to the submenu. We send WM_MDISETMENU message to the client window to achieve the desired result. wParam of WM_MDISETMENU contains the handle of the menu you would like to replace the original menu. lParam contains the handle of the submenu you want the window list to appear. Right after sending WM_MDISETMENU, we call DrawMenuBar to refresh the menu else your menu will be a mess.

.else invoke DefMDIChildProc,hChild,uMsg,wParam,lParam ret

.endif Within the window procedure of the MDI child window, you must pass all unhandled messages to DefMDIChildProcinstead of DefWindowProc.

.elseif ax==IDM_TILEHORZ invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0

.elseif ax==IDM_TILEVERT invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0

.elseif ax==IDM_CASCADE invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0

Page 201: 052 Iczelions_Win32_Assembly_Tutorials.pdf

201

When the user selects one of the menuitems in the window submenu, we send the corresponding message to the client window. If the user chooses to tile the windows, we send WM_MDITILE to the client window, specifying in wParam what kind of tiling we want. WM_CASCADE is similar.

.elseif ax==IDM_CLOSE invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0 invoke SendMessage,eax,WM_CLOSE,0,0

If the user chooses "Close" menuitem, we must obtain the handle of the currently active MDI child window first by sending WM_MDIGETACTIVE to the client window. The return value in eax is the handle of the currently active MDI child window. After that, we send WM_CLOSE to that window.

.elseif uMsg==WM_CLOSE invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO .if eax==IDYES

invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0 .endif

Within the window procedure of the MDI child, when WM_CLOSE is received, it displays a message box asking the user if he really wants to close the window. If the answer is yes, we send WM_MDIDESTROY to the client window. WM_MDIDESTROY closes the MDI child window and restores the title of the frame window.

Page 202: 052 Iczelions_Win32_Assembly_Tutorials.pdf

202

Tutorial 33: RichEdit Control: Basics

There are lots of request on tutorials about RichEdit controls. Finally I have played with it enough to think I can write tutorials about it. So here it is: the first RichEdit tutorial. The tutorials will describe nearly everything there is to know about RichEdit control or at least as much as I know it. The amount of information is rather large so I divide it into several parts, this tutorial being the first part. In this tutorial, you'll learn what a RichEdit control is, how to create it and how to load/save data to/from it.

Download the example.

Theory

A richedit control can be thought of as a souped-up edit control. It provides many desirable features that are lacking from the plain simple edit control, for example, the ability to use multiple font face/size, multiple-level undo/redo, search-for-text operation, OLE-embedded objects, drag-and-drop editing support, etc. Since the richedit control has so many features, it's stored in a separate DLL. This also means that, to use it, you can't just call InitCommonControls like other common controls. You have to call LoadLibrary to load the richedit DLL.

The problem is that there are three versions of richedit control up till now. Version 1,2, and 3. The table below shows you the name of the DLL for each version.

DLL Name RichEdit version Richedit Class NameRiched32.dll 1.0 RICHEDITRichEd20.dll 2.0 RICHEDIT20ARichEd20.dll 3.0 RICHEDIT20A

You can notice that richedit version 2 and 3 use the same DLL name. They also use the same class name! This can pose a problem if you want to use specific features of richedit 3.0. Up to now, I haven't found an official method of differentiating between version 2.0 and 3.0. However, there is a workaround which works ok, I'll show you later.

.dataRichEditDLL db "RichEd20.dll",0

......data?

hRichEditDLL dd ?.code

invoke LoadLibrary,addr RichEditDLLmov hRichEditDLL,eax......invoke FreeLibrary,hRichEditDLL

When the richedit dll is loaded, it registers the RichEdit window class. Thus it's imperative that you load the DLL before you create the control. The names of the richedit control classes are also different. Now you may have a question: how do I know which version of richedit control should I use? Using the latest version is not always appropriate if you don't require the extra features. So below is the table that shows the features provided by each version of richedit control.

Feature Version 1.0 Version 2.0 Version 3.0

selection bar x x x

unicode editing x x

Page 203: 052 Iczelions_Win32_Assembly_Tutorials.pdf

203

character/paragraph formatting x x x

search for text forward forward/backward forward/backward

OLE embedding x x x

Drag and drop editing x x x

Undo/Redo single-level multi-level multi-level

automatic URL recognition x x

Accelerator key support x x

Windowless operation x x

Line break CRLF CR only CR only (can emulate version 1.0)

Zoom x

Paragraph numbering x

simple table x

normal and heading styles x

underline coloring x

hidden text x

font binding x

The above table is by no means comprehensive: I only list the important features.

Creating the richedit control

After loading the richedit dll, you can call CreateWindowEx to create the control. You can use edit control styles and common window styles in CreateWindowEx except ES_LOWERCASE, ES_UPPERCASE and ES_OEMCONVERT.

.constRichEditID equ 300

.dataRichEditDLL db "RichEd20.dll",0RichEditClass db "RichEdit20A",0...

.data?hRichEditDLL dd ?hwndRichEdit dd ?

.code.....invoke LoadLibrary,addr RichEditDLLmov hRichEditDLL,eaxinvoke CreateWindowEx,0,addr RichEditClass,WS_VISIBLE or ES_MULTILINE or WS_CHILD

or WS_VSCROLL or WS_HSCROLL, \

CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,RichEditID,hInstance,0

mov hwndRichEdit,eax

Page 204: 052 Iczelions_Win32_Assembly_Tutorials.pdf

204

Setting default text and background color

You may have the problem with setting text color and the background color of the edit control. But this problem has been remedy in richedit control. To set the background color of the richedit control, you send EM_SETBKGNDCOLOR to the richedit control. This message has the following syntax.

wParam == color option. The value of 0 in this parameter specifies that Windows uses the color value in lParamas the background color. If this value is nonzero, Windows uses the Windows system background color. Since we send this message to change the background color, we must pass 0 in wParam.lParam == specifies the COLORREF structure of the color you want to set if wParam is 0.

For example, if I want to set the background color to pure blue, I would issue this following line:

invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,0FF0000h

To set the text color, richedit control provides another new message, EM_SETCHARFORMAT, for the job. This message controls the text formatting of a range to characters in selection or all text in the control. This message has the following syntax:

wParam == formatting options:

SCF_ALL The operation affects all text in the control.

SCF_SELECTION The operation affects only the text in selection

SCF_WORD or SCF_SELECTION

Affects the word in selection. If the selection is empy, ie, only the caret is in the word, the operation affects that word. SCF_WORD flag must be used with SCF_SELECTION.

lParam == pointer to a CHARFORMAT or CHARFORMAT2 structure that specifies the text formatting to be applied. CHARFORMAT2 is available for richedit 2.0 and above only. This doesn't mean that you must use CHARFORMAT2 with RichEdit 2.0 or above. You can still use CHARFORMAT if the added features in CHARFORMAT2 are not necessary for your need.

CHARFORMATA STRUCT cbSize DWORD ? dwMask DWORD ? dwEffects DWORD ? yHeight DWORD ? yOffset DWORD ? crTextColor COLORREF ? bCharSet BYTE ? bPitchAndFamily BYTE ? szFaceName BYTE LF_FACESIZE dup(?) _wPad2 WORD ?

CHARFORMATA ENDS

Field Name Description

cbSize The size of the structure. RichEdit control uses this field to determine the version of the structure whether it is CHARFORMAT or CHARFORMAT2

dwMask

Bit flags that determine which of the following members are valid.

CFM_BOLD The CFE_BOLD value of the dwEffects member is valid

CFM_CHARSET The bCharSet member is valid.

Page 205: 052 Iczelions_Win32_Assembly_Tutorials.pdf

205

CFM_COLOR The crTextColor member and the CFE_AUTOCOLOR value of the dwEffects member are valid

CFM_FACE The szFaceName member is valid.

CFM_ITALIC The CFE_ITALIC value of the dwEffects member is valid

CFM_OFFSET The yOffset member is valid

CFM_PROTECTED The CFE_PROTECTED value of the dwEffects member is valid

CFM_SIZE The yHeight member is valid

CFM_STRIKEOUT The CFE_STRIKEOUT value of the dwEffects member is valid.

CFM_UNDERLINE The CFE_UNDERLINE value of the dwEffects member is valid

dwEffects

The character effects. Can be the combination of the following values

CFE_AUTOCOLOR Use the system text color

CFE_BOLD Characters are bold

CFE_ITALIC Characters are italic

CFE_STRIKEOUT Characters are struck.

CFE_UNDERLINE Characters are underlined

CFE_PROTECTED Characters are protected; an attempt to modify them will cause an EN_PROTECTED notification message.

yHeight Character height, in twips (1/1440 of an inch or 1/20 of a printer's point).

yOffset Character offset, in twips, from the baseline. If the value of this member is positive, the character is a superscript; if it is negative, the character is a subscript.

crTextColor Text color. This member is ignored if the CFE_AUTOCOLOR character effect is specified.

bCharSet Character set value

bPitchAndFamily Font family and pitch.

szFaceName Null-terminated character array specifying the font name

_wPad2 Padding

From examination of the structure, you'll see that we can change the text effects (bold,italic, strikeout,underline), text color (crTextColor) and font face/size/character set. A notable flag is CFE_RPOTECTED. The text with this flag is marked as protected which means that when the user tries to modify it, EN_PROTECTED notification message will be sent to the parent window. And you can allow the change to happen or not.

CHARFORMAT2 adds more text styles such as font weight, spacing,text background color, kerning, etc. If you don't need these extra features, simply use CHARFORMAT.

To set text formatting, you have to think about the range of text you want to apply to. Richedit control introduces the notion of character text range. Richedit control gives each character a number starting from 0: the first characterin the control has Id of 0, the second character 1 and so on. To specify a text range, you must give the richedit control two numbers: the IDs of the first and the last character of the range. To apply the text formatting with EM_SETCHARFORMAT, you have at most three choices:

1. Apply to all text in the control (SCF_ALL)

Page 206: 052 Iczelions_Win32_Assembly_Tutorials.pdf

206

2. Apply to the text currently in selection (SCF_SELECTION)3. Apply to the whole word currently in selection (SCF_WORD or SCF_SELECTION)

The first and the second choices are straightforward. The last choice requires a little explanation. If the current selection only covers one or more of the characters in the word but not the whole word, specifying the flag SCF_WORD+SCF_SELECTION applies the text formatting to the whole word. Even if there is no current selection, ie, only the caret is positioned in the word, the third choice also applies the text formatting to the whole word.

To use EM_SETCHARFORMAT, you need to fill several members of CHARFORMAT (or CHARFORMAT2) structure. For example, if we want to set the text color, we will fill the CHARFORMAT structure as follows:

.data?cf CHARFORMAT <>

....

.codemov cf.cbSize,sizeof cfmov cf.dwMask,CFM_COLORmov cf.crTextColor,0FF0000hinvoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cf

The above code snippet sets the text color of the richedit control to pure blue. Note that if there is no text in the richedit control when EM_SETCHARFORMAT is issued, the text entered into the richedit control following the message will use the text formatting specified by the EM_SETCHARFORMAT message.

Setting the text/saving the text

For those of you who are used to edit control, you'll surely be familiar with WM_GETTEXT/WM_SETTEXT as the means to set the text/get the text to/from the control. This method still works with richedit control but may not be efficient if the file is large. Edit control limits the text that can be entered into it to 64K but richedit control can accept text much larger than that. It would be very cumbersome to allocate a very large block of memory (such as 10 MB or so) to receive the text from WM_GETTEXT. Richedit control offers a new approach to this method, ie. text streaming.

To put it simply, you provide the address of a callback function to the richedit control. And richedit control will call that callback, passing the address of the buffer to it, when it's ready. The callback will fill the buffer with the data it wants to send to the control or read the data from the buffer and then waits for the next call until the operation is finished. This paradigm is used for both streaming in (setting the text) and streaming out (getting the text out of the control). You'll see that this method is more efficient: the buffer is provided by the richedit control itself so the data are divided into chunks. The operations involve two messages: EM_STREAMIN and EM_STREAMOUT

Both EM_STREAMIN and EM_STREAMOUT use the same syntax:

wParam == formatting options.

SF_RTF The data is in the rich-text format (RTF)

SF_TEXT The data is in the plain text format

SFF_PLAINRTF Only the keywords common to all languages are streamed in.

SFF_SELECTIONIf specified, the target of the operation is the text currently in selection. If you stream the text in, the text replaces the current selection. If you stream the text out, only the text currently in selection is streamed out. If this flag is not specified, the operation affects the whole text in the control.

SF_UNICODE (Available on RichEdit 2.0 or later) Specify the unicode text.

Page 207: 052 Iczelions_Win32_Assembly_Tutorials.pdf

207

lParam == point to an EDITSTREAM structure which has the following definition:

EDITSTREAM STRUCT dwCookie DWORD ? dwError DWORD ? pfnCallback DWORD ?

EDITSTREAM ENDS

dwCookieapplication-defined value that will be passed to the callback function speficied in pfnCallback member below. We normally pass some important value to the callback function such as the file handle to use in the stream-in/out procedure.

dwErrorIndicates the results of the stream-in (read) or stream-out (write) operation. A value of zero indicates no error. A nonzero value can be the return value of the EditStreamCallback function or a code indicating that the control encountered an error.

pfnCallback Pointer to an EditStreamCallback function, which is an application-defined function that the control calls to transfer data. The control calls the callback function repeatedly, transferring a portion of the data with each call

The editstream callback function has the following definition:

EditStreamCallback proto dwCookie:DWORD,pBuffer:DWORD,NumBytes:DWORD,pBytesTransferred:DWORD

You have to create a function with the above prototype in your program. And then pass its address to EM_STREAMIN or EM_STREAMOUT via EDITSTREAM structure.

For stream-in operation (settting the text in the richedit control):

dwCookie: the application-defined value you pass to EM_STREAMIN via EDITSTREAM structure. We almost always

pass the file handle of the file we want to set its content to the control here.pBuffer: points to the buffer provided by the richedit control that will receive the text from your callback

function.NumBytes: the maximum number of bytes you can write the the buffer (pBuffer) in this call. You MUST

always obey this limit, ie, you can sendless data than the value in NumBytes but must not send more data than this value. You can

think of this value as the sizeof the buffer in pBuffer.

pBytesTransferred: points to a dword that you must set the value indicating the number of bytes you actually transferred to the buffer.

This value is usually identical to the value in NumBytes. The exception is when the data is to send is less than

the size of the buffer provided such as when the end of file is reached.

For stream-out operation (getting the text out of the richedit control):

dwCookie: Same as the stream-in operation. We usually pass the file handle we want to write the data to in this parameter.

pBuffer: points to the buffer provided by the richedit control that is filled with the data from the richedit control.

To obtain its size, you must examine the value of NumBytes. NumBytes: the size of the data in the buffer pointed to by pBuffer. pBytesTransferred: points to a dword that you must set the value indicating the number of bytes you

actually read from the buffer.

Page 208: 052 Iczelions_Win32_Assembly_Tutorials.pdf

208

The callback function returns 0 to indicate success and richedit control will continue calling the callback function ifthere is still data left to read/write. If some error occurs during the process and you want to stop the operation, returns a non-zero value and the richedit control will discard the data pointed to by pBuffer. The error/success value will be filled in the dwError field of EDITSTREAM so you can examine the error/success status of the stream operation after SendMessage returns.

Example:

The example below is a simple editor which you can open an asm source code file, edit and save it. It uses RichEdit control version 2.0 or above.

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\comdlg32.incinclude \masm32\include\gdi32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\gdi32.libincludelib \masm32\lib\comdlg32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.constIDR_MAINMENU equ 101IDM_OPEN equ 40001IDM_SAVE equ 40002IDM_CLOSE equ 40003IDM_SAVEAS equ 40004IDM_EXIT equ 40005IDM_COPY equ 40006IDM_CUT equ 40007IDM_PASTE equ 40008IDM_DELETE equ 40009IDM_SELECTALL equ 40010IDM_OPTION equ 40011IDM_UNDO equ 40012IDM_REDO equ 40013IDD_OPTIONDLG equ 101IDC_BACKCOLORBOX equ 1000IDC_TEXTCOLORBOX equ 1001

RichEditID equ 300

.dataClassName db "IczEditClass",0AppName db "IczEdit version 1.0",0RichEditDLL db "riched20.dll",0RichEditClass db "RichEdit20A",0NoRichEdit db "Cannot find riched20.dll",0ASMFilterString db "ASM Source code (*.asm)",0,"*.asm",0

db "All Files (*.*)",0,"*.*",0,0OpenFileFail db "Cannot open the file",0

Page 209: 052 Iczelions_Win32_Assembly_Tutorials.pdf

209

WannaSave db "The data in the control is modified. Want to save it?",0FileOpened dd FALSEBackgroundColor dd 0FFFFFFh ; default to whiteTextColor dd 0 ; default to black

.data?hInstance dd ?hRichEdit dd ?hwndRichEdit dd ?FileName db 256 dup(?)AlternateFileName db 256 dup(?)CustomColors dd 16 dup(?)

.codestart:

invoke GetModuleHandle, NULLmov hInstance,eaxinvoke LoadLibrary,addr RichEditDLL.if eax!=0

mov hRichEdit,eaxinvoke WinMain, hInstance,0,0, SW_SHOWDEFAULTinvoke FreeLibrary,hRichEdit

.elseinvoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR

.endifinvoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:DWORDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,IDR_MAINMENUmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcINVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwnd.while TRUE

invoke GetMessage, ADDR msg,0,0,0.break .if (!eax)

Page 210: 052 Iczelions_Win32_Assembly_Tutorials.pdf

210

invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endwmov eax,msg.wParamret

WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORDinvoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0xor eax,1ret

StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORDinvoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0xor eax,1ret

StreamOutProc endp

CheckModifyState proc hWnd:DWORDinvoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0.if eax!=0

invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL.if eax==IDYES

invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0.elseif eax==IDCANCEL

mov eax,FALSEret

.endif.endifmov eax,TRUEret

CheckModifyState endp

SetColor procLOCAL cfm:CHARFORMATinvoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColorinvoke RtlZeroMemory,addr cfm,sizeof cfmmov cfm.cbSize,sizeof cfmmov cfm.dwMask,CFM_COLORpush TextColorpop cfm.crTextColorinvoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfmret

SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL clr:CHOOSECOLOR.if uMsg==WM_INITDIALOG.elseif uMsg==WM_COMMAND

mov eax,wParamshr eax,16.if ax==BN_CLICKED

mov eax,wParam.if ax==IDCANCEL

invoke SendMessage,hWnd,WM_CLOSE,0,0.elseif ax==IDC_BACKCOLORBOX

Page 211: 052 Iczelions_Win32_Assembly_Tutorials.pdf

211

invoke RtlZeroMemory,addr clr,sizeof clrmov clr.lStructSize,sizeof clrpush hWndpop clr.hwndOwnerpush hInstancepop clr.hInstancepush BackgroundColorpop clr.rgbResultmov clr.lpCustColors,offset CustomColorsmov clr.Flags,CC_ANYCOLOR or CC_RGBINITinvoke ChooseColor,addr clr.if eax!=0

push clr.rgbResultpop BackgroundColorinvoke GetDlgItem,hWnd,IDC_BACKCOLORBOXinvoke InvalidateRect,eax,0,TRUE

.endif.elseif ax==IDC_TEXTCOLORBOX

invoke RtlZeroMemory,addr clr,sizeof clrmov clr.lStructSize,sizeof clrpush hWndpop clr.hwndOwnerpush hInstancepop clr.hInstancepush TextColorpop clr.rgbResultmov clr.lpCustColors,offset CustomColorsmov clr.Flags,CC_ANYCOLOR or CC_RGBINITinvoke ChooseColor,addr clr.if eax!=0

push clr.rgbResultpop TextColorinvoke GetDlgItem,hWnd,IDC_TEXTCOLORBOXinvoke InvalidateRect,eax,0,TRUE

.endif.elseif ax==IDOK

;==================================================================================

; Save the modify state of the richedit control because changing the text color changes the

; modify state of the richedit control.

;==================================================================================

invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0push eaxinvoke SetColorpop eaxinvoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0invoke EndDialog,hWnd,0

.endif.endif

.elseif uMsg==WM_CTLCOLORSTATICinvoke GetDlgItem,hWnd,IDC_BACKCOLORBOX.if eax==lParam

invoke CreateSolidBrush,BackgroundColor

Page 212: 052 Iczelions_Win32_Assembly_Tutorials.pdf

212

ret.else

invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX.if eax==lParam

invoke CreateSolidBrush,TextColorret

.endif.endifmov eax,FALSEret

.elseif uMsg==WM_CLOSEinvoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

OptionProc endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL chrg:CHARRANGELOCAL ofn:OPENFILENAMELOCAL buffer[256]:BYTELOCAL editstream:EDITSTREAMLOCAL hFile:DWORD.if uMsg==WM_CREATE

invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\

CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0

mov hwndRichEdit,eax

;=============================================================

; Set the text limit. The default is 64K

;=============================================================

invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0

;=============================================================

; Set the default text/background color

;=============================================================

invoke SetColorinvoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0

.elseif uMsg==WM_INITMENUPOPUPmov eax,lParam.if ax==0 ; file menu

.if FileOpened==TRUE ; a file is already openedinvoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED

Page 213: 052 Iczelions_Win32_Assembly_Tutorials.pdf

213

invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED.else

invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED

.endif.elseif ax==1 ; edit menu

;=============================================================================

; Check whether there is some text in the clipboard. If so, we enable the paste menuitem

;=============================================================================

invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0.if eax==0 ; no text in the clipboard

invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED.else

invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED.endif

;==========================================================

; check whether the undo queue is empty

;==========================================================

invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0.if eax==0

invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED.else

invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED.endif

;=========================================================

; check whether the redo queue is empty

;=========================================================

invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0.if eax==0

invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED.else

invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED.endif

;=========================================================

; check whether there is a current selection in the richedit control.; If there is, we enable the cut/copy/delete menuitem

;=========================================================

invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg

Page 214: 052 Iczelions_Win32_Assembly_Tutorials.pdf

214

mov eax,chrg.cpMin.if eax==chrg.cpMax ; no current selection

invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED

.elseinvoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED

.endif.endif

.elseif uMsg==WM_COMMAND.if lParam==0 ; menu commands

mov eax,wParam.if ax==IDM_OPEN

invoke RtlZeroMemory,addr ofn,sizeof ofnmov ofn.lStructSize,sizeof ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter,offset ASMFilterStringmov ofn.lpstrFile,offset FileNamemov byte ptr [FileName],0mov ofn.nMaxFile,sizeof FileNamemov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or

OFN_PATHMUSTEXISTinvoke GetOpenFileName,addr ofn.if eax!=0

invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUEmov hFile,eax

;================================================================

; stream the text into the richedit control

;================================================================

mov editstream.dwCookie,eaxmov editstream.pfnCallback,offset

StreamInProcinvoke

SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream

;==========================================================

; Initialize the modify state to false

;==========================================================

invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0

invoke CloseHandle,hFilemov FileOpened,TRUE

.else

Page 215: 052 Iczelions_Win32_Assembly_Tutorials.pdf

215

invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR

.endif.endif

.elseif ax==IDM_CLOSEinvoke CheckModifyState,hWnd.if eax==TRUE

invoke SetWindowText,hwndRichEdit,0mov FileOpened,FALSE

.endif.elseif ax==IDM_SAVE

invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUE@@:

mov hFile,eax

;================================================================

; stream the text to the file

;================================================================

mov editstream.dwCookie,eaxmov editstream.pfnCallback,offset StreamOutProcinvoke

SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream

;==========================================================

; Initialize the modify state to false

;==========================================================

invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0

invoke CloseHandle,hFile.else

invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR

.endif.elseif ax==IDM_COPY

invoke SendMessage,hwndRichEdit,WM_COPY,0,0.elseif ax==IDM_CUT

invoke SendMessage,hwndRichEdit,WM_CUT,0,0.elseif ax==IDM_PASTE

invoke SendMessage,hwndRichEdit,WM_PASTE,0,0.elseif ax==IDM_DELETE

invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0.elseif ax==IDM_SELECTALL

mov chrg.cpMin,0mov chrg.cpMax,-1invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

.elseif ax==IDM_UNDOinvoke SendMessage,hwndRichEdit,EM_UNDO,0,0

.elseif ax==IDM_REDOinvoke SendMessage,hwndRichEdit,EM_REDO,0,0

Page 216: 052 Iczelions_Win32_Assembly_Tutorials.pdf

216

.elseif ax==IDM_OPTIONinvoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr

OptionProc,0.elseif ax==IDM_SAVEAS

invoke RtlZeroMemory,addr ofn,sizeof ofnmov ofn.lStructSize,sizeof ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter,offset ASMFilterStringmov ofn.lpstrFile,offset AlternateFileNamemov byte ptr [AlternateFileName],0mov ofn.nMaxFile,sizeof AlternateFileNamemov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or

OFN_PATHMUSTEXISTinvoke GetSaveFileName,addr ofn.if eax!=0

invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUEjmp @B

.endif.endif

.elseif ax==IDM_EXITinvoke SendMessage,hWnd,WM_CLOSE,0,0

.endif.endif

.elseif uMsg==WM_CLOSEinvoke CheckModifyState,hWnd.if eax==TRUE

invoke DestroyWindow,hWnd.endif

.elseif uMsg==WM_SIZEmov eax,lParammov edx,eaxand eax,0FFFFhshr edx,16invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE

.elseif uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endpend start

;===================================================================; The resource file;===================================================================#include "resource.h"

Page 217: 052 Iczelions_Win32_Assembly_Tutorials.pdf

217

#define IDR_MAINMENU 101#define IDD_OPTIONDLG 101#define IDC_BACKCOLORBOX 1000#define IDC_TEXTCOLORBOX 1001#define IDM_OPEN 40001#define IDM_SAVE 40002#define IDM_CLOSE 40003#define IDM_SAVEAS 40004#define IDM_EXIT 40005#define IDM_COPY 40006#define IDM_CUT 40007#define IDM_PASTE 40008#define IDM_DELETE 40009#define IDM_SELECTALL 40010#define IDM_OPTION 40011#define IDM_UNDO 40012#define IDM_REDO 40013

IDR_MAINMENU MENU DISCARDABLE BEGIN

POPUP "&File"BEGIN

MENUITEM "&Open", IDM_OPENMENUITEM "&Close", IDM_CLOSEMENUITEM "&Save", IDM_SAVEMENUITEM "Save &As", IDM_SAVEASMENUITEM SEPARATORMENUITEM "E&xit", IDM_EXIT

ENDPOPUP "&Edit"BEGIN

MENUITEM "&Undo", IDM_UNDOMENUITEM "&Redo", IDM_REDOMENUITEM "&Copy", IDM_COPYMENUITEM "C&ut", IDM_CUTMENUITEM "&Paste", IDM_PASTEMENUITEM SEPARATORMENUITEM "&Delete", IDM_DELETEMENUITEM SEPARATORMENUITEM "Select &All", IDM_SELECTALL

ENDMENUITEM "Options", IDM_OPTION

END

IDD_OPTIONDLG DIALOG DISCARDABLE 0, 0, 183, 54STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTERCAPTION "Options"FONT 8, "MS Sans Serif"BEGIN

DEFPUSHBUTTON "OK",IDOK,137,7,39,14PUSHBUTTON "Cancel",IDCANCEL,137,25,39,14GROUPBOX "",IDC_STATIC,5,0,124,49LTEXT "Background Color:",IDC_STATIC,20,14,60,8LTEXT "",IDC_BACKCOLORBOX,85,11,28,14,SS_NOTIFY | WS_BORDERLTEXT "Text Color:",IDC_STATIC,20,33,35,8LTEXT "",IDC_TEXTCOLORBOX,85,29,28,14,SS_NOTIFY | WS_BORDER

Page 218: 052 Iczelions_Win32_Assembly_Tutorials.pdf

218

END

Analysis:

The program first loads the richedit dll, which in this case is riched20.dll. If the dll cannot be loaded, it exits to Windows.

invoke LoadLibrary,addr RichEditDLL .if eax!=0

mov hRichEdit,eax invoke WinMain,hInstance,0,0, SW_SHOWDEFAULT invoke FreeLibrary,hRichEdit

.else invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR

.endif invoke ExitProcess,eax

After the dll is loaded successfully, we proceed to create a normal window which will be the parent of the richedit control. Within the WM_CREATE handler, we create the richedit control:

invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\

CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0

mov hwndRichEdit,eax

Note that we specify ES_MULTILINE style else the control will be a single-lined one.

invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0

After the richedit control is created, we must set the new text limit on it. By default, the richedit control has 64K text limit, the same as a simple multi-line edit control. We must extend this limit to allow it to operate with larger files. In the above line, I specify -1 which amounts to 0FFFFFFFFh, a very large value.

invoke SetColor

Next, we set the text/background color. Since this operation can be performed in other part of the program, I put the code in a function named SetColor.

SetColor procLOCAL cfm:CHARFORMATinvoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor

Setting the background color of the richedit control is a straightforward operation: just send EM_SETBKGNDCOLOR message to the richedit control. (If you use a multi-line edit control, you have to process WM_CTLCOLOREDIT). The default background color is white.

invoke RtlZeroMemory,addr cfm,sizeof cfmmov cfm.cbSize,sizeof cfmmov cfm.dwMask,CFM_COLORpush TextColorpop cfm.crTextColor

Page 219: 052 Iczelions_Win32_Assembly_Tutorials.pdf

219

After the background color is set, we fill in the members of CHARFORMAT in order to set the text color. Note that we fill cbSize with the size of the structure so the richedit control knows we are sending it CHARFORMAT, not CHARFORMAT2. dwMask has only one flag, CFM_COLOR, because we only want to set the text color and crTextColor is filled with the value of the desired text color.

invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfmret

SetColor endp

After settting the color, you have to empty undo buffer simply because the act of changing text/background color is undo-able. We send EM_EMPTYUNDOBUFFER message to achieve this.

invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0

After filling the CHARFORMAT structure, we send EM_SETCHARFORMAT to the richedit control, specifying SCF_ALL flag in wParam to indicate that we want the text formatting to be applied to all text in the control.

Note that when we first created the richedit control, we didn't specify its size/position at that time. That's because we want it to cover the whole client area of the parent window. We resize it whenever the size of the parent window changes.

.elseif uMsg==WM_SIZEmov eax,lParammov edx,eaxand eax,0FFFFhshr edx,16invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE

In the above code snippet, we use the new dimension of the client area passed in lParam to resize the richedit control with MoveWindow.

When the user clicks on the File/Edit menu bar, we process WM_INITPOPUPMENU so that we can prepare the states of the menuitems in the submenu before displaying it to the user. For example, if a file is already opened in the richedit control, we want to disable the open menuitem and enable all the remaining menuitems.

In the case of the File menu bar, we use the variable FileOpened as the flag to determine whether a file is already opened. If the value in this variable is TRUE, we know that a file is already opened.

.elseif uMsg==WM_INITMENUPOPUPmov eax,lParam.if ax==0 ; file menu

.if FileOpened==TRUE ; a file is already openedinvoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED

.elseinvoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED

.endif

As you can see, if a file is already opened, we gray out the open menuitem and enable the remaining menuitems. The reverse is true of FileOpened is false.

Page 220: 052 Iczelions_Win32_Assembly_Tutorials.pdf

220

In the case of the edit menu bar, we need to check the state of the richedit control/clipboard first.

invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0.if eax==0 ; no text in the clipboard

invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED.else

invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED.endif

We first check whether some text is available in the clipboard by sending EM_CANPASTE message. If some text is available, SendMessage returns TRUE and we enable the paste menuitem. If not, we gray out the menuitem.

invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0.if eax==0

invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED.else

invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED.endif

Next, we check whether the undo buffer is empty by sending EM_CANUNDO message. If it's not empty, SendMessage returns TRUE and we enable the undo menuitem.

invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0.if eax==0

invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED.else

invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED.endif

We check the redo buffer by sending EM_CANREDO message to the richedit control. If it's not empty, SendMessage returns TRUE and we enable the redo menuitem.

invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrgmov eax,chrg.cpMin.if eax==chrg.cpMax ; no current selection

invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED

.elseinvoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED

.endif

Lastly, we check whether a current selection exists by sending EM_EXGETSEL message. This message uses a CHARRANGE structure which is defined as follows:

CHARRANGE STRUCTcpMin DWORD ?cpMax DWORD ?

CHARRANGE ENDS

cpMin contains the character position index immediately preceding the first character in the range.cpMax contains the character position immediately following the last character in the range.

Page 221: 052 Iczelions_Win32_Assembly_Tutorials.pdf

221

After EM_EXGETSEL returns, the CHARRANGE structure is filled with the starting-ending character position indices of the selection range. If there is no current selection, cpMin and cpMax are identical and we gray out the cut/copy/delete menuitems.

When the user clicks the Open menuitem, we display an open file dialog box and if the user selects a file, we open the file and stream its content to the richedit control.

invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUEmov hFile,eax

mov editstream.dwCookie,eaxmov editstream.pfnCallback,offset

StreamInProcinvoke

SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream

After the file is successfully opened with CreateFile, we fill the EDITSTREAM structure in preparation for EM_STREAMIN message. We choose to send the handle to the opened file via dwCookie member and pass the address of the stream callback function in pfnCallback.

The stream callback procedure itself is the essence of simplicity.

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORDinvoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0xor eax,1ret

StreamInProc endp

You can see that all parameters of the stream callback procedure fit perfectly with ReadFile. And the return value of ReadFile is xor-ed with 1 so that if it returns 1 (success), the actual value returned in eax is 0 and vice versa.

invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0invoke CloseHandle,hFilemov FileOpened,TRUE

After EM_STREAMIN returns, it means the stream operation is completed. In reality, we must check the value of dwError member of the EDITSTREAM structure.

Richedit (and edit) control supports a flag to indicate whether its content is modified. We can obtain the value of this flag by sending EM_GETMODIFY message to the control. SendMessage returns TRUE if the content of the control was modified. Since we stream the text into the control, it's a kind of a modification. We must set the modify flag to FALSE by sending EM_SETMODIFY with wParam==FALSE to the control to start anew after the stream-in opertion is finished. We immediately close the file and set FileOpened to TRUE to indicate that a file was opened.

When the user clicks on save/saveas menuitem, we use EM_STREAMOUT message to output the content of the richedit control to a file. As with the streamin callback function, the stream-out callback function is simplicity in itself. It fits perfectly with WriteFile.

The text operations such as cut/copy/paste/redo/undo are easily implemented by sending single message to the richedit control, WM_CUT/WM_COPY/WM_PASTE/WM_REDO/WM_UNDO respectively.

The delete/select all operations are done as follows:

Page 222: 052 Iczelions_Win32_Assembly_Tutorials.pdf

222

.elseif ax==IDM_DELETEinvoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0

.elseif ax==IDM_SELECTALLmov chrg.cpMin,0mov chrg.cpMax,-1invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

The delete operation affects the currently selection. I send EM_REPLACESEL message with NULL string so the richedit control will replace the currently selected text with the null string.

The select-all operation is done by sending EM_EXSETSEL message, specifying cpMin==0 and cpMax==-1 which amounts to selecting all the text.

When the user selects Option menu bar, we display a dialog box presenting the current background/text colors.

When the user clicks on one of the color boxes, it displays the choose-color dialog box. The "color box" is in fact a static control with SS_NOTIFY and WS_BORDER flag. A static control with SS_NOTIFY flag will notify its parent window with mouse actions on it, such as BN_CLICKED (STN_CLICKED). That's the trick.

.elseif ax==IDC_BACKCOLORBOXinvoke RtlZeroMemory,addr clr,sizeof clrmov clr.lStructSize,sizeof clrpush hWndpop clr.hwndOwnerpush hInstancepop clr.hInstancepush BackgroundColorpop clr.rgbResultmov clr.lpCustColors,offset CustomColorsmov clr.Flags,CC_ANYCOLOR or CC_RGBINITinvoke ChooseColor,addr clr.if eax!=0

push clr.rgbResultpop BackgroundColorinvoke GetDlgItem,hWnd,IDC_BACKCOLORBOXinvoke InvalidateRect,eax,0,TRUE

.endif

When the user clicks on one of the color box, we fill the members of the CHOOSECOLOR structure and call ChooseColor to display the choose-color dialog box. If the user selects a color, the colorref value is returned in rgbResult member and we store that value in BackgroundColor variable. After that, we force a repaint on the color box by calling InvalidateRect on the handle to the color box. The color box sends WM_CTLCOLORSTATICmessage to its parent window.

invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX.if eax==lParam

invoke CreateSolidBrush,BackgroundColor

Page 223: 052 Iczelions_Win32_Assembly_Tutorials.pdf

223

ret

Within the WM_CTLCOLORSTATIC handler, we compare the handle of the static control passed in lParam to that of both the color boxes. If the values match, we create a new brush using the color in the variable and immediately return. The static control will use the newly created brush to paint its background.

Page 224: 052 Iczelions_Win32_Assembly_Tutorials.pdf

224

Tutorial 34: RichEdit Control: More Text Operations

You'll learn more about text operations under RichEdit control. Specifically, you'll know how to search for/replace text, jumping to specific line number.

Download the example.

Theory

Searching for Text

There are several text operations under RichEdit control. Searching for text is one such operation. Searching for text is done by sending EM_FINDTEXT or EM_FINDTEXTEX. These messages has a small difference.

EM_FINDTEXTwParam == Search options. Can be any combination of the values in the table below.These options are identical for both

EM_FINDTEXT and EM_FINDTEXTEX

FR_DOWN

If this flag is specified, the search starts from the end of the current selection to the end of the text in the control (downward). This flag has effect only for RichEdit 2.0 or later: This behavior is the default for RichEdit 1.0. The default behavior of RichEdit 2.0 or later is to search from the end of the current selection to the beginning of the text (upward).In summary, if you use RichEdit 1.0, you can't do anything about the search direction: it always searches downward. If you use RichEdit 2.0 and you want to search downward, you must specify this flag else the search would be upward.

FR_MATCHCASE If this flag is specified, the search is case-sensitive.

FR_WHOLEWORD If this flag is set, the search finds the whole word that matches the specified search string.

Actually, there are a few more flags but they are relevant to non-English languages.lParam == pointer to the FINDTEXT structure.

FINDTEXT STRUCTchrg CHARRANGE <>lpstrText DWORD ?

FINDTEXT ENDS

chrg is a CHARRANGE structure which is defined as follows:

CHARRANGE STRUCTcpMin DWORD ?cpMax DWORD ?

CHARRANGE ENDS

cpMin contains the character index of the first character in the character array (range).cpMax contains the character index of the character immediately following the last character in the character array.

In essence, to search for a text string, you have to specify the character range in which to search. The meaning of cpMin and cpMax differ according to whether the search is downward or upward. If the search is downward, cpMin

Page 225: 052 Iczelions_Win32_Assembly_Tutorials.pdf

225

specifies the starting character index to search in and cpMax the ending character index. If the search is upward, the reverse is true, ie. cpMin contains the ending character index while cpMax the starting character index.

lpstrText is the pointer to the text string to search for.

EM_FINDTEXT returns the character index of the first character in the matching text string in the richedit control. It returns -1 ifno match is found.

EM_FINDTEXTEXwParam == the search options. Same as those of EM_FINDTEXT.lParam == pointer to the FINDTEXTEX structure.

FINDTEXTEX STRUCTchrg CHARRANGE <>lpstrText DWORD ?chrgText CHARRANGE <>

FINDTEXTEX ENDS

The first two members of FINDTEXTEX are identical to those of FINDTEXT structure. chrgText is a CHARRANGE structure that willbe filled with the starting/ending characterindices if a match is found.

The return value of EM_FINDTEXTEX is the same as that of EM_FINDTEXT.

The difference between EM_FINDTEXT and EM_FINDTEXTEX is that the FINDTEXTEX structure has an additional member, chrgText, which will be filled with the starting/ending character indices if a match is found. This is convenient if we want to do more text operations on the string.

Replace/Insert Text

RichEdit control provides EM_SETTEXTEX for replacing/inserting text. This message combines the functionality of WM_SETTEXT and EM_REPLACESEL. It has the following syntax:

EM_SETTEXTEXwParam == pointer to SETTEXTEX structure.

SETTEXTEX STRUCTflags DWORD ?codepage DWORD ?

SETTEXTEX ENDS

flags can be the combination of the following values:

ST_DEFAULT Deletes the undo stack, discards rich-text formatting, replaces all text.

ST_KEEPUNDO Keeps the undo stack

ST_SELECTION Replaces selection and keeps rich-text formatting

codepage is the constant that specifies the code page you want to text to be. Usually, we simply use CP_ACP.

Text Selection

Page 226: 052 Iczelions_Win32_Assembly_Tutorials.pdf

226

We can select the text programmatically with EM_SETSEL or EM_EXSETSEL. Either one works fine. Choosing which message to use depends on the available format of the character indices. If they are already stored in a CHARRANGE structure, it's easier to use EM_EXSETSEL.

EM_EXSETSELwParam == not used. Must be 0lParam == pointer to a CHARRANGE structure that contains the character range to be selected.

Event Notification

In the case of a multiline edit control, you have to subclass it in order to obtain the input messages such as mouse/keyboard events. RichEdit control provides a better scheme that will notify the parent window of such events. In order to register for notifications, the parent window sends EM_SETEVENTMASK message to the RichEdit control, specifying which events it's interested in. EM_SETEVENTMASK has the following syntax:

EM_SETEVENTMASKwParam == not used. Must be 0lParam == event mask value. It can be the combination of the flags in the table below.

ENM_CHANGE Sends EN_CHANGE notifications

ENM_CORRECTTEXT Sends EN_CORRECTTEXT notifications

ENM_DRAGDROPDONE Sends EN_DRAGDROPDONE notifications

ENM_DROPFILES Sends EN_DROPFILES notifications.

ENM_KEYEVENTS Sends EN_MSGFILTER notifications for keyboard events

ENM_LINK Rich Edit 2.0 and later: Sends EN_LINK notifications when the mouse pointer is over text that has the CFE_LINK and one of several mouse actions is performed.

ENM_MOUSEEVENTS Sends EN_MSGFILTER notifications for mouse events

ENM_OBJECTPOSITIONS Sends EN_OBJECTPOSITIONS notifications

ENM_PROTECTED Sends EN_PROTECTED notifications

ENM_REQUESTRESIZE Sends EN_REQUESTRESIZE notifications

ENM_SCROLL Sends EN_HSCROLL and EN_VSCROLL notifications

ENM_SCROLLEVENTS Sends EN_MSGFILTER notifications for mouse wheel events

ENM_SELCHANGE Sends EN_SELCHANGE notifications

ENM_UPDATE

Sends EN_UPDATE notifications.Rich Edit 2.0 and later: this flag is ignored and the EN_UPDATE notifications are always sent. However, if Rich Edit 3.0 emulates Rich Edit 1.0, you must use this flag to send EN_UPDATE notifications

All the above notifications will be sent as WM_NOTIFY message: you have to check the code member of NMHDRstructure for the notification message. For example, if you want to register for mouse events (eg. you want to provide a context sensitive popup menu), you must do something like this:

invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS..........WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD.........

Page 227: 052 Iczelions_Win32_Assembly_Tutorials.pdf

227

.elseif uMsg==WM_NOTIFYpush esimov esi,lParamassume esi:ptr NMHDR.if [esi].code==EN_MSGFILTER

....[ do something here]....

.endifpop esi

Example:

The following example is the update of IczEdit in tutorial no. 33. It adds search/replace functionality and accelerator keys to the program. It also processes the mouse events and provides a popup menu on right mouse click.

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\comdlg32.incinclude \masm32\include\gdi32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\gdi32.libincludelib \masm32\lib\comdlg32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.constIDR_MAINMENU equ 101IDM_OPEN equ 40001IDM_SAVE equ 40002IDM_CLOSE equ 40003IDM_SAVEAS equ 40004IDM_EXIT equ 40005IDM_COPY equ 40006IDM_CUT equ 40007IDM_PASTE equ 40008IDM_DELETE equ 40009IDM_SELECTALL equ 40010IDM_OPTION equ 40011IDM_UNDO equ 40012IDM_REDO equ 40013IDD_OPTIONDLG equ 101IDC_BACKCOLORBOX equ 1000IDC_TEXTCOLORBOX equ 1001IDR_MAINACCEL equ 105IDD_FINDDLG equ 102IDD_GOTODLG equ 103IDD_REPLACEDLG equ 104IDC_FINDEDIT equ 1000IDC_MATCHCASE equ 1001

Page 228: 052 Iczelions_Win32_Assembly_Tutorials.pdf

228

IDC_REPLACEEDIT equ 1001IDC_WHOLEWORD equ 1002IDC_DOWN equ 1003IDC_UP equ 1004IDC_LINENO equ 1005IDM_FIND equ 40014IDM_FINDNEXT equ 40015IDM_REPLACE equ 40016IDM_GOTOLINE equ 40017IDM_FINDPREV equ 40018RichEditID equ 300

.dataClassName db "IczEditClass",0AppName db "IczEdit version 2.0",0RichEditDLL db "riched20.dll",0RichEditClass db "RichEdit20A",0NoRichEdit db "Cannot find riched20.dll",0ASMFilterString db "ASM Source code (*.asm)",0,"*.asm",0

db "All Files (*.*)",0,"*.*",0,0OpenFileFail db "Cannot open the file",0WannaSave db "The data in the control is modified. Want to save it?",0FileOpened dd FALSEBackgroundColor dd 0FFFFFFh ; default to whiteTextColor dd 0 ; default to blackhSearch dd ? ; handle to the search/replace dialog boxhAccel dd ?

.data?hInstance dd ?hRichEdit dd ?hwndRichEdit dd ?FileName db 256 dup(?)AlternateFileName db 256 dup(?)CustomColors dd 16 dup(?)FindBuffer db 256 dup(?)ReplaceBuffer db 256 dup(?)uFlags dd ?findtext FINDTEXTEX <>

.codestart:

mov byte ptr [FindBuffer],0mov byte ptr [ReplaceBuffer],0invoke GetModuleHandle, NULLmov hInstance,eaxinvoke LoadLibrary,addr RichEditDLL.if eax!=0

mov hRichEdit,eaxinvoke WinMain, hInstance,0,0, SW_SHOWDEFAULTinvoke FreeLibrary,hRichEdit

.elseinvoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR

.endifinvoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD

Page 229: 052 Iczelions_Win32_Assembly_Tutorials.pdf

229

LOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:DWORDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,IDR_MAINMENUmov wc.lpszClassName,OFFSET ClassNameinvoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcINVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwndinvoke LoadAccelerators,hInstance,IDR_MAINACCELmov hAccel,eax.while TRUE

invoke GetMessage, ADDR msg,0,0,0.break .if (!eax)invoke IsDialogMessage,hSearch,addr msg.if eax==FALSE

invoke TranslateAccelerator,hwnd,hAccel,addr msg.if eax==0

invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endif.endif

.endwmov eax,msg.wParamret

WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORDinvoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0xor eax,1ret

StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORDinvoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0xor eax,1ret

StreamOutProc endp

CheckModifyState proc hWnd:DWORD

Page 230: 052 Iczelions_Win32_Assembly_Tutorials.pdf

230

invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0.if eax!=0

invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL.if eax==IDYES

invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0.elseif eax==IDCANCEL

mov eax,FALSEret

.endif.endifmov eax,TRUEret

CheckModifyState endp

SetColor procLOCAL cfm:CHARFORMATinvoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColorinvoke RtlZeroMemory,addr cfm,sizeof cfmmov cfm.cbSize,sizeof cfmmov cfm.dwMask,CFM_COLORpush TextColorpop cfm.crTextColorinvoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfmret

SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL clr:CHOOSECOLOR.if uMsg==WM_INITDIALOG.elseif uMsg==WM_COMMAND

mov eax,wParamshr eax,16.if ax==BN_CLICKED

mov eax,wParam.if ax==IDCANCEL

invoke SendMessage,hWnd,WM_CLOSE,0,0.elseif ax==IDC_BACKCOLORBOX

invoke RtlZeroMemory,addr clr,sizeof clrmov clr.lStructSize,sizeof clrpush hWndpop clr.hwndOwnerpush hInstancepop clr.hInstancepush BackgroundColorpop clr.rgbResultmov clr.lpCustColors,offset CustomColorsmov clr.Flags,CC_ANYCOLOR or CC_RGBINITinvoke ChooseColor,addr clr.if eax!=0

push clr.rgbResultpop BackgroundColorinvoke GetDlgItem,hWnd,IDC_BACKCOLORBOXinvoke InvalidateRect,eax,0,TRUE

.endif.elseif ax==IDC_TEXTCOLORBOX

invoke RtlZeroMemory,addr clr,sizeof clrmov clr.lStructSize,sizeof clr

Page 231: 052 Iczelions_Win32_Assembly_Tutorials.pdf

231

push hWndpop clr.hwndOwnerpush hInstancepop clr.hInstancepush TextColorpop clr.rgbResultmov clr.lpCustColors,offset CustomColorsmov clr.Flags,CC_ANYCOLOR or CC_RGBINITinvoke ChooseColor,addr clr.if eax!=0

push clr.rgbResultpop TextColorinvoke GetDlgItem,hWnd,IDC_TEXTCOLORBOXinvoke InvalidateRect,eax,0,TRUE

.endif.elseif ax==IDOK

invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0push eaxinvoke SetColorpop eaxinvoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0invoke EndDialog,hWnd,0

.endif.endif

.elseif uMsg==WM_CTLCOLORSTATICinvoke GetDlgItem,hWnd,IDC_BACKCOLORBOX.if eax==lParam

invoke CreateSolidBrush,BackgroundColorret

.elseinvoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX.if eax==lParam

invoke CreateSolidBrush,TextColorret

.endif.endifmov eax,FALSEret

.elseif uMsg==WM_CLOSEinvoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

OptionProc endp

SearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD.if uMsg==WM_INITDIALOG

push hWndpop hSearchinvoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWNinvoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer

.elseif uMsg==WM_COMMANDmov eax,wParamshr eax,16

Page 232: 052 Iczelions_Win32_Assembly_Tutorials.pdf

232

.if ax==BN_CLICKEDmov eax,wParam.if ax==IDOK

mov uFlags,0invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr

findtext.chrginvoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr

FindBuffer,sizeof FindBuffer.if eax!=0

invoke IsDlgButtonChecked,hWnd,IDC_DOWN.if eax==BST_CHECKED

or uFlags,FR_DOWNmov eax,findtext.chrg.cpMin.if eax!=findtext.chrg.cpMax

push findtext.chrg.cpMaxpop findtext.chrg.cpMin

.endifmov findtext.chrg.cpMax,-1

.elsemov findtext.chrg.cpMax,0

.endifinvoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE.if eax==BST_CHECKED

or uFlags,FR_MATCHCASE.endifinvoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD.if eax==BST_CHECKED

or uFlags,FR_WHOLEWORD.endifmov findtext.lpstrText,offset FindBufferinvoke

SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext.if eax!=-1

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

.endif.endif

.elseif ax==IDCANCELinvoke SendMessage,hWnd,WM_CLOSE,0,0

.elsemov eax,FALSEret

.endif.endif

.elseif uMsg==WM_CLOSEmov hSearch,0invoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

SearchProc endp

ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL settext:SETTEXTEX

Page 233: 052 Iczelions_Win32_Assembly_Tutorials.pdf

233

.if uMsg==WM_INITDIALOGpush hWndpop hSearchinvoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBufferinvoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer

.elseif uMsg==WM_COMMANDmov eax,wParamshr eax,16.if ax==BN_CLICKED

mov eax,wParam.if ax==IDCANCEL

invoke SendMessage,hWnd,WM_CLOSE,0,0.elseif ax==IDOK

invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer

invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer

mov findtext.chrg.cpMin,0mov findtext.chrg.cpMax,-1mov findtext.lpstrText,offset FindBuffermov settext.flags,ST_SELECTIONmov settext.codepage,CP_ACP.while TRUE

invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext

.if eax==-1.break

.elseinvoke

SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgTextinvoke

SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer.endif

.endw.endif

.endif.elseif uMsg==WM_CLOSE

mov hSearch,0invoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

ReplaceProc endp

GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL LineNo:DWORDLOCAL chrg:CHARRANGE.if uMsg==WM_INITDIALOG

push hWndpop hSearch

.elseif uMsg==WM_COMMANDmov eax,wParamshr eax,16.if ax==BN_CLICKED

Page 234: 052 Iczelions_Win32_Assembly_Tutorials.pdf

234

mov eax,wParam.if ax==IDCANCEL

invoke SendMessage,hWnd,WM_CLOSE,0,0.elseif ax==IDOK

invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSEmov LineNo,eaxinvoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0.if eax>LineNo

invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0

mov chrg.cpMin,eaxmov chrg.cpMax,eaxinvoke

SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrginvoke SetFocus,hwndRichEdit

.endif.endif

.endif.elseif uMsg==WM_CLOSE

mov hSearch,0invoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

GoToProc endp

PrepareEditMenu proc hSubMenu:DWORDLOCAL chrg:CHARRANGEinvoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0.if eax==0 ; no text in the clipboard

invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED.else

invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED.endifinvoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0.if eax==0

invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED.else

invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED.endifinvoke SendMessage,hwndRichEdit,EM_CANREDO,0,0.if eax==0

invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED.else

invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED.endifinvoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrgmov eax,chrg.cpMin.if eax==chrg.cpMax ; no current selection

invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYEDinvoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYEDinvoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED

.elseinvoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLED

Page 235: 052 Iczelions_Win32_Assembly_Tutorials.pdf

235

invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLEDinvoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED

.endifret

PrepareEditMenu endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL ofn:OPENFILENAMELOCAL buffer[256]:BYTELOCAL editstream:EDITSTREAMLOCAL hFile:DWORDLOCAL hPopup:DWORDLOCAL pt:POINTLOCAL chrg:CHARRANGE.if uMsg==WM_CREATE

invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\

CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0

mov hwndRichEdit,eaxinvoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0invoke SetColorinvoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTSinvoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0

.elseif uMsg==WM_NOTIFYpush esimov esi,lParamassume esi:ptr NMHDR.if [esi].code==EN_MSGFILTER

assume esi:ptr MSGFILTER.if [esi].msg==WM_RBUTTONDOWN

invoke GetMenu,hWndinvoke GetSubMenu,eax,1mov hPopup,eaxinvoke PrepareEditMenu,hPopupmov edx,[esi].lParammov ecx,edxand edx,0FFFFhshr ecx,16mov pt.x,edxmov pt.y,ecxinvoke ClientToScreen,hWnd,addr ptinvoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or

TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL.endif

.endifpop esi

.elseif uMsg==WM_INITMENUPOPUPmov eax,lParam.if ax==0 ; file menu

.if FileOpened==TRUE ; a file is already openedinvoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED

.else

Page 236: 052 Iczelions_Win32_Assembly_Tutorials.pdf

236

invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED

.endif.elseif ax==1 ; edit menu

invoke PrepareEditMenu,wParam.elseif ax==2 ; search menu bar

.if FileOpened==TRUEinvoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED

.elseinvoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED

.endif.endif

.elseif uMsg==WM_COMMAND.if lParam==0 ; menu commands

mov eax,wParam.if ax==IDM_OPEN

invoke RtlZeroMemory,addr ofn,sizeof ofnmov ofn.lStructSize,sizeof ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter,offset ASMFilterStringmov ofn.lpstrFile,offset FileNamemov byte ptr [FileName],0mov ofn.nMaxFile,sizeof FileNamemov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or

OFN_PATHMUSTEXISTinvoke GetOpenFileName,addr ofn.if eax!=0

invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUEmov hFile,eax

;================================================================

; stream the text into the richedit control

;================================================================

mov editstream.dwCookie,eaxmov editstream.pfnCallback,offset

StreamInProcinvoke

SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream

Page 237: 052 Iczelions_Win32_Assembly_Tutorials.pdf

237

;==========================================================

; Initialize the modify state to false

;==========================================================

invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0

invoke CloseHandle,hFilemov FileOpened,TRUE

.elseinvoke MessageBox,hWnd,addr

OpenFileFail,addr AppName,MB_OK or MB_ICONERROR.endif

.endif.elseif ax==IDM_CLOSE

invoke CheckModifyState,hWnd.if eax==TRUE

invoke SetWindowText,hwndRichEdit,0mov FileOpened,FALSE

.endif.elseif ax==IDM_SAVE

invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUE@@:

mov hFile,eax

;================================================================

; stream the text to the file

;================================================================

mov editstream.dwCookie,eaxmov editstream.pfnCallback,offset StreamOutProcinvoke

SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream

;==========================================================

; Initialize the modify state to false

;==========================================================

invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0

invoke CloseHandle,hFile.else

invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR

.endif.elseif ax==IDM_COPY

invoke SendMessage,hwndRichEdit,WM_COPY,0,0.elseif ax==IDM_CUT

invoke SendMessage,hwndRichEdit,WM_CUT,0,0

Page 238: 052 Iczelions_Win32_Assembly_Tutorials.pdf

238

.elseif ax==IDM_PASTEinvoke SendMessage,hwndRichEdit,WM_PASTE,0,0

.elseif ax==IDM_DELETEinvoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0

.elseif ax==IDM_SELECTALLmov chrg.cpMin,0mov chrg.cpMax,-1invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

.elseif ax==IDM_UNDOinvoke SendMessage,hwndRichEdit,EM_UNDO,0,0

.elseif ax==IDM_REDOinvoke SendMessage,hwndRichEdit,EM_REDO,0,0

.elseif ax==IDM_OPTIONinvoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr

OptionProc,0.elseif ax==IDM_SAVEAS

invoke RtlZeroMemory,addr ofn,sizeof ofnmov ofn.lStructSize,sizeof ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter,offset ASMFilterStringmov ofn.lpstrFile,offset AlternateFileNamemov byte ptr [AlternateFileName],0mov ofn.nMaxFile,sizeof AlternateFileNamemov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or

OFN_PATHMUSTEXISTinvoke GetSaveFileName,addr ofn.if eax!=0

invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUEjmp @B

.endif.endif

.elseif ax==IDM_FIND.if hSearch==0

invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0

.endif.elseif ax==IDM_REPLACE

.if hSearch==0invoke

CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0.endif

.elseif ax==IDM_GOTOLINE.if hSearch==0

invoke CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0

.endif.elseif ax==IDM_FINDNEXT

invoke lstrlen,addr FindBuffer.if eax!=0

invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

Page 239: 052 Iczelions_Win32_Assembly_Tutorials.pdf

239

mov eax,findtext.chrg.cpMin.if eax!=findtext.chrg.cpMax

push findtext.chrg.cpMaxpop findtext.chrg.cpMin

.endifmov findtext.chrg.cpMax,-1mov findtext.lpstrText,offset FindBufferinvoke

SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext.if eax!=-1

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

.endif.endif

.elseif ax==IDM_FINDPREVinvoke lstrlen,addr FindBuffer.if eax!=0

invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

mov findtext.chrg.cpMax,0mov findtext.lpstrText,offset FindBufferinvoke

SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext.if eax!=-1

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

.endif.endif

.elseif ax==IDM_EXITinvoke SendMessage,hWnd,WM_CLOSE,0,0

.endif.endif

.elseif uMsg==WM_CLOSEinvoke CheckModifyState,hWnd.if eax==TRUE

invoke DestroyWindow,hWnd.endif

.elseif uMsg==WM_SIZEmov eax,lParammov edx,eaxand eax,0FFFFhshr edx,16invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE

.elseif uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endpend start

Analysis

Page 240: 052 Iczelions_Win32_Assembly_Tutorials.pdf

240

The search-for-text capability is implemented with EM_FINDTEXTEX. When the user clicks on Find menuitem, IDM_FIND message is sent and the Find dialog box is displayed.

invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer.if eax!=0

When the user types the text to search for and then press OK button, we get the text to be searched for into FindBuffer.

mov uFlags,0 invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

If the text string is not null, we continue to initialize uFlags variable to 0.This variable is used to store the search flags used with EM_FINDTEXTEX. After that, we obtain the current selection with EM_EXGETSEL because we need to know the starting point of the search operation.

invoke IsDlgButtonChecked,hWnd,IDC_DOWN.if eax==BST_CHECKED

or uFlags,FR_DOWNmov eax,findtext.chrg.cpMin.if eax!=findtext.chrg.cpMax

push findtext.chrg.cpMaxpop findtext.chrg.cpMin

.endifmov findtext.chrg.cpMax,-1

.elsemov findtext.chrg.cpMax,0

.endif

The next part is a little tricky. We check the direction radio button to ascertain which direction the search should go. If the downward search is indicated, we set FR_DOWN flag to uFlags. After that, we check whether a selection is currently in effect by comparing the values of cpMin and cpMax. If both values are not equal, it means there is a current selection and we must continue the search from the end of that selection to the end of text in the control. Thus we need to replace the value of cpMax with that of cpMin and change the value of cpMax to -1 (0FFFFFFFFh). If there is no current selection, the range to search is from the current caret position to the end of text.

If the user chooses to search upward, we use the range from the start of the selection to the beginning of the text in the control. That's why we only modify the value of cpMax to 0. In the case of upward search, cpMin contains the character index of the last character in the search range and cpMax the character index of the first char in the search range. It's the inverse of the downward search.

invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE.if eax==BST_CHECKED

or uFlags,FR_MATCHCASE.endif

Page 241: 052 Iczelions_Win32_Assembly_Tutorials.pdf

241

invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD.if eax==BST_CHECKED

or uFlags,FR_WHOLEWORD.endifmov findtext.lpstrText,offset FindBuffer

We continue to check the checkboxes for the search flags, ie, FR_MATCHCASE and FR_WHOLEWORD. Lastly, we put the offset of the text to search for in lpstrText member.

invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext.if eax!=-1

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

.endif.endif

We are now ready to issue EM_FINDTEXTEX. After that, we examine the search result returned by SendMessage. If the return value is -1, no match is found in the search range. Otherwise, chrgText member of FINDTEXTEX structure is filled with the character indices of the matching text. We thus proceed to select it withEM_EXSETSEL.

The replace operation is done in much the same manner.

invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBufferinvoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer

We retrieve the text to search for and the text used to replace.

mov findtext.chrg.cpMin,0mov findtext.chrg.cpMax,-1mov findtext.lpstrText,offset FindBuffer

To make it easy, the replace operation affects all the text in the control. Thus the starting index is 0 and the ending index is -1.

mov settext.flags,ST_SELECTIONmov settext.codepage,CP_ACP

We initialize SETTEXTEX structure to indicate that we want to replace the current selection and use the default system code page.

.while TRUEinvoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext.if eax==-1

.break.else

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer

.endif.endw

We enter an infinite loop, searching for the matching text. If one is found, we select it with EM_EXSETSEL and replace it with EM_SETTEXTEX. When no more match is found, we exit the loop.

Page 242: 052 Iczelions_Win32_Assembly_Tutorials.pdf

242

Find Next and Find Prev. features use EM_FINDTEXTEX message in the similar manner to the find operation.

We will examine the Go to Line feature next. When the user clicks Go To Line menuitem, we display a dialog box below:

When the user types a line number and presses Ok button, we begin the operation.

invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSEmov LineNo,eax

Obtain the line number from the edit control

invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0.if eax>LineNo

Obtain the number of lines in the control. Check whether the user specifies the line number that is out of the range.

invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0

If the line number is valid, we want to move the caret to the first character of that line. So we send EM_LINEINDEX message to the richedit control. This message returns the character index of the first character in the specified line. We send the line number in wParam and in return, we has the character index.

invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eax

To set the current selection, this time we use EM_SETSEL because the character indices are not already in a CHARRANGE structure thus it saves us two instructions (to put those indices into a CHARRANGE structure).

invoke SetFocus,hwndRichEdit.endif

The caret will not be displayed unless the richedit control has the focus. So we call SetFocus on it.

Page 243: 052 Iczelions_Win32_Assembly_Tutorials.pdf

243

Tutorial 35: RichEdit Control: Syntax Hilighting

Before reading this tutorial, let me warn you that it's a complicated subject: not suited for a beginner. This is the last in the richedit control tutorials.

Download the example.

Theory

Syntax hilighting is a subject of hot debate for those writing text editors. The best method (in my opinion) is to code a custom edit control and this is the approach taken by lots of commercial softwares. However, for those of us who don't have time for coding such control, the next best thing is to adapt the existing control to make it suit our need.

Let us take a look at what RichEdit control provides to help us in implementing syntax hilighting. I should state at this moment that the following method is not the "correct" path: I just want to show you the pitfall that many fall for. RichEdit control provides EM_SETCHARFORMAT message that you can use to change the color of the text. At first glance, this message seems to be the perfect solution (I know because I was one of the victim). However, a closer examination will show you several things that are undesirable:

� EM_SETCHARFORMAT only works for a text currently in selection or all text in the control. If you want to change the text color (hilight) a certain word, you must first select it.

� EM_SETCHARFORMAT is very slow� It has a problem with the caret position in the richedit control

With the above discussion, you can see that using EM_SETCHARFORMAT is a wrong choice. I'll show you the "relatively correct" choice.

The method I currently use is "syntax hilighting just-in-time". I'll hilight only the visible portion of text. Thus the speed of the hilighting will not be related to the size of the file at all. No matter how large the file, only a small portion of it is visible at one time.

How to do that? The answer is simple:

1. subclass the richedit control and handle WM_PAINT message within your own window procedure2. When it receives WM_PAINT message, it calls the original window procedure of the richedit control to let

it update the screen as usual.3. After that, we overwrite the words to be hilighted with different color

Of course, the road is not that easy: there are still a couple of minor things to fix but the above method works quite nicely. The display speed is very satisfactory.

Now let's concentrate on the detail. The subclassing process is simple and doesn't require much attention. The really complicated part is when we have to find a fast way of searching for the words to be hilighted. This is further complicated by the need not to hilight any word within a comment block.

The method I use may not be the best but it works ok. I'm sure you can find a faster way. Anyway, here it is:

� I create a 256 dword array, initialized to 0. Each dword corresponds to a possible ASCII character,named ASMSyntaxArray. For example, the 21th dword represents the ascii 20h (space). I use them as a fast lookup table: For example, if I have the word "include", I'll extract the first character (i) from the word and look up the dword at the corresponding index. If that dword is 0, I know immediately that there is no

Page 244: 052 Iczelions_Win32_Assembly_Tutorials.pdf

244

words to be hilighted starting with "i". If the dword is non-zero, it contains the pointer to the linked list of the WORDINFO structure which contains the information about the word to be hilighted.

� I read the words to be hilighted and create a WORDINFO structure for each of them.

WORDINFO struct WordLen dd ? ; the length of the word: used as a

quick comparison pszWord dd ? ; pointer to the word pColor dd ? ; point to the dword that contains the

color used to hilite the word NextLink dd ? ; point to the next WORDINFO

structure WORDINFO ends

As you can see, I use the length of the word as the second quick comparison. If the first character of the word matches, we next compare its length to the available words. Each dword in ASMSyntaxArray contains a pointer to the head of the associated WORDINFO array. For example, the dword that represents the character "i" will contain the pointer to the linked list of the words that begin with "i". pColor member points to the dword that contains the color value used to hilight the word. pszWord points to the word to be hilighted, in lowercase.

� The memory for the linked list is allocated from the default heap so it's fast and easy to clean up, ie, no cleaning up required at all.

The word list is stored in a file named "wordfile.txt" and I access it with GetPrivateProfileString APIs. I provide as many as 10 different syntax coloring, starting from C1 to C10. The color array is named ASMColorArray. pColor member of each WORDINFO structure points to one of the dwords in ASMColorArray. Thus it is easy to change the syntax coloring on the fly: you just change the dword in ASMColorArray and all words using that color will use the new color immediately.

Example

.386

.model flat,stdcalloption casemap:noneinclude \masm32\include\windows.incinclude \masm32\include\user32.incinclude \masm32\include\comdlg32.incinclude \masm32\include\gdi32.incinclude \masm32\include\kernel32.incincludelib \masm32\lib\gdi32.libincludelib \masm32\lib\comdlg32.libincludelib \masm32\lib\user32.libincludelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

WORDINFO structWordLen dd ? ; the length of the word: used as a quick comparisonpszWord dd ? ; pointer to the wordpColor dd ? ; point to the dword that contains the color used to hilite the

wordNextLink dd ? ; point to the next WORDINFO structure

WORDINFO ends

.constIDR_MAINMENU equ 101

Page 245: 052 Iczelions_Win32_Assembly_Tutorials.pdf

245

IDM_OPEN equ 40001IDM_SAVE equ 40002IDM_CLOSE equ 40003IDM_SAVEAS equ 40004IDM_EXIT equ 40005IDM_COPY equ 40006IDM_CUT equ 40007IDM_PASTE equ 40008IDM_DELETE equ 40009IDM_SELECTALL equ 40010IDM_OPTION equ 40011IDM_UNDO equ 40012IDM_REDO equ 40013IDD_OPTIONDLG equ 101IDC_BACKCOLORBOX equ 1000IDC_TEXTCOLORBOX equ 1001IDR_MAINACCEL equ 105IDD_FINDDLG equ 102IDD_GOTODLG equ 103IDD_REPLACEDLG equ 104IDC_FINDEDIT equ 1000IDC_MATCHCASE equ 1001IDC_REPLACEEDIT equ 1001IDC_WHOLEWORD equ 1002IDC_DOWN equ 1003IDC_UP equ 1004IDC_LINENO equ 1005IDM_FIND equ 40014IDM_FINDNEXT equ 40015IDM_REPLACE equ 40016IDM_GOTOLINE equ 40017IDM_FINDPREV equ 40018RichEditID equ 300

.dataClassName db "IczEditClass",0AppName db "IczEdit version 3.0",0RichEditDLL db "riched20.dll",0RichEditClass db "RichEdit20A",0NoRichEdit db "Cannot find riched20.dll",0ASMFilterString db "ASM Source code (*.asm)",0,"*.asm",0

db "All Files (*.*)",0,"*.*",0,0OpenFileFail db "Cannot open the file",0WannaSave db "The data in the control is modified. Want to save it?",0FileOpened dd FALSEBackgroundColor dd 0FFFFFFh ; default to whiteTextColor dd 0 ; default to blackWordFileName db "\wordfile.txt",0ASMSection db "ASSEMBLY",0C1Key db "C1",0C2Key db "C2",0C3Key db "C3",0C4Key db "C4",0C5Key db "C5",0C6Key db "C6",0C7Key db "C7",0C8Key db "C8",0

Page 246: 052 Iczelions_Win32_Assembly_Tutorials.pdf

246

C9Key db "C9",0C10Key db "C10",0ZeroString db 0ASMColorArray dd 0FF0000h,0805F50h,0FFh,666F00h,44F0h,5F8754h,4 dup(0FF0000h)CommentColor dd 808000h

.data?hInstance dd ?hRichEdit dd ?hwndRichEdit dd ?FileName db 256 dup(?)AlternateFileName db 256 dup(?)CustomColors dd 16 dup(?)FindBuffer db 256 dup(?)ReplaceBuffer db 256 dup(?)uFlags dd ?findtext FINDTEXTEX <>ASMSyntaxArray dd 256 dup(?)hSearch dd ? ; handle to the search/replace dialog boxhAccel dd ?hMainHeap dd ? ; heap handleOldWndProc dd ?RichEditVersion dd ?

.codestart:

mov byte ptr [FindBuffer],0mov byte ptr [ReplaceBuffer],0invoke GetModuleHandle, NULLmov hInstance,eaxinvoke LoadLibrary,addr RichEditDLL.if eax!=0

mov hRichEdit,eaxinvoke GetProcessHeapmov hMainHeap,eaxcall FillHiliteInfoinvoke WinMain, hInstance,0,0, SW_SHOWDEFAULTinvoke FreeLibrary,hRichEdit

.elseinvoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR

.endifinvoke ExitProcess,eax

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORDLOCAL wc:WNDCLASSEXLOCAL msg:MSGLOCAL hwnd:DWORDmov wc.cbSize,SIZEOF WNDCLASSEXmov wc.style, CS_HREDRAW or CS_VREDRAWmov wc.lpfnWndProc, OFFSET WndProcmov wc.cbClsExtra,NULLmov wc.cbWndExtra,NULLpush hInstpop wc.hInstancemov wc.hbrBackground,COLOR_WINDOW+1mov wc.lpszMenuName,IDR_MAINMENUmov wc.lpszClassName,OFFSET ClassName

Page 247: 052 Iczelions_Win32_Assembly_Tutorials.pdf

247

invoke LoadIcon,NULL,IDI_APPLICATIONmov wc.hIcon,eaxmov wc.hIconSm,eaxinvoke LoadCursor,NULL,IDC_ARROWmov wc.hCursor,eaxinvoke RegisterClassEx, addr wcINVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\

WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\hInst,NULL

mov hwnd,eaxinvoke ShowWindow, hwnd,SW_SHOWNORMALinvoke UpdateWindow, hwndinvoke LoadAccelerators,hInstance,IDR_MAINACCELmov hAccel,eax.while TRUE

invoke GetMessage, ADDR msg,0,0,0.break .if (!eax)invoke IsDialogMessage,hSearch,addr msg.if eax==FALSE

invoke TranslateAccelerator,hwnd,hAccel,addr msg.if eax==0

invoke TranslateMessage, ADDR msginvoke DispatchMessage, ADDR msg

.endif.endif

.endwmov eax,msg.wParamret

WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORDinvoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0xor eax,1ret

StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORDinvoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0xor eax,1ret

StreamOutProc endp

CheckModifyState proc hWnd:DWORDinvoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0.if eax!=0

invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL.if eax==IDYES

invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0.elseif eax==IDCANCEL

mov eax,FALSEret

.endif.endifmov eax,TRUEret

CheckModifyState endp

Page 248: 052 Iczelions_Win32_Assembly_Tutorials.pdf

248

SetColor procLOCAL cfm:CHARFORMATinvoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColorinvoke RtlZeroMemory,addr cfm,sizeof cfmmov cfm.cbSize,sizeof cfmmov cfm.dwMask,CFM_COLORpush TextColorpop cfm.crTextColorinvoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfmret

SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL clr:CHOOSECOLOR.if uMsg==WM_INITDIALOG.elseif uMsg==WM_COMMAND

mov eax,wParamshr eax,16.if ax==BN_CLICKED

mov eax,wParam.if ax==IDCANCEL

invoke SendMessage,hWnd,WM_CLOSE,0,0.elseif ax==IDC_BACKCOLORBOX

invoke RtlZeroMemory,addr clr,sizeof clrmov clr.lStructSize,sizeof clrpush hWndpop clr.hwndOwnerpush hInstancepop clr.hInstancepush BackgroundColorpop clr.rgbResultmov clr.lpCustColors,offset CustomColorsmov clr.Flags,CC_ANYCOLOR or CC_RGBINITinvoke ChooseColor,addr clr.if eax!=0

push clr.rgbResultpop BackgroundColorinvoke GetDlgItem,hWnd,IDC_BACKCOLORBOXinvoke InvalidateRect,eax,0,TRUE

.endif.elseif ax==IDC_TEXTCOLORBOX

invoke RtlZeroMemory,addr clr,sizeof clrmov clr.lStructSize,sizeof clrpush hWndpop clr.hwndOwnerpush hInstancepop clr.hInstancepush TextColorpop clr.rgbResultmov clr.lpCustColors,offset CustomColorsmov clr.Flags,CC_ANYCOLOR or CC_RGBINITinvoke ChooseColor,addr clr.if eax!=0

push clr.rgbResultpop TextColorinvoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX

Page 249: 052 Iczelions_Win32_Assembly_Tutorials.pdf

249

invoke InvalidateRect,eax,0,TRUE.endif

.elseif ax==IDOKinvoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0push eaxinvoke SetColorpop eaxinvoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0invoke EndDialog,hWnd,0

.endif.endif

.elseif uMsg==WM_CTLCOLORSTATICinvoke GetDlgItem,hWnd,IDC_BACKCOLORBOX.if eax==lParam

invoke CreateSolidBrush,BackgroundColorret

.elseinvoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX.if eax==lParam

invoke CreateSolidBrush,TextColorret

.endif.endifmov eax,FALSEret

.elseif uMsg==WM_CLOSEinvoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

OptionProc endp

SearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD.if uMsg==WM_INITDIALOG

push hWndpop hSearchinvoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWNinvoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer

.elseif uMsg==WM_COMMANDmov eax,wParamshr eax,16.if ax==BN_CLICKED

mov eax,wParam.if ax==IDOK

mov uFlags,0invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr

findtext.chrginvoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr

FindBuffer,sizeof FindBuffer.if eax!=0

invoke IsDlgButtonChecked,hWnd,IDC_DOWN.if eax==BST_CHECKED

or uFlags,FR_DOWNmov eax,findtext.chrg.cpMin

Page 250: 052 Iczelions_Win32_Assembly_Tutorials.pdf

250

.if eax!=findtext.chrg.cpMaxpush findtext.chrg.cpMaxpop findtext.chrg.cpMin

.endifmov findtext.chrg.cpMax,-1

.elsemov findtext.chrg.cpMax,0

.endifinvoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE.if eax==BST_CHECKED

or uFlags,FR_MATCHCASE.endifinvoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD.if eax==BST_CHECKED

or uFlags,FR_WHOLEWORD.endifmov findtext.lpstrText,offset FindBufferinvoke

SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext.if eax!=-1

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

.endif.endif

.elseif ax==IDCANCELinvoke SendMessage,hWnd,WM_CLOSE,0,0

.elsemov eax,FALSEret

.endif.endif

.elseif uMsg==WM_CLOSEmov hSearch,0invoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

SearchProc endp

ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL settext:SETTEXTEX.if uMsg==WM_INITDIALOG

push hWndpop hSearchinvoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBufferinvoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer

.elseif uMsg==WM_COMMANDmov eax,wParamshr eax,16.if ax==BN_CLICKED

mov eax,wParam.if ax==IDCANCEL

invoke SendMessage,hWnd,WM_CLOSE,0,0.elseif ax==IDOK

Page 251: 052 Iczelions_Win32_Assembly_Tutorials.pdf

251

invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer

invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer

mov findtext.chrg.cpMin,0mov findtext.chrg.cpMax,-1mov findtext.lpstrText,offset FindBuffermov settext.flags,ST_SELECTIONmov settext.codepage,CP_ACP.while TRUE

invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext

.if eax==-1.break

.elseinvoke

SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgTextinvoke

SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer.endif

.endw.endif

.endif.elseif uMsg==WM_CLOSE

mov hSearch,0invoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

ReplaceProc endp

GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL LineNo:DWORDLOCAL chrg:CHARRANGE.if uMsg==WM_INITDIALOG

push hWndpop hSearch

.elseif uMsg==WM_COMMANDmov eax,wParamshr eax,16.if ax==BN_CLICKED

mov eax,wParam.if ax==IDCANCEL

invoke SendMessage,hWnd,WM_CLOSE,0,0.elseif ax==IDOK

invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSEmov LineNo,eaxinvoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0.if eax>LineNo

invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0

invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eaxinvoke SetFocus,hwndRichEdit

.endif

Page 252: 052 Iczelions_Win32_Assembly_Tutorials.pdf

252

.endif.endif

.elseif uMsg==WM_CLOSEmov hSearch,0invoke EndDialog,hWnd,0

.elsemov eax,FALSEret

.endifmov eax,TRUEret

GoToProc endp

PrepareEditMenu proc hSubMenu:DWORDLOCAL chrg:CHARRANGEinvoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0.if eax==0 ; no text in the clipboard

invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED.else

invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED.endifinvoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0.if eax==0

invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED.else

invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED.endifinvoke SendMessage,hwndRichEdit,EM_CANREDO,0,0.if eax==0

invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED.else

invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED.endifinvoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrgmov eax,chrg.cpMin.if eax==chrg.cpMax ; no current selection

invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYEDinvoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYEDinvoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED

.elseinvoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLEDinvoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLEDinvoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED

.endifret

PrepareEditMenu endp

ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD

LOCAL buffer[128]:BYTELOCAL InProgress:DWORDmov InProgress,FALSElea esi,buffermov edi,pBufferinvoke CharLower,edimov ecx,nSize

SearchLoop:

Page 253: 052 Iczelions_Win32_Assembly_Tutorials.pdf

253

or ecx,ecxjz Finishedcmp byte ptr [edi]," "je EndOfWordcmp byte ptr [edi],9 ; tabje EndOfWordmov InProgress,TRUEmov al,byte ptr [edi]mov byte ptr [esi],alinc esi

SkipIt:inc edidec ecxjmp SearchLoop

EndOfWord:cmp InProgress,TRUEje WordFoundjmp SkipIt

WordFound:mov byte ptr [esi],0push ecxinvoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFOpush esimov esi,eaxassume esi:ptr WORDINFOinvoke lstrlen,addr buffermov [esi].WordLen,eaxpush ArrayOffsetpop [esi].pColorinc eaxinvoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eaxmov [esi].pszWord,eaxmov edx,eaxinvoke lstrcpy,edx,addr buffermov eax,pArraymovzx edx,byte ptr [buffer]shl edx,2 ; multiply by 4add eax,edx.if dword ptr [eax]==0

mov dword ptr [eax],esi.else

push dword ptr [eax]pop [esi].NextLinkmov dword ptr [eax],esi

.endifpop esipop ecxlea esi,buffermov InProgress,FALSEjmp SkipIt

Finished:.if InProgress==TRUE

invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFOpush esimov esi,eaxassume esi:ptr WORDINFOinvoke lstrlen,addr buffer

Page 254: 052 Iczelions_Win32_Assembly_Tutorials.pdf

254

mov [esi].WordLen,eaxpush ArrayOffsetpop [esi].pColorinc eaxinvoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eaxmov [esi].pszWord,eaxmov edx,eaxinvoke lstrcpy,edx,addr buffermov eax,pArraymovzx edx,byte ptr [buffer]shl edx,2 ; multiply by 4add eax,edx.if dword ptr [eax]==0

mov dword ptr [eax],esi.else

push dword ptr [eax]pop [esi].NextLinkmov dword ptr [eax],esi

.endifpop esi

.endifret

ParseBuffer endp

FillHiliteInfo proc uses ediLOCAL buffer[1024]:BYTELOCAL pTemp:DWORDLOCAL BlockSize:DWORDinvoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArrayinvoke GetModuleFileName,hInstance,addr buffer,sizeof bufferinvoke lstrlen,addr buffermov ecx,eaxdec ecxlea edi,bufferadd edi,ecxstdmov al,"\"repne scasbcldinc edimov byte ptr [edi],0invoke lstrcat,addr buffer,addr WordFileNameinvoke GetFileAttributes,addr buffer.if eax!=-1

mov BlockSize,1024*10invoke HeapAlloc,hMainHeap,0,BlockSizemov pTemp,eax

@@:invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr

ZeroString,pTemp,BlockSize,addr buffer.if eax!=0

inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

Page 255: 052 Iczelions_Win32_Assembly_Tutorials.pdf

255

.endifmov edx,offset ASMColorArrayinvoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C2Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,4invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C3Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,8invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C4Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,12invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C5Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize

Page 256: 052 Iczelions_Win32_Assembly_Tutorials.pdf

256

mov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,16invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C6Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,20invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C7Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,24invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C8Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,28invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C9Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax

Page 257: 052 Iczelions_Win32_Assembly_Tutorials.pdf

257

.if eax==BlockSize ; the buffer is too smalladd BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,32invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endif@@:

invoke GetPrivateProfileString,addr ASMSection,addr C10Key,addr ZeroString,pTemp,BlockSize,addr buffer

.if eax!=0inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endifmov edx,offset ASMColorArrayadd edx,36invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

.endifinvoke HeapFree,hMainHeap,0,pTemp

.endifret

FillHiliteInfo endp

NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL hdc:DWORDLOCAL hOldFont:DWORDLOCAL FirstChar:DWORDLOCAL rect:RECTLOCAL txtrange:TEXTRANGELOCAL buffer[1024*10]:BYTELOCAL hRgn:DWORDLOCAL hOldRgn:DWORDLOCAL RealRect:RECTLOCAL pString:DWORDLOCAL BufferSize:DWORDLOCAL pt:POINT.if uMsg==WM_PAINT

push edipush esiinvoke HideCaret,hWndinvoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParampush eaxmov edi,offset ASMSyntaxArrayinvoke GetDC,hWndmov hdc,eaxinvoke SetBkMode,hdc,TRANSPARENTinvoke SendMessage,hWnd,EM_GETRECT,0,addr rectinvoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rectinvoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0invoke SendMessage,hWnd,EM_LINEINDEX,eax,0

Page 258: 052 Iczelions_Win32_Assembly_Tutorials.pdf

258

mov txtrange.chrg.cpMin,eaxmov FirstChar,eaxinvoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.rightmov txtrange.chrg.cpMax,eaxpush rect.leftpop RealRect.leftpush rect.toppop RealRect.toppush rect.rightpop RealRect.rightpush rect.bottompop RealRect.bottominvoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottommov hRgn,eaxinvoke SelectObject,hdc,hRgnmov hOldRgn,eaxinvoke SetTextColor,hdc,CommentColorlea eax,buffermov txtrange.lpstrText,eaxinvoke SendMessage,hWnd,EM_GETTEXTRANGE,0,addr txtrange.if eax>0

mov esi,eax ; esi == size of the textmov BufferSize,eaxpush edipush ebxlea edi,buffermov edx,edi ; used as the reference pointmov ecx,esimov al,";"

ScanMore:repne scasbje NextSkipjmp NoMoreHit

NextSkip:dec ediinc ecxmov pString,edimov ebx,edisub ebx,edxadd ebx,FirstCharmov txtrange.chrg.cpMin,ebxpush eaxmov al,0Dhrepne scasbpop eax

HiliteTheComment:.if ecx>0

mov byte ptr [edi-1],0.endifmov ebx,edisub ebx,edxadd ebx,FirstCharmov txtrange.chrg.cpMax,ebxpushadmov edi,pStringmov esi,txtrange.chrg.cpMax

Page 259: 052 Iczelions_Win32_Assembly_Tutorials.pdf

259

sub esi,txtrange.chrg.cpMin ; esi contains the length of the buffer

mov eax,esipush edi.while eax>0

.if byte ptr [edi]==9mov byte ptr [edi],0

.endifinc edidec eax

.endwpop edi.while esi>0

.if byte ptr [edi]!=0invoke lstrlen,edipush eaxmov ecx,edilea edx,buffersub ecx,edxadd ecx,FirstChar.if RichEditVersion==3

invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx

.elseinvoke

SendMessage,hWnd,EM_POSFROMCHAR,ecx,0mov ecx,eaxand ecx,0FFFFhmov rect.left,ecxshr eax,16mov rect.top,eax

.endifinvoke DrawText,hdc,edi,-1,addr rect,0pop eaxadd edi,eaxsub esi,eax

.elseinc edidec esi

.endif.endwmov ecx,txtrange.chrg.cpMaxsub ecx,txtrange.chrg.cpMininvoke RtlZeroMemory,pString,ecxpopad.if ecx>0

jmp ScanMore.endif

NoMoreHit:pop ebxpop edimov ecx,BufferSizelea esi,buffer.while ecx>0

mov al,byte ptr [esi]

Page 260: 052 Iczelions_Win32_Assembly_Tutorials.pdf

260

.if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9

mov byte ptr [esi],0.endifdec ecxinc esi

.endwlea esi,buffermov ecx,BufferSize.while ecx>0

mov al,byte ptr [esi].if al!=0

push ecxinvoke lstrlen,esipush eaxmov edx,eax ; edx contains the length of

the stringmovzx eax,byte ptr [esi].if al>="A" && al<="Z"

sub al,"A"add al,"a"

.endifshl eax,2add eax,edi ; edi contains the pointer to

the WORDINFO pointer array.if dword ptr [eax]!=0

mov eax,dword ptr [eax]assume eax:ptr WORDINFO.while eax!=0

.if edx==[eax].WordLenpushadinvoke

lstrcmpi,[eax].pszWord,esi.if eax==0

popadmov ecx,esilea edx,buffersub ecx,edxadd ecx,FirstCharpushad.if

RichEditVersion==3invoke

SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx.else

invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0

mov ecx,eax

and ecx,0FFFFh

mov rect.left,ecx

shr eax,16mov

rect.top,eax

Page 261: 052 Iczelions_Win32_Assembly_Tutorials.pdf

261

.endifpopadmov

edx,[eax].pColorinvoke

SetTextColor,hdc,dword ptr [edx]invoke

DrawText,hdc,esi,-1,addr rect,0.break

.endifpopad

.endifpush [eax].NextLinkpop eax

.endw.endifpop eaxpop ecxadd esi,eaxsub ecx,eax

.elseinc esidec ecx

.endif.endw

.endifinvoke SelectObject,hdc,hOldRgninvoke DeleteObject,hRgninvoke SelectObject,hdc,hOldFontinvoke ReleaseDC,hWnd,hdcinvoke ShowCaret,hWndpop eaxpop esipop ediret

.elseif uMsg==WM_CLOSEinvoke SetWindowLong,hWnd,GWL_WNDPROC,OldWndProc

.elseinvoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParamret

.endifNewRichEditProc endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORDLOCAL ofn:OPENFILENAMELOCAL buffer[256]:BYTELOCAL editstream:EDITSTREAMLOCAL hFile:DWORDLOCAL hPopup:DWORDLOCAL pt:POINTLOCAL chrg:CHARRANGE.if uMsg==WM_CREATE

invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\

CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0

mov hwndRichEdit,eax

Page 262: 052 Iczelions_Win32_Assembly_Tutorials.pdf

262

invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK

invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1.if eax==0 ; means this message is not processed

mov RichEditVersion,2.else

mov RichEditVersion,3invoke

SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT.endifinvoke SetWindowLong,hwndRichEdit,GWL_WNDPROC, addr NewRichEditProcmov OldWndProc,eaxinvoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0invoke SetColorinvoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTSinvoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0

.elseif uMsg==WM_NOTIFYpush esimov esi,lParamassume esi:ptr NMHDR.if [esi].code==EN_MSGFILTER

assume esi:ptr MSGFILTER.if [esi].msg==WM_RBUTTONDOWN

invoke GetMenu,hWndinvoke GetSubMenu,eax,1mov hPopup,eaxinvoke PrepareEditMenu,hPopupmov edx,[esi].lParammov ecx,edxand edx,0FFFFhshr ecx,16mov pt.x,edxmov pt.y,ecxinvoke ClientToScreen,hWnd,addr ptinvoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or

TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL.endif

.endifpop esi

.elseif uMsg==WM_INITMENUPOPUPmov eax,lParam.if ax==0 ; file menu

.if FileOpened==TRUE ; a file is already openedinvoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED

.elseinvoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED

.endif.elseif ax==1 ; edit menu

invoke PrepareEditMenu,wParam

Page 263: 052 Iczelions_Win32_Assembly_Tutorials.pdf

263

.elseif ax==2 ; search menu bar.if FileOpened==TRUE

invoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLEDinvoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED

.elseinvoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYEDinvoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED

.endif.endif

.elseif uMsg==WM_COMMAND.if lParam==0 ; menu commands

mov eax,wParam.if ax==IDM_OPEN

invoke RtlZeroMemory,addr ofn,sizeof ofnmov ofn.lStructSize,sizeof ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter,offset ASMFilterStringmov ofn.lpstrFile,offset FileNamemov byte ptr [FileName],0mov ofn.nMaxFile,sizeof FileNamemov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or

OFN_PATHMUSTEXISTinvoke GetOpenFileName,addr ofn.if eax!=0

invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUEmov hFile,eaxmov editstream.dwCookie,eaxmov editstream.pfnCallback,offset

StreamInProcinvoke

SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstreaminvoke

SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0invoke CloseHandle,hFilemov FileOpened,TRUE

.elseinvoke MessageBox,hWnd,addr

OpenFileFail,addr AppName,MB_OK or MB_ICONERROR.endif

.endif.elseif ax==IDM_CLOSE

invoke CheckModifyState,hWnd.if eax==TRUE

invoke SetWindowText,hwndRichEdit,0mov FileOpened,FALSE

.endif

Page 264: 052 Iczelions_Win32_Assembly_Tutorials.pdf

264

.elseif ax==IDM_SAVEinvoke CreateFile,addr

FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0.if eax!=INVALID_HANDLE_VALUE

@@:mov hFile,eaxmov editstream.dwCookie,eaxmov editstream.pfnCallback,offset StreamOutProcinvoke

SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstreaminvoke

SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0invoke CloseHandle,hFile

.elseinvoke MessageBox,hWnd,addr OpenFileFail,addr

AppName,MB_OK or MB_ICONERROR.endif

.elseif ax==IDM_COPYinvoke SendMessage,hwndRichEdit,WM_COPY,0,0

.elseif ax==IDM_CUTinvoke SendMessage,hwndRichEdit,WM_CUT,0,0

.elseif ax==IDM_PASTEinvoke SendMessage,hwndRichEdit,WM_PASTE,0,0

.elseif ax==IDM_DELETEinvoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0

.elseif ax==IDM_SELECTALLmov chrg.cpMin,0mov chrg.cpMax,-1invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

.elseif ax==IDM_UNDOinvoke SendMessage,hwndRichEdit,EM_UNDO,0,0

.elseif ax==IDM_REDOinvoke SendMessage,hwndRichEdit,EM_REDO,0,0

.elseif ax==IDM_OPTIONinvoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr

OptionProc,0.elseif ax==IDM_SAVEAS

invoke RtlZeroMemory,addr ofn,sizeof ofnmov ofn.lStructSize,sizeof ofnpush hWndpop ofn.hwndOwnerpush hInstancepop ofn.hInstancemov ofn.lpstrFilter,offset ASMFilterStringmov ofn.lpstrFile,offset AlternateFileNamemov byte ptr [AlternateFileName],0mov ofn.nMaxFile,sizeof AlternateFileNamemov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or

OFN_PATHMUSTEXISTinvoke GetSaveFileName,addr ofn.if eax!=0

invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0

.if eax!=INVALID_HANDLE_VALUEjmp @B

.endif

Page 265: 052 Iczelions_Win32_Assembly_Tutorials.pdf

265

.endif.elseif ax==IDM_FIND

.if hSearch==0invoke

CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0.endif

.elseif ax==IDM_REPLACE.if hSearch==0

invoke CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0

.endif.elseif ax==IDM_GOTOLINE

.if hSearch==0invoke

CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0.endif

.elseif ax==IDM_FINDNEXTinvoke lstrlen,addr FindBuffer.if eax!=0

invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

mov eax,findtext.chrg.cpMin.if eax!=findtext.chrg.cpMax

push findtext.chrg.cpMaxpop findtext.chrg.cpMin

.endifmov findtext.chrg.cpMax,-1mov findtext.lpstrText,offset FindBufferinvoke

SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext.if eax!=-1

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

.endif.endif

.elseif ax==IDM_FINDPREVinvoke lstrlen,addr FindBuffer.if eax!=0

invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg

mov findtext.chrg.cpMax,0mov findtext.lpstrText,offset FindBufferinvoke

SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext.if eax!=-1

invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText

.endif.endif

.elseif ax==IDM_EXITinvoke SendMessage,hWnd,WM_CLOSE,0,0

.endif.endif

.elseif uMsg==WM_CLOSEinvoke CheckModifyState,hWnd.if eax==TRUE

invoke DestroyWindow,hWnd

Page 266: 052 Iczelions_Win32_Assembly_Tutorials.pdf

266

.endif.elseif uMsg==WM_SIZE

mov eax,lParammov edx,eaxand eax,0FFFFhshr edx,16invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE

.elseif uMsg==WM_DESTROYinvoke PostQuitMessage,NULL

.elseinvoke DefWindowProc,hWnd,uMsg,wParam,lParamret

.endifxor eax,eaxret

WndProc endpend start

Analysis:

The first action before calling WinMain to to call FillHiliteInfo. This function reads the content of wordfile.txt and parses the content.

FillHiliteInfo proc uses ediLOCAL buffer[1024]:BYTELOCAL pTemp:DWORDLOCAL BlockSize:DWORDinvoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray

Initialize ASMSyntaxArray to zero.

invoke GetModuleFileName,hInstance,addr buffer,sizeof bufferinvoke lstrlen,addr buffermov ecx,eaxdec ecxlea edi,bufferadd edi,ecxstdmov al,"\"repne scasbcldinc edimov byte ptr [edi],0invoke lstrcat,addr buffer,addr WordFileName

Construct the full path name of wordfile.txt: I assume that it's always in the same folder as the program.

invoke GetFileAttributes,addr buffer.if eax!=-1

I use this method as a quick way of checking whether a file exists.

mov BlockSize,1024*10invoke HeapAlloc,hMainHeap,0,BlockSizemov pTemp,eax

Page 267: 052 Iczelions_Win32_Assembly_Tutorials.pdf

267

Allocate the memory block to store the words. Default to 10K. The memory is allocated from the default heap.

@@:invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr

ZeroString,pTemp,BlockSize,addr buffer.if eax!=0

I use GetPrivateProfileString to retrieve the content of each key in wordfile.txt. The key starts from C1 to C10.

inc eax.if eax==BlockSize ; the buffer is too small

add BlockSize,1024*10invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSizemov pTemp,eaxjmp @B

.endif

Checking whether the memory block is large enough. If it is not, we increment the size by 10K until the block is large enough.

mov edx,offset ASMColorArrayinvoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

Pass the words, the memory block handle, the size of the data read from wordfile.txt, the address of the color dword that will be used to hilight the words and the address of ASMSyntaxArray.

Now, let's examine what ParseBuffer does. In essence, this function accepts the buffer containing the words to be hilighted ,parses them to individual words and stores each of them in a WORDINFO structure array that can be accessed quickly from ASMSyntaxArray.

ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD

LOCAL buffer[128]:BYTELOCAL InProgress:DWORDmov InProgress,FALSE

InProgress is the flag I use to indicate whether the scanning process has begun. If the value is FALSE, we haven't encountered a non-white space character yet.

lea esi,buffermov edi,pBufferinvoke CharLower,edi

esi points to our local buffer that will contain the word we have parsed from the word list. edi points to the word list string. To simplify the search later, we convert all characters to lowercase.

mov ecx,nSizeSearchLoop:

or ecx,ecxjz Finishedcmp byte ptr [edi]," "je EndOfWordcmp byte ptr [edi],9 ; tabje EndOfWord

Page 268: 052 Iczelions_Win32_Assembly_Tutorials.pdf

268

Scan the whole word list in the buffer, looking for the white spaces. If a white space is found, we have to determine whether it marks the end or the beginning of a word.

mov InProgress,TRUEmov al,byte ptr [edi]mov byte ptr [esi],alinc esi

SkipIt:inc edidec ecxjmp SearchLoop

If the byte under scrutiny is not a white space, we copy it to the buffer to construct a word and then continue the scan.

EndOfWord:cmp InProgress,TRUEje WordFoundjmp SkipIt

If a white space is found, we check the value in InProgress. If the value is TRUE, we can assume that the white space marks the end of a word and we may proceed to put the word currently in the local buffer (pointed to by esi) into a WORDINFO structure. If the value is FALSE, we continue the scan until a non-white space character is found.

WordFound:mov byte ptr [esi],0push ecxinvoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO

When the end of a word is found, we append 0 to the buffer to make the word an ASCIIZ string. We then allocate a block of memory from the heap the size of WORDINFO for this word.

push esimov esi,eaxassume esi:ptr WORDINFOinvoke lstrlen,addr buffermov [esi].WordLen,eax

We obtain the length of the word in the local buffer and store it in the WordLen member of the WORDINFOstructure, to be used as a quick comparison.

push ArrayOffsetpop [esi].pColor

Store the address of the dword that contains the color to be used to hilight the word in pColor member.

inc eaxinvoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eaxmov [esi].pszWord,eaxmov edx,eaxinvoke lstrcpy,edx,addr buffer

Allocate memory from the heap to store the word itself. Right now, the WORDINFO structure is ready to be inserted into the appropriate linked list.

Page 269: 052 Iczelions_Win32_Assembly_Tutorials.pdf

269

mov eax,pArraymovzx edx,byte ptr [buffer]shl edx,2 ; multiply by 4add eax,edx

pArray contains the address of ASMSyntaxArray. We want to move to the dword that has the same index as the value of the first character of the word. So we put the first character of the word in edx then multiply edx by 4 (because each element in ASMSyntaxArray is 4 bytes in size) and then add the offset to the address of ASMSyntaxArray. We have the address of the corresponding dword in eax.

.if dword ptr [eax]==0mov dword ptr [eax],esi

.elsepush dword ptr [eax]pop [esi].NextLinkmov dword ptr [eax],esi

.endif

Check the value of the dword. If it's 0, it means there is currently no word that begins with this character in the list. We thus put the address of the current WORDINFO structure in that dword.

If the value in the dword is not 0, it means there is at least one word that begins with this character in the array. We thus insert this WORDINFO structure to the head of the linked list and update its NextLink member to point to the next WORDINFO structure.

pop esipop ecxlea esi,buffermov InProgress,FALSEjmp SkipIt

After the operation is complete, we begin the next scan cycle until the end of buffer is reached.

invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK

invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1.if eax==0 ; means this message is not processed

mov RichEditVersion,2.else

mov RichEditVersion,3invoke

SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT.endif

After the richedit control is created, we need to determine the its version. This step is necessary since EM_POSFROMCHAR behaves differently for RichEdit 2.0 and 3.0 and EM_POSFROMCHAR is crucial to our syntax hilighting routine. I have never seen a documented way of checking the version of richedit control thus I have to use a workaround. In this case, I set an option that is specific to version 3.0 and immediately retrieve its value. If I can retrieve the value, I assume that the control version is 3.0.

If you use RichEdit control version 3.0, you will notice that updating the font color for a large file takes quite a long time. This problem seems to be specific to version 3.0. I found a workaround: making the control emulate the behavior of the system edit control by sending EM_SETEDITSTYLE message.

Page 270: 052 Iczelions_Win32_Assembly_Tutorials.pdf

270

After we can obtain the version information, we proceed to subclass the richedit control. We will now examine the new window procedure for the richedit control.

NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD................if uMsg==WM_PAINT

push edipush esiinvoke HideCaret,hWndinvoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParampush eax

We handle WM_PAINT message. First, we hide the caret so as to avoid some ugly gfx after the hilighting. After that we pass the message to the original richedit procedure to let it update the window. When CallWindowProcreturns, the text is updated with its usual color/background. Now is our opportunity to do syntax hilighting.

mov edi,offset ASMSyntaxArrayinvoke GetDC,hWndmov hdc,eaxinvoke SetBkMode,hdc,TRANSPARENT

Store the address of ASMSyntaxArray in edi. Then we obtain the handle to the device context and set the text background mode to transparent so the text that we will write will use the default background color.

invoke SendMessage,hWnd,EM_GETRECT,0,addr rectinvoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rectinvoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0invoke SendMessage,hWnd,EM_LINEINDEX,eax,0

We want to obtain the visible text so we first have to obtain the formatting rectangle by sending EM_GETRECTmessage to the richedit control. Now that we have the bounding rectangle, we obtain the nearest character index to the upper left corner of the rectangle with EM_CHARFROMPOS. Once we have the character index (the first visible character in the control), we can start to do syntax hilighting starting from that position. But the effect might not be as good as when we start from the first character of the line that the character is in. That's why I need to obtain the line number of that the first visible character is in by sending EM_LINEFROMCHAR message. To obtain the first character of that line, I send EM_LINEINDEX message.

mov txtrange.chrg.cpMin,eaxmov FirstChar,eaxinvoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.rightmov txtrange.chrg.cpMax,eax

Once we have the first character index, store it for future reference in FirstChar variable. Next we obtain the last visible character index by sending EM_CHARFROMPOS, passing the lower-right corner of the formatting rectangle in lParam.

push rect.leftpop RealRect.leftpush rect.toppop RealRect.toppush rect.rightpop RealRect.rightpush rect.bottompop RealRect.bottom

Page 271: 052 Iczelions_Win32_Assembly_Tutorials.pdf

271

invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottommov hRgn,eaxinvoke SelectObject,hdc,hRgnmov hOldRgn,eax

While doing syntax hilighting, I noticed an unsightly side-effect of this method: if the richedit control has a margin (you can specify margin by sending EM_SETMARGINS message to the richedit control), DrawText writes over the margin. Thus I need to create a clipping region, the size of the formatting rectangle, by calling CreateRectRgn. The output of GDI functions will be clipped to the "writable" area.

Next, we need to hilight the comments first and get them out of our way. My method is to search for ";" and hilight the text with the comment color until the carriage return is found. I will not analyze the routine here: it's fairly long and complicated. Suffice here to say that, when all the comments are hilighted, we replace them with 0s in the buffer so that the words in the comments will not be processed/hilighted later.

mov ecx,BufferSizelea esi,buffer.while ecx>0

mov al,byte ptr [esi].if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" ||

al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9

mov byte ptr [esi],0.endifdec ecxinc esi

.endw

Once the comments are out of our way, we separate the words in the buffer by replacing the "separator" characters with 0s. With this method, we need not concern about the separator characters while processing the words in the buffer anymore: there is only one separator character, NULL.

lea esi,buffermov ecx,BufferSize.while ecx>0

mov al,byte ptr [esi].if al!=0

Search the buffer for the first character that is not null,ie, the first character of a word.

push ecxinvoke lstrlen,esipush eaxmov edx,eax

Obtain the length of the word and put it in edx

movzx eax,byte ptr [esi].if al>="A" && al<="Z"

sub al,"A"add al,"a"

.endif

Convert the character to lowercase (if it's an uppercase character)

Page 272: 052 Iczelions_Win32_Assembly_Tutorials.pdf

272

shl eax,2add eax,edi ; edi contains the pointer to the

WORDINFO pointer array.if dword ptr [eax]!=0

After that, we skip to the corresponding dword in ASMSyntaxArray and check whether the value in that dword is 0. If it is, we can skip to the next word.

mov eax,dword ptr [eax]assume eax:ptr WORDINFO.while eax!=0

.if edx==[eax].WordLen

If the value in the dword is non-zero, it points to the linked list of WORDINFO structures. We process to walk the linked list, comparing the length of the word in our local buffer with the length of the word in the WORDINFOstructure. This is a quick test before we compare the words. Should save some clock cycles.

pushadinvoke lstrcmpi,[eax].pszWord,esi.if eax==0

If the lengths of both words are equal, we proceed to compare them with lstrcmpi.

popadmov ecx,esilea edx,buffersub ecx,edxadd ecx,FirstChar

We construct the character index from the address of the first character of the matching word in the buffer. We first obtain its relative offset from the starting address of the buffer then add the character index of the first visible character to it.

pushad.if RichEditVersion==3

invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx

.elseinvoke

SendMessage,hWnd,EM_POSFROMCHAR,ecx,0mov ecx,eaxand ecx,0FFFFhmov rect.left,ecxshr eax,16mov rect.top,eax

.endifpopad

Once we know the character index of the first character of the word to be hilighted, we proceed to obtain the coordinate of it by sending EM_POSFROMCHAR message. However, this message is interpreted differently by richedit 2.0 and 3.0. For richedit 2.0, wParam contains the character index and lParam is not used. It returns the coordinate in eax. For richedit 3.0, wParam is the pointer to a POINT structure that will be filled with the coordinate and lParam contains the character index.

Page 273: 052 Iczelions_Win32_Assembly_Tutorials.pdf

273

As you can see, passing the wrong arguments to EM_POSFROMCHAR can wreak havoc to your system. That's why I have to differentiate between RichEdit control versions.

mov edx,[eax].pColorinvoke

SetTextColor,hdc,dword ptr [edx]invoke DrawText,hdc,esi,-

1,addr rect,0

Once we got the coordinate to start, we set the text color with the one specified in the WORDINFO structure. And then proceed to overwrite the word with the new color.

As the final words, this method can be improved in several ways. For example, I obtain all the text starting from the first to the last visible line. If the lines are very long, the performance may hurt by processing the words that are not visible. You can optimize this by obtaining the really visible text line by line. Also the searching algorithm can be improved by using a more efficient method. Don't take me wrong: the syntax hilighting method used in thisexample is FAST but it can be FASTER. :)