Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Assembly, Simple Text (Win32, NASM)

hello world assembly

  • Please log in to reply
No replies to this topic

#1 RhetoricalRuvim

RhetoricalRuvim

    JavaScript Programmer

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1311 posts
  • Location:C:\Countries\US
  • Programming Language:C, Java, C++, PHP, Python, JavaScript

Posted 14 August 2011 - 06:33 PM

Last time we made a simple window with nothing in it. This time, let's add some text to the window.

But before we get to the code, we have to go over a few things.

HDC - What Is A Device Context?
Windows uses device contexts for painting. To, let's say, draw a polygon, you need a device context; when you do draw a polygon, you're actually drawing to the device context. From there, things can be put to the screen.

DrawText() - The Text-Drawing Function
We're going to use DrawText() this time in our example program.

Here are the parameters for the DrawText() function:
hdc - A handle to the device context to draw to.
text - The text to draw; use "\r\n" for new lines.
count - The length of the text. If this parameter is -1, text is assumed to be a NULL-terminated string.
rectangle - A pointer to a RECT structure for where to draw the text.
format - Text format flags.

For more information about the DrawText() function, go to this page.

RECT - The Rectangle Structure
The RECT structure defines a rectangle. It tells the coordinates of the top-left and the bottom-right corners.

The Structure:
DWORD left - The X coordinate of the top-left corner.
DWORD top - The Y coordinate of the top-left corner.
DWORD right - The X coordinate of the bottom-right corner.
DWORD bottom - The Y coordinate of the bottom-right corner.

For more information about the RECT structure, go to this page.



Simple Text - The Idea
The idea is to make a window titled "Simple Text Example." Then, when Windows asks us to paint the window, we want to draw the text "Hello World! \r\n\r\nBye now. "


Simple Text - The Code
This program is based on our previous program that makes a window, but the draw text code is added, now.

The new parts of the code are colored orange, for excrescence.
;; 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 

[COLOR=#ff8c00]extern BeginPaint 
extern DrawTextA 
extern EndPaint [/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 

[COLOR=#ff8c00]import BeginPaint user32.dll 
import DrawTextA user32.dll 
import EndPaint 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: 
    [COLOR=#ff8c00];; 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] [/COLOR]
    enter [COLOR=#ff8c00]76[/COLOR], 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. 
    
    [COLOR=#ff8c00];; 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]
    
    ;; 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 
    [COLOR=#ff8c00];; 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. 
        
        ;; Now is the time to fill the RECT structure. 
        lea ebx, [ebp-76]            ;; EBX= address of the rectangle structure. 
        mov dword [ebx+00], 50       ;; top-left corner X= 50 
        mov dword [ebx+04], 20       ;; top-left corner Y= 20 
        mov dword [ebx+08], 450      ;; bottom-right corner X= 450 
        mov dword [ebx+12], 350      ;; bottom-right corner Y= 350 
        
        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. 
        push dword the_text      ;; The text we are drawing. 
        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]
    ;; 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 

;; 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 "[COLOR=#ff8c00]Simple Text Example[/COLOR]", 0 

;; The error message. 
err_msg                             db "An error occurred while making the new window. ", 0 

[COLOR=#ff8c00];; The text to draw. 
the_text                            db "Hello World! ", 13, 10, 13, 10, "Bye now. ", 0 [/COLOR]

;; 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 


Simple Text - The Output
The output should look something like this:
SimpleText_output_cc.PNG






First Tutorial:
Intro To Win32 Assembly, Using NASM

Previous Tutorial:
Simple Window

Next Tutorial:
Intro To Algorithms With String Functions




References:
DrawText Function (Windows): DrawText
RECT Structure (Windows): RECT

Edited by RhetoricalRuvim, 20 August 2011 - 06:55 PM.

  • 0





Also tagged with one or more of these keywords: hello world, assembly

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download