So, let's review about Windows messages.
When a program makes a new window, it has to also have a window procedure for the window. The window procedure is the function that receives Windows messages about events and acts upon those events accordingly.
For example, the user is trying to move the window to the side of the screen, so that there would be more room for another window. The window being moved receives a message that says that it has been moved and where its new location is.
Another example is if a key is pressed. When a key is pressed, the WM_KEYDOWN message is sent to the window. Then, when the key is released, a WM_KEYUP message is sent to the window. But in general, when a character key is pressed, a WM_CHAR message is sent to the window. All three of these messages send the virtual key code of the key they're talking about in the wParam parameter.
For the mouse, there is WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_MBUTTONDOWN, and WM_MBUTTONUP. In those message names, "L" means left, and refers to the left mouse button, "R" means right, and refers to the right mouse button, and "M" means middle and refers to the middle mouse button.
In general, the lParam parameter contains the X and Y coordinates of the mouse cursor at that moment. The X coordinate is the lower-order word and the Y coordinate is the higher-order word. In other words, bits 0 through 15 of lParam represent the X coordinate of the mouse cursor, and bits 16 through 31 represent the Y coordinate of the mouse cursor.
Let's review the four main string functions.
The String Functions
Our string functions use the standard calling convention.
The four functions we defined are:
- strlen - Find the length of the string pointed to by argument 1.
- strcpy - Copy the string pointed to by argument 2 to the buffer pointed to by argument 1.
- strcat - String concatenate; append the string pointed to by argument 2 to the string pointed to by the argument 1.
- strcmp - Compare the string pointed to by argument 1 to the string pointed to by argument 2 and return the difference. (0 means the strings are equal.)
So let's now review the integer-string functions.
The Integer-String Functions
The two integer-string functions we defined also use the standard calling convention; they are:
- i2str - Convert from the integer provided by argument 1 to the string which is pointed to by argument 2.
- str2i - Convert from a string, pointed to by argument 1, to an integer (the result is returned (in EAX)).
NASM - %include syntax
In different compilers or assemblers, you can use the include syntax. For NASM, it's the %include directive.
For example, to include everything that's inside the file 'somecode.asm', you would type:
Mouse-Keyboard-Text - The Idea
The idea, here, is:
user_string is a string that we will use for the "screen buffer."
when a key is pressed and there's a character code:
- if the key was backspace, delete** the last character or new-line combination from user_string
**note: do not do anything if user_string is already empty.
- if the key was return (enter key), append a new-line (CRLF or "\r\n") combination to user_string
- otherwise, append the character code to user_string
when the left mouse button is released:
when it's time to paint the window:
- draw the text that user_string contains onto the client area of our window
when the window has been destroyed (closed):
- tell Windows to tell the message loop that it's time to exit, already
for all other cases, ask Windows to use the default procedure for the current message
Mouse-Keyboard-Text - The Code
Note that it is assumed, in the code, that you have a folder named 'inc' in the same directory as the .asm file. Inside that folder need to be two files: 'str.asm' and 'istr.asm'. The former defines the four string functions, while the latter defines the two integer-string functions.
Here's the main code (note: orange text denotes text that is new in this tutorial):
;; Define the externs for the functions that we'll use in this program. extern GetModuleHandleA extern GetCommandLineA extern ExitProcess extern MessageBoxA extern LoadIconA extern LoadCursorA extern RegisterClassExA extern CreateWindowExA extern ShowWindow extern UpdateWindow extern GetMessageA extern TranslateMessage extern DispatchMessageA extern PostQuitMessage extern DefWindowProcA extern BeginPaint extern DrawTextA extern EndPaint ;[COLOR=#FF8C00] extern GetClientRect ;; This function is not the main focus of this tutorial, ;; but it's the function we use to get the rectangle that fills the entire client area of our window. extern InvalidateRect ;; We'll need this function to re-paint our window when a key or the mouse button is pressed. [/COLOR] ;; Import the Win32 API functions. import GetModuleHandleA kernel32.dll import GetCommandLineA kernel32.dll import ExitProcess kernel32.dll import MessageBoxA user32.dll import LoadIconA user32.dll import LoadCursorA user32.dll import RegisterClassExA user32.dll import CreateWindowExA user32.dll import ShowWindow user32.dll import UpdateWindow user32.dll import GetMessageA user32.dll import TranslateMessage user32.dll import DispatchMessageA user32.dll import PostQuitMessage user32.dll import DefWindowProcA user32.dll import BeginPaint user32.dll import DrawTextA user32.dll import EndPaint user32.dll ;[COLOR=#FF8C00] import GetClientRect user32.dll import InvalidateRect user32.dll ;[/COLOR] ;; Tell NASM that we're about to type things for the code section. section .text use32 ;; We specify that here is the place where the program should start ;; executing. ..start: ;; We pass 0 as a parameter. push dword 0 ;; Then we call the GetModuleHandle() function. call [GetModuleHandleA] ;; And we store the result (which is in EAX) in the hInstance global variable. mov dword [hInstance], eax ;; Then we call the function to get the command line for our program. call [GetCommandLineA] ;; And we store the result in the CommandLine global variable. mov dword [CommandLine], eax ;; Now we call our WindowMain() function. ;; The parameters to pass are: hInstance, 0, CommandLine, SW_SHOWDEFAULT ;; SW_<something> is a Windows constant for how to show a window. ;; If we look into windows.h or windows.inc, we'll find that ;; SW_SHOWDEFAULT is defined as 10, so we'll pass that as the last argument. push dword 10 ;; Now the CommandLine variable. push dword [CommandLine] ;; The brackets tell NASM to use a memory access, and not a memory address. ;; And a NULL (NULL is equal to 0). push dword 0 ;; Then the hInstance variable. push dword [hInstance] ;; Once again, we don't want the pointer to hInstance, we want the actual value. ;; And we make a call to WindowMain(). call WindowMain ;; Then we exit the program, returning EAX, which is what WindowMain() will return. push eax call [ExitProcess] ;; This is now the WindowMain() function. ;; We will want to reserve enough stack space for a WNDCLASSEX structure so ;; we can make a class for our window, a MSG structure so we can receive messages ;; from our window when some event happens, and an HWND, which is just a ;; double-word that's used for storing the handle to our window. WindowMain: ;; WNDCLASSEX is 48 bytes in size. Let's use [ebp-48] for the start of our ;; window class structure. MSG is 28 bytes in size; let's use [ebp-48-24] ;; = [ebp-72] for that. Then there's HWND, which is 4 bytes in size. ;; We'll use [ebp-76] to store that value. ;; So we'll have to reserve 76 bytes on the stack. enter 76, 0 ;; We need to fill out the WNDCLASSEX structure, now. lea ebx, [ebp-48] ;; We load EBX with the address of our WNDCLASSEX structure. ;; The structure of WNDCLASSEX can be found at this page: ;; http://msdn.microsoft.com/en-us/library/ms633577(v=vs.85).aspx mov dword [ebx+00], 48 ;; Offset 00 is the size of the structure. mov dword [ebx+04], 3 ;; Offset 04 is the style for the window. 3 is equal to CS_HREDRAW | CS_VREDRAW mov dword [ebx+08], WindowProcedure ;; Offset 08 is the address of our window procedure. mov dword [ebx+12], 0 ;; I'm not sure what offset 12 and offset 16 are for. mov dword [ebx+16], 0 ;; But I do know that they're supposed to be NULL, at least for now. mov eax, dword [ebp+8] ;; We load the hInstance value. mov dword [ebx+20], eax ;; Offset 20 is the hInstance value. mov dword [ebx+32], 5 + 1 ;; Offset 32 is the handle to the background brush. We set that to COLOR_WINDOW + 1. mov dword [ebx+36], 0 ;; Offset 36 is the menu name, what we set to NULL, because we don't have a menu. mov dword [ebx+40], ClassName ;; Offset 40 is the class name for our window class. ;; Note that when we're trying to pass a string, we pass the memory address of the string, and the ;; function to which we pass that address takes care of the rest. ;; LoadIcon(0, IDI_APPLICATION) where IDI_APPLICATION is equal to 32512. push dword 32512 push dword 0 call [LoadIconA] ;; All Win32 API functions preserve the EBP, EBX, ESI, and EDI registers, so it's ;; okay if we use EBX to store the address of the WNDCLASSEX structure, for now. mov dword [ebx+24], eax ;; Offset 24 is the handle to the icon for our window. mov dword [ebx+44], eax ;; Offset 44 is the handle to the small icon for our window. ;; LoadCursor(0, IDC_ARROW) where IDC_ARROW is equal to 32512. push dword 32512 push dword 0 call [LoadCursorA] mov dword [ebx+28], eax ;; Offset 28 is the handle to the cursor for our window. ;; Now we register our window class with Windows, so that we can use the class name ;; for our window, when we make that. ;; Since EBX already has the address of our WNDCLASSEX structure, we can just pussh ;; EBX, so we don't have to reload the address of that structure. push ebx call [RegisterClassExA] ;; CreateWindowEx(0, ClassName, window title, WS_OVERLAPPEDWINDOW, x, y, width, height, handle to parent window, handle to menu, hInstance, NULL); push dword 0 push dword [ebp+8] push dword 0 push dword 0 push dword 400 ;; 400 pixels high. push dword 500 ;; 500 pixels wide. push dword 0x80000000 ;; CW_USEDEFAULT push dword 0x80000000 ;; CW_USEDEFAULT push dword 0x00 | 0xC00000 | 0x80000 | 0x40000 | 0x20000 | 0x10000 ;; WS_OVERLAPPEDWINDOW ;; WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX push dword ApplicationName push dword ClassName push dword 0 call [CreateWindowExA] ;; Store the result (which should be a handle to our window) in [ebp-76]. mov dword [ebp-76], eax ;; Check if EAX is zero. If so, jump to the error-handling routine. sub eax, 0 ;; The only difference between SUB and CMP is that CMP doesn't store the result in the first operand. ;; Here we're subtracting 0 from EAX, which won't change EAX, so it doesn't matter if we use SUB. jz .new_window_failed ;; Now we need to show the window and update the window. ;; ShowWindow([ebp-76], [ebp+20]) push dword [ebp+20] push dword [ebp-76] call [ShowWindow] ;; UpdateWindow([ebp-76]) push dword [ebp-76] call [UpdateWindow] .MessageLoop: ;; GetMessage(the MSG structure, 0, 0, 0) push dword 0 push dword 0 push dword 0 lea ebx, [ebp-72] push ebx call [GetMessageA] ;; If GetMessage() returns 0, it's time to exit. cmp eax, 0 jz .MessageLoopExit ;; TranslateMessage(the MSG) lea ebx, [ebp-72] push ebx call [TranslateMessage] ;; DispatchMessage(the MSG) lea ebx, [ebp-72] push ebx call [DispatchMessageA] ;; And start the loop over again. jmp .MessageLoop .MessageLoopExit: ;; We'll need to jump over the error-handling routing, so we can continue. jmp .finish .new_window_failed: ;; Display a message box with the error message. push dword 0 push dword 0 push dword err_msg push dword 0 call [MessageBoxA] ;; Exit, returning 1. mov eax, 1 leave ret 16 .finish: ;; We return the MSG.wParam value. lea ebx, [ebp-72] mov eax, dword [ebx+08] ;; It's time to leave. leave ;; And, since WindowMain() has 4 arguments, we free 4 * 4 = 16 bytes from ;; the stack, after we return. ret 16 ;; We also need a procedure to handle the events that our window sends us. ;; We call that procedure WindowProcedure(). ;; It also has to take 4 arguments, which are as follows: ;; hWnd The handle to the window that sent us that event. ;; This would be the handle to the window that uses ;; our window class. ;; uMsg This is the message that the window sent us. It ;; describes the event that has happened. ;; wParam This is a parameter that goes along with the ;; event message. ;; lParam This is an additional parameter for the message. ;; If we process the message, we have to return 0. ;; Otherwise, we have to return whatever the DefWindowProc() function ;; returns. DefWindowProc() is kind of like the "default window procedure" ;; function. It takes the default action, based on the message. ;; For now, we only care about the WM_DESTROY message, which tells us ;; that the window has been closed. If we don't take care of the ;; WM_DESTROY message, who knows what will happen. ;; Later on, of course, we can expand our window to process other ;; messages too. WindowProcedure: ;; The PAINTSTRUCT structure is 56 bytes in size. ;; Then also, the HDC value is 4 bytes in size. ;; (Note: HDC means "handle to device context.") ;; We'll also need enough space for a RECT ;; structure, which is 4 * 4 bytes in size. ;; 56 + 4 + 16 = 76. ;; PAINTSTRUCT ps = [ebp-56] ;; HDC hdc = [ebp-60] ;; RECT rectangle = [ebp-76] enter 76, 0 ;; We need to retrieve the uMsg value. mov eax, dword [ebp+12] ;; We get the value of the second argument. ;; Now here comes the new instruction. We need to compare the value we just ;; retrieved to WM_DESTROY to see if the message is a WM_DESTROY message. ;; If so, we'll jump to the .window_destroy label. cmp eax, 2 ;; Compare EAX to WM_DESTROY, which is equal to 2. jz .window_destroy ;; If it's equal to what we compared it to, jump to ;; the .window_destroy label. ;; If the processor doesn't jump to the .window_destroy label, it means that ;; the result of the comparison is not equal. In that case, the message ;; must be something else. ;; Check to see whether the message is WM_PAINT (which is 0x0F). ;; If so, jump to .window_paint cmp eax, 0x0F jz .window_paint ;[COLOR=#FF8C00]; Check if the message is WM_LBUTTONUP (left cursor button released (after being pressed)), ;; and if it is, jump to .window_click cmp eax, 0x202 ;; WM_LBUTTONUP = 202 hex jz .window_click ;; Check to see if the message is WM_CHAR (a character from a key press). ;; We could also check for WM_KEYDOWN or WM_KEYUP to catch key/button ;; events, but those events happen even when the mouse buttons are ;; used. cmp eax, 0x102 ;; WM_CHAR = 102 hex jz .window_character ;[/COLOR] ;; In cases like this we can either take care of the message right now, or ;; we can jump to another location in the code that would take care of the ;; message. ;; We'll just jump to the .window_default label. jmp .window_default ;; We need to define the .window_destroy label, now. .window_destroy: ;; If uMsg is equal to WM_DESTROY (2), then the processor will execute this ;; code next. ;; We pass 0 as an argument to the PostQuitMessage() function, to tell it ;; to pass 0 as the value of wParam for the next message. At that point, ;; GetMessage() will return 0, and the message loop will terminate. push dword 0 ;; Now we call the PostQuitMessage() function. call [PostQuitMessage] ;; When we're done doing what we need to upon the WM_DESTROY condition, ;; we need to jump over to the end of this area, or else we'd end up ;; in the .window_default code, which won't be very good. jmp .window_finish ;; This is the .window_paint part of the code. .window_paint: ;; Windows does not save every pixel of every window; when ;; a window is minimized, or covered, the pixel information ;; is lost. That's why we need to make code that would ;; re-draw everything inside the window, every time ;; Windows asks it to. ;; First of all, we need to ask Windows for a handle to a ;; device context for our window. ;; To do that, we need to use the BeginPaint() Win32 API function. ;; BeginPaint(hWnd, address of ps) lea ebx, [ebp-56] push ebx push dword [ebp+8] call [BeginPaint] ;; And we save the handle to the device context. mov dword [ebp-60], eax ;; Don't forget to use EndPaint() after using the HDC, or else ;; you would run out of Graphical Device Interface (GDI) resources. ;[COLOR=#FF8C00];; Now is the time to fill the RECT structure. lea ebx, [ebp-76] ;; EBX= address of the rectangle structure. ;; We'll use the GetClientRect() Win32 API function to fill in the RECT ;; structure, this time. The GetClientRect() function specifies the ;; rectangle that would fit inside the client area of our window. ;; The client area is the area where the buttons, edit controls, ;; drawings, etc., appear within the window. push ebx push dword [ebp+8] call [GetClientRect] ;; Once again, EBX is preserved, so we can use it again after the function call. [/COLOR] push dword 0 ;; Format NULL. push ebx ;; EBX is currently the address of our rectangle. push dword -1 ;; The character count in our text. If this parameter is -1 (which it is), ;; then the text is assumed to be a NULL-terminated string. [COLOR=#FF8C00] push dword user_string ;; The text we are drawing. [/COLOR] push dword [ebp-60] ;; The handle to the device context to draw to. call [DrawTextA] ;; After we're finished using the HDC, we need to notify Windows of that. ;; We do that by using the EndPaint() function, which takes the same ;; parameters as the BeginPaint() function. lea ebx, [ebp-56] push ebx push dword [ebp+8] call [EndPaint] jmp .window_finish ;[COLOR=#FF8C00]; Then we define the .window_character label. .window_character: ;; wParam is the character code of the key that was pressed. mov eax, dword [ebp+16] ;; Now we check the character code to see if it's 8 (backspace). cmp eax, 8 jz .window_character_is_backspace ;; Check if the character code is 13 (carriage return). cmp eax, 13 jz .window_character_return ;; Otherwise, handle the character differently. jmp .window_character_other .window_character_is_backspace: ;; We get the address of the last character in the user_string string. push dword user_string call strlen ;; EAX= the length of string user_string. add eax, user_string ;; EAX= the address of the string user_string + (the length of the string user_string). ;; So EAX is a pointer to the NULL terminator byte of the string, at this point. dec eax ;; But we want the pointer to the last character, so we decrement EAX. ;; Now EAX points to the character right before the last character. mov ebx, eax ;; Since EBX is the base/address register, it would be best to store such a ;; pointer in EBX. cmp byte [ebx], 10 ;; Check if that byte is "\n". jnz .window_character_is_backspace_over1 ;; If not, jump over. ;; If so, it means that a "\r" character preceeds the "\n" character. ;; In that case we have to go back a step to include the "\r" character. dec ebx ;; Now EBX *should* point to the "\r" character. .window_character_is_backspace_over1: ;; We need to check to make sure that we're not about to overwrite the variable that comes right before ;; user_string. cmp ebx, user_string jl .window_character_finish ;; We don't want to interfere with other variables' values. ;; Memory Map: ;; some other variables... | user_string ... | ..... ;; So if user_string is already 0 characters long, we would get the address of user_string ;; in EBX, at the code above this. Then we decrement EBX, so it points to the variable ;; right below it in memory. We don't want that. That's why we check to make sure ;; that EBX points to user_string or higher; otherwise we skip this part and finish. ;; Now we truncate the string to not include the character EBX is pointing to and characters after it. mov byte [ebx], 0 ;; We just set byte [ebx] to 0 (NULL), which would tell other functions, later on, that that's the end ;; of the string, so that the character EBX is pointing to right now, and the characters after it, ;; will be omitted. What else would you expect when pressing the backspace key? jmp .window_character_finish ;; And we jump over the rest of the .window_character_* code ;; to the end of this. .window_character_return: ;; EAX is the character code, at the moment, so we can use it as such. mov ebx, string1 ;; Load EBX with the address of string1. mov byte [ebx], al ;; Put the character code into byte [EBX]. ;; The thing is, if EAX is 65 then AL would be 65. If EAX is 102 then AL will be 102. ;; If EAX is 258, then AL will be 2. What? In case you don't know from the previous ;; tutorials, AL is the lowest byte of EAX, meaning that it's the least significant ;; byte. AH is the next least significant byte. To get AX, it's: AX= AH * 256 + AL. ;; That is because AL only goes up to 255, and to get 256 you would need to set AH ;; to 1 and AL to 0. 257 is 1 for AH and 1 for AL. 258 AH is still 1 and AL is 2. ;; When AL is 255 and it's time to increment AX, AH is incremented and AL is reset ;; to 0. So 512 in AX would mean 2 in AH and 0 in AL. And so on. ;; In this case, since our character codes really only go up to 255 (we don't care ;; about higher character codes for now), just the least significant byte is enough. mov byte [ebx+1], 10 ;; If the character is "\r", we need to add an "\n" ;; to make a CRLF (carriage-return, line-feed). mov byte [ebx+2], 0 ;; And we NULL-terminate our little string. ;; strcat() is the string concatenate function. It takes two string pointers as ;; parameters. One way to look at it is, it adds the second string to the first ;; string. push ebx push dword user_string call strcat ;; Then we jump over to the end. jmp .window_character_finish .window_character_other: mov ebx, string1 ;; Similar to the .window_character_return code. mov byte [ebx], al ;; We save the character code. mov byte [ebx+1], 0 ;; And we NULL-terminate the string. ;; And then we append the character to the end of the user_string string. push ebx push dword user_string call strcat ;; And we jump over to the next instruction. There's no point of that, but I ;; put this here just for consistency. jmp .window_character_finish .window_character_finish: ;; The InvalidateRect() Win32 API function tells Windows that it's time to re-paint the window specified. ;; We can either pass it a rectangle, so it only refreshes that part of the window, or we can tell it ;; to refresh the whole window, by passing it NULL instead of the rectangle pointer. ;; The other thing, if the last parameter is TRUE (1 or non-zero), then the background of the window ;; will be re-painted; the background won't be re-painted, otherwise. ;; And the first parameter is the handle to the window to re-paint. If we pass NULL (0) for that, ;; it'll re-paint all the windows it can. push dword 1 ;; We do want it to re-paint the background. push dword 0 ;; We want it to refresh the whole client area. push dword [ebp+8] ;; The handle to our window. call [InvalidateRect] ;; And we call the function. jmp .window_finish ;; Now we define the .window_click label. .window_click: ;; The mouse has been clicked. ;; The job, here, is to append the coordinates of the mouse cursor to our user_string string. ;; We first need to write "(" to our user_string string. mov ebx, string1 mov byte [ebx], "(" mov byte [ebx+1], 0 push ebx push dword user_string call strcat ;; We get the X coordinate. mov eax, dword [ebp+20] ;; lParam has the coordinates. push ax xor eax, eax pop ax ;; The low-order word has the X coordinate. ;; Now we put the X coordinate, converted to a string, into string1. push dword string1 push eax call i2str ;; The i2str() function converts an integer to a string. The first argument is the ;; integer, and the second argument is the string to save the number to. ;; Now we append the new number string to our user_string string. push dword string1 push dword user_string call strcat ;; We append ", " to our user_string string. mov ebx, string1 mov byte [ebx], 44 ;; ASCII code for ',' mov byte [ebx+1], 32 ;; ASCII code for ' ' mov byte [ebx+2], 0 push dword string1 push dword user_string call strcat ;; At this point, we need to convert the Y coordinate to a string and append that to user_string. ;; Get the Y coordinate. mov eax, dword [ebp+20] ;; lParam has the coordinates. shr eax, 16 ;; The high-order word has the Y coordinate. ;; So EAX is now the Y coordinate of the mouse cursor. ;; Now we convert the Y coordinate of the mouse cursor to a string. push dword string1 push eax call i2str ;; And then we append this string to our user_string string. push dword string1 push dword user_string call strcat ;; Now all we have left to do is to append a ")" (ASCII code 41) to our user_string string. mov ebx, string1 mov byte [ebx+00], 41 mov byte [ebx+01], 0 push ebx push dword user_string call strcat ;; And we need to re-paint our window, now. push dword 1 push dword 0 push dword [ebp+8] call [InvalidateRect] jmp .window_finish ;[/COLOR]; And we define the .window_default label. .window_default: ;; Right now we don't care about what uMsg is; we just use the default ;; window procedure. ;; In order for use to call the DefWindowProc() function, we need to ;; pass the arguments to it. ;; It's arguments are the same as WindowProcedure()'s arguments. ;; We push the arguments to the stack, in backwards order. push dword [ebp+20] push dword [ebp+16] push dword [ebp+12] push dword [ebp+08] ;; And we call the DefWindowProc() function. call [DefWindowProcA] ;; At this point, we need to return. The return value must ;; be equal to whatever DefWindowProc() returned, so we ;; can't change EAX. ;; But we need to leave before we return. leave ;; Then, we can return. WindowProcedure() has 4 arguments, 4 bytes each, ;; so we free 4 * 4 = 16 bytes from the stack, after returning. ret 16 ;; Any code after the RET instruction will not be executed. ;; But we'll put code there anyway, just for consistency. jmp .window_finish ;; This is where the we want to jump to after doing everything we need to. .window_finish: ;; Unless we use the DefWindowProc() function, we need to return 0. xor eax, eax ;; XOR EAX, EAX is a way to clear EAX. ;; Same applies for any other register. ;; Then we need to leave. leave ;; And, as said earlier, we free 16 bytes, after returning. ret 16 ;; The functions we'll need to define are strlen, strcat, and i2str. ;; Those functions are defined in the following include files. %include "inc/istr.asm" %include "inc/str.asm" ;; We're about to define variables for the data section. section .data ;; We define the class name for our window class. ClassName db "SimpleWindowClass", 0 ;; Then we define the application name, for our window's title. ApplicationName db "Simple Text Example", 0 ;; The error message. err_msg db "An error occurred while making the new window. ", 0 ;; The text to draw. the_text db "Hello World! ", 13, 10, 13, 10, "Bye now. ", 0 ;; We're about to define variables for the bss section. section .bss ;; And we reserve a double-word for hInstance and a double-word for CommandLine. hInstance resd 1 CommandLine resd 1 ;; Now we reserve 4096 bytes for user_string. user_string resb 4096 ;; And we reserve 512 bytes for string1. string1 resb 512
In case you need it, here's "str.asm" :
strlen: enter 0, 0 ;; The stack frame. pusha ;; We push EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI (in that order). mov eax, dword [ebp+8] ;; Get the address of the string; it's the first argument. mov ebx, eax ;; b= address of the string xor ecx, ecx ;; c= 0 .lp1: ;; local label loop 1 ;; if byte [b] is 0 then go to loop 1 stop cmp byte [ebx], 0 jz .lp1s inc ecx ;; c= c + 1 inc ebx ;; b= b + 1 jmp .lp1 ;; go to loop 1 .lp1s: ;; return c mov eax, ecx mov dword [ebp-4], eax popa leave ret 4 strcpy: enter 0, 0 ;; We won't need any local variables. pusha ;; Save all the register values. mov eax, dword [ebp+08] mov ebx, eax ;; b= argument 1 mov eax, dword [ebp+12] mov edx, eax ;; d= argument 2 .lp1: mov al, byte [edx] ;; a= byte [d] (note: EDX (on 386 or later machines) can also be used for addressing) mov byte [ebx], al ;; byte [b]= a inc ebx ;; b= b + 1 inc edx ;; d= d + 1 cmp al, 0 ;; Subtract 0 from AL (don't store the result). jnz .lp1 ;; If the result is not supposed to be 0, go to loop 1. ;; ..... popa ;; Restore all the register values. leave ;; Switch back to the previous stack frame. ret 8 ;; Free 8 bytes after return. strcat: enter 0, 0 ;; No local memory variables. pusha ;; Save the values of all the registers. ;; a= argument1 + strlen argument1 = strlen argument1 + argument1, so... push dword [ebp+8] ;; strlen argument1 call strlen ;; ... + argument1 add eax, dword [ebp+8] ;; strcpy(a, argument2) push dword [ebp+12] ;; argument2 push eax ;; a call strcpy popa ;; Restore the values of all the registers. leave ;; Switch back to the previous stack frame. ret 8 ;; Free 8 bytes after returning. strcmp: enter 0, 0 ;; We don't really need any memory local variables, right now. pusha ;; Save the values of all the general-purpose registers. ;; b= argument 1 mov eax, dword [ebp+08] mov ebx, eax ;; d= argument 2 mov eax, dword [ebp+12] mov edx, eax ;; We'll use EAX for both integer c and character a. xor eax, eax .lp1: mov al, byte [edx] ;; Get byte [d] mov ah, byte [ebx] ;; Get byte [b] sub al, ah ;; See if they're the same. jnz .lp1s ;; If not, the strings are not equal for sure. cmp ah, 0 ;; They're the same; but are they 0? ;; Note that we can't use AL, here, because it'll always be 0 if the processor ;; makes it to this instruction, because of the SUB instruction, above. jz .lp1s ;; If so, the strings ended already, and we need to return. inc ebx ;; b= b + 1 inc edx ;; d= d + 1 jmp .lp1 .lp1s: cbw ;; This instruction converts a byte value to a word value. ;; This helps in situations where AL is a signed integer, because ;; if you just clear AH then AX will be 256 - |the integer| for negative numbers. ;; 00000010 is binary for 2 ;; 11111110 is binary for -2 ;; 11111101 is binary for -3 ;; 11111100 is binary for -4 ;; ... and so on ... ;; But if you include AH to make AX, then it would be (if AH is 0): ;; 00000000 00000010 is binary for 2 ;; 00000000 11111110 is binary for 254 ;; 00000000 11111101 is binary for 253 ;; ... and so on ... ;; So what CBW does is it takes the left-most bit of AL and ;; it sets every bit in AH to that value. ;; 00000000 00000010 is binary for 2 ;; 11111111 11111110 is binary for -2 ;; 11111111 11111101 is binary for -3 ;; ... and so on ... cwde ;; Then we have to convert the word (in AX) to a double-word; same idea as before. mov dword [ebp-4], eax ;; return c popa ;; Restore the register values. leave ;; Switch back to the previous stack frame. ret 8 ;; Free 8 bytes after return.
And, also in case you need that, here's "istr.asm" :
;; Now the i2str() function. i2str: ;; params: the_integer, the_string enter 4, 0 pusha ;; Initialize the beginning of the string to '-' mov eax, dword [ebp+12] mov ebx, eax mov byte [ebx], 45 ;; ASCII code for '-' ;; We initialize ESI to 0. xor eax, eax mov esi, eax ;; Check if the integer is negative. mov eax, dword [ebp+08] sub eax, 0 jnl .over1 inc esi ;; If the integer is negative, we would need to increment the number. neg eax ;; Also, we make sure the number we're dealing with is positive. .over1: mov dword [ebp-4], eax ;; We save the integer to [ebp-4] ;; Count the number of digits required for the string. mov eax, dword [ebp-4] ;; EAX= the integer xor ecx, ecx ;; ECX= 0 mov ebx, 10 ;; EBX= 10 .lp1: inc ecx ;; ECX= ECX + 1 xor edx, edx ;; EDX= 0 div ebx ;; EAX= EAX / EBX = EAX / 10 cmp eax, 0 ;; If not 0 yet, jnz .lp1 ;; continue loop. ;; ..... add ecx, esi ;; ESI will be 1 if the number's negative and 0 if the number is positive. ;; That way, this number (in ECX) would be incremented if the number is negative. ;; Then we get the pointer. mov eax, dword [ebp+12] add eax, ecx ;; We add the offset number, that we came up with earlier, to the pointer. mov ebx, eax ;; Now the pointer points to where the NULL terminator should be, in the future. ;; Set the byte at the memory address pointed to by the pointer to 0. mov byte [ebx], 0 ;; This is a loop. .lp2: dec ebx ;; We decrement the pointer. mov eax, dword [ebp-4] xor edx, edx mov ecx, 10 div ecx ;; Divide the integer by 10. xchg eax, edx ;; We'll change this back in a moment. ;; EAX is now the remainder. add eax, 48 mov byte [ebx], al ;; Set the byte pointed to by the pointer to the remainder + 48. xchg eax, edx ;; EAX is now the integer, again. mov dword [ebp-4], eax sub eax, 0 jnz .lp2 ;; If the integer is not 0, we continue with the loop. .lp2s: popa leave ret 8 ;; And the str2i() function. str2i: ;; params: the_string enter 0, 0 push dword 1 ;; Set dword [ebp-4] to 1. pusha ;; Let's count the number of minus (-) signs in the string. mov eax, dword [ebp+8] xor ecx, ecx mov ebx, eax .lp1: mov al, byte [ebx] cmp al, 0 jz .lp1s inc ebx inc ecx cmp al, 45 ;; 45 is the ASCII code for '-' (minus) jz .lp1 dec ecx cmp al, 48 ;; Also, leading '0' s don't count. jz .lp1 cmp al, 32 ;; 32 is the ASCII code for ' ' (space) jz .lp1 cmp al, 9 ;; 9 is the ASCII code for ' ' (tab-space) jz .lp1 dec ebx .lp1s: ;; See if the number's odd. mov eax, ecx ;; We're looking at the number of minus (-) signs. test eax, 1 jz .over1 mov dword [ebp-4], -1 ;; If odd, set dword [ebp-4] to -1. .over1: ;; EBX already points to the first non-space, non-tab-space, non-minus, and non-ASCII-48 character. ;; We initialize the integer to 0. xor eax, eax mov esi, eax ;; We'll use ESI for the integer. ;; This is a loop. .lp2: xor eax, eax ;; Clear EAX. mov al, byte [ebx] ;; Take the byte from the memory location pointed to by our pointer. ;; Break the loop if this value is not a valid number character. cmp al, 48 jl .lp2s cmp al, 57 jg .lp2s sub al, 48 ;; Subtract 48 from that. add esi, eax ;; Then add that number to our integer. ;; Multiply our integer by 10. mov eax, esi ;; Multiplying the integer. mov ecx, 10 ;; Multiplying by 10. mul ecx ;; And perform the operation. mov esi, eax ;; Then we save the result. inc ebx ;; Increment our pointer. jmp .lp2 ;; Continue loop. .lp2s: ;; This is the end of the loop. ;; We'll need to divide the number by 10, because our loop multiplies the number by 10 ;; one more than needed times. mov eax, esi xor edx, edx ;; Clear EDX. mov ecx, 10 ;; We're going to divide by 10. div ecx mov esi, eax ;; Now we just have to apply the sign to our integer. ;; Since [ebp-4] is -1 if the number's supposed to be negative, ;; and 1 if the number's supposed to be positive, it would ;; make sense to just multiply our integer by [ebp-4] to ;; get it to be with the right sign. ;; Get [ebp-4] and prepare for multiplication. mov eax, dword [ebp-4] mov ecx, eax ;; Get the integer. mov eax, esi ;; We need to use IMUL because [ebp-4] is a signed integer. imul ecx ;; Now we have EAX= the final result; we need to return that integer. mov dword [ebp-8], eax ;; Note that the number of bytes reserved for local variables is 4; ;; the return value (EAX) is at [ebp-(number of bytes reserved)-4], which is [ebp-(4)-4], which is [ebp-8]. popa leave ret 4
Mouse-Keyboard-Text - The Output
A window should appear. When you press keyboard keys, you should be able to type stuff. When you click anywhere inside the client area of the window, there should appear an XY coordinate pair that identifies the location of the mouse cursor when the left mouse button was released.
Here's how the output looked like when I tested it:
Intro To Win32 Assembly, Using NASM
File I/O and 'incbin'
Edited by RhetoricalRuvim, 20 August 2011 - 06:59 PM.