Sure glad I can type, this will be a bit long...
Back in the day, to do anything like write to the screen we would have to use Interrupts to tell the CPU and DOS what to do.
I am sure you have seen Assembly code that looked like this:
mov ah, 8h int 21h add al, 3h mov dl, al mov ah, 2h int 21h
and gotten scared off. All that does is get a nuber that typed in the console, add 3 to it and prints the result back to the console. Nowadays, this code will not run on Windows 7 and anything above without an emulator like DOSBox.
Luckily, Win32 Assembly is not that bad, and it is even a bit easier if you have used the Windows API in your programs. You CAN still write console apps, but they are not DOS apps and do not use Interrupts instead use the Windows API.
One Interrupt you CAN use and will use a lot is - int 3 this will call up your debugger and display your code at that point. In the 1st Getting Started tut is a link for OllyDbg, it is a good and powerful debugger.
There are 2 main ways to create windows and controls - with CreateWindowEx or using a resource editor and create dialogs. To get an app designed quickly, nothing beats a resource editor. I prefer CreateWindowEx, though sometimes I will
create the windows and controls in a resource editor and convert that to CreateWindowEx code (thanks to a Dialog to Window converter in the IDE I use. This tutorial I will use CreateWindowEx, next one I will do the same with Dialogs.
Few things before we start:
In Assembly, nothing is holding your hand like a runtime or compiler that checks your buffers, preserves the registers etc, so you have to be careful to write and read the correct addresses. Another IMPORTANT item is Register Preservation.
What the hell is that? The general purpose registers are eax, ebx, ecx, edx, esi, edi, (also ebp, and esp but these are special) Register preservation just means that when you call a Windows API, you are guaranteed
whatever value is in the registers ebx, esi, and edi before the call to an API, that same value will be there after the call. eax, ecx, and edx are not guaranteed to be preserved. Confused?
Lets say you do:
mov eax, 3 invoke MessageBox, NULL, NULL, NULL, MB_OK
and are expecting eax to still contain 3 after calling MessageBox, you are wrong. Especially with eax, the return values of most functions are returned in eax.
If you wanted eax to stay the same, you would push it onto the stack (another tutorial subject to come) and pop it off the stack after the call
mov eax, 3 push eax invoke MessageBox, NULL, NULL, NULL, MB_OK ; check return value pop eax
Or just use a register that has to get preserved like esi
mov esi, 3 invoke MessageBox, NULL, NULL, NULL, MB_OK ; check return value ; esi still contains 3
Register preservation is IMPORTANT in windows callback procs, if you use esi, edi or ebx in a callback and forget to save it, your app will bomb at some point. Luckily, MASM makes it easy to save regs with the word USES.
If you are writing a proc that does not interact with anybody else then you don't have to preserve these regs, BUT if you are writing code that some other developer is going to call, then the standard is to follow the
Register Preservation rules.
This tut, we will create a window with a button. That button when clicked, will grab a copy of your screen and "slide" it to the right (didn't know what else to do with the button, I DID NOT want the button to say hello)
.386 .model flat, stdcall option casemap:none include windows.inc include masm32.inc include kernel32.inc include user32.inc include comctl32.inc include gdi32.inc include \masm32\macros\macros.asm includelib masm32.lib includelib kernel32.lib includelib user32.lib includelib comctl32.lib includelib gdi32.lib
Windows.inc has a bunch of equates and structures that we need. Masm32.inc has protos for functions we will call. The Windows API incs, contain protos and equates for calls.
For instance, we use CreateWindowEx but there is no such API CreateWindowEx it is known as CreateWindowExA or CreateWindowExW (for the Unicode version) So, in user32.inc if you open it, you will see
CreateWindowEx equ <CreateWindowExA>
If you wanted to, you could do MakeMeAWindow equ <CreateWindowExA> and use MakeMeAWindow instead of CreateWindowEx.
The SDK/MSDN will tell you where the API you need is located, then you just include the inc and lib for it.
next are the includelib. True, lib files can contain code that can be "statically" linked into your program, but in the API case the lib files gives the linker hints as to where the functions are at runtime.
SetControlFonts PROTO :DWORD,:DWORD BTN_DONT_CLICK_ME equ 1001
For any procedure you call with "invoke", you need a prototype to tell ML how many parameters there are and the data type so it can do some checks for you. It makes sure in our case SetControlFonts has 2 parameters, no more and no less
or it will throw an error.
The BTN_DONT_CLICK_ME equate is what I will use in our message handler to see if the button was clicked
.data szFontName BYTE "Verdana", 0 szWndClsMain BYTE "DIC_FIRST_ASM_APP", 0 szWndClsButton BYTE "BUTTON", 0 szDontClickMe BYTE "Don't Click Me :-)", 0
Ooooh, our INITIALIZED data section. This is where we put all of the strings for the app, any initialized structures, arrays, numbers all go here. The string can be enclosed between either ' or " and is NULL terminated (0)
if you forget to NULL terminated your strings, windows will throw a hissy fit at random times in you app and you will go crazy as to why. So don't forget that 0 at the end of your strings.
.data? hInst DWORD ? hHeap DWORD ? hFont DWORD ? hMain DWORD ? swid DWORD ? shgt DWORD ? hDC DWORD ? cDC DWORD ? dwid DWORD ? hScr DWORD ? hBmp DWORD ? hOld DWORD ? msg MSG <?>
This is where we put anything we don't need initialized (when your app starts, they are "zeroed" out), handles to controls, windows, fonts, things we need for the life of the app. They are "Global"
.code ; this is were all our code will go FirstWindow: ; this is our entry point. ; Initialize common controls xor edi,edi push edi push sizeof INITCOMMONCONTROLSEX invoke InitCommonControlsEx, esp comment # The return value from Windows API calls get returned in the eax register # invoke GetModuleHandle, NULL ; With Intel Assembly syntax it is mov destination, source mov hInst, eax ; Get the handle to our heap invoke GetProcessHeap mov hHeap, eax ; Create a font ; We could of easily used a LOGFONT structure in the .data? section ; But why use a global when we don't have to.. ; First we will alloc some memory for our LOGFONT stucture ; the handle to our heap is still in eax, so we use it invoke HeapAlloc, eax, HEAP_ZERO_MEMORY, sizeof LOGFONT ; esi is preserved across API calls so we will save the pointer there ; http://en.wikipedia.org/wiki/X86_calling_conventions mov esi, eax ; Next we will fill the LOGFONT structure with some info mov (LOGFONT PTR [esi]).lfHeight, -11 mov (LOGFONT PTR [esi]).lfWeight, FW_NORMAL lea edi, (LOGFONT PTR [esi]).lfFaceName ; szCatStr is a function in the MASM32 library that appends the second string to the end of the first. invoke szCatStr, edi, offset szFontName ; now we pass the pointer to the LOGFONT structure to CreateFontIndirect invoke CreateFontIndirect, esi ; save the handle to the font mov hFont, eax ; Free the pointer to our LOGFONT structure invoke HeapFree, hHeap, 0, esi ; Register our window class ; I personally don't want to use a global var just for this one WNDCLASSEX Structure ; so I will alloc from our heap invoke HeapAlloc, hHeap, HEAP_ZERO_MEMORY, sizeof WNDCLASSEX mov esi, eax mov (WNDCLASSEX PTR [esi]).cbSize, sizeof WNDCLASSEX mov (WNDCLASSEX PTR [esi]).style, CS_HREDRAW or CS_VREDRAW mov eax, hInst mov (WNDCLASSEX PTR [esi]).hInstance, eax ; The above 2 lines is 1 byte smaller than the following 2 lines ; push hInst ; pop (WNDCLASSEX PTR [esi]).hInstance mov (WNDCLASSEX PTR [esi]).lpfnWndProc, offset WndProcMain mov (WNDCLASSEX PTR [esi]).hbrBackground, COLOR_3DFACE + 1 mov (WNDCLASSEX PTR [esi]).lpszClassName, offset szWndClsMain xor eax, eax mov (WNDCLASSEX PTR [esi]).hIcon, eax mov (WNDCLASSEX PTR [esi]).hIconSm, eax ; The above 3 lines are 8 bytes VS: ; The following 2 lines are 14 bytes ; mov (WNDCLASSEX PTR [esi]).hIcon, 0 ; mov (WNDCLASSEX PTR [esi]).hIconSm, 0 invoke LoadCursor, NULL, IDC_ARROW mov (WNDCLASSEX PTR [esi]).hCursor, eax invoke RegisterClassEx, esi invoke HeapFree, hHeap, 0, esi ; Are you ready? Lets create and display our window! ; First we will get the coords to center our window invoke GetSystemMetrics, SM_CXSCREEN ; screen width ; Here we subtract our app window width from the screen width sub eax, 755 ; instead of dividing by 2, we shift eax right by 1 shr eax, 1 ; save the value to ebx xchg eax, ebx invoke GetSystemMetrics, SM_CYSCREEN ; screen height ; subtract our window height from screen height and "divide by 2" sub eax, 485 shr eax, 1 ; ##### Create main window ; we don't need ShowWindow or UpdateWindow since we use the WS_VISIBLE style ; ALSO, we don't "add" styles togeter, we "OR" them together invoke CreateWindowEx, WS_EX_APPWINDOW or WS_EX_CONTROLPARENT, \ offset szWndClsMain, \ NULL , \ WS_OVERLAPPED or WS_SYSMENU or WS_MINIMIZEBOX or WS_CLIPCHILDREN or WS_VISIBLE, \ ebx, eax, \ 755, 485, \ HWND_DESKTOP, NULL, \ hInst, NULL mov hMain, eax ; the infamous "Message Loop" ; IsDialogMessage is in here so we can nav with tab key ; This will dispatch all messages to the correct handler .while TRUE invoke GetMessage, ADDR msg, NULL, 0, 0 .break .if (!eax) invoke IsDialogMessage, hMain, ADDR msg .if eax == FALSE invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endif .endw ; We made it here, so user closed app invoke ExitProcess, msg.messageYou don't have to use .if/.else/.elseif/.endif if you don't want to, you could use labels, cmps and jmps. For beginners, I strongly recommend .if/.endif ML won't create bad code.
This is our Window Procedure (Windows Callback) that will handle all messages sent to our window. We have 1 window so one proc, for a bigger program with multiple windows or subclasses, you will have many more.
WndProcMain proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM ; it is faster to compare a register vs memory, so we will save the message to eax and compare that. mov eax, uMsg .if eax == WM_CREATE ; when we called CreateWindowEx, it sent the message WM_CREATE to our window, this is where you create any controls, set fonts, any initialization. ;##### Our little button invoke CreateWindowEx, NULL, offset szWndClsButton, offset szDontClickMe, BS_CENTER or BS_TEXT or BS_VCENTER or\ WS_VISIBLE or WS_CHILD or WS_TABSTOP, 317, 232, 120,20, hWin, BTN_DONT_CLICK_ME, hInst, NULL ; easy way to set the fonts for all controls in our window ; we will use the callback EnumChildWindows to set all control fonts to the font we created earlier. ; When you have many controls, this will enumerate all controls and pass the handle to our Proc SetControlFonts ; where we set the fonts. invoke EnumChildWindows, hWin, addr SetControlFonts, NULL ; If you need to have a control in another font like bold or underline ; this is where you set those fonts AFTER the call to EnumChildWindow (or your control won't get your new font) with SendMessage and WM_SETFONT ; Many messages fall under WM_COMMAND, we only want BN_CLICKED .elseif eax == WM_COMMAND ; The high word of wParam is the notification code. ; move wParam into edx register mov edx, wParam ; the control ID is in the Low Word of wParam (edx) the low word of edx is dx ; zero extend dx and move to eax movzx eax, dx ; the notification code is in the high word of edx, so we shift edx right by 16 to grab it. shr edx, 16 .if edx == BN_CLICKED .if eax == BTN_DONT_CLICK_ME ; oooh, someone clicked our button, call our proc to move the screen call Magic .endif .endif .elseif eax == WM_CLOSE ; our app is closing, call our proc to clean up any memory, handles, objects the destroy the window call CleanUp invoke DestroyWindow, hWin .elseif eax == WM_DESTROY invoke PostQuitMessage, NULL .else ; this is for any messages we are not handling, pass them on to the default proc for the window/control invoke DefWindowProc, hWin, uMsg, wParam, lParam ret .endif ; for the messages we process, we have to return 0 xor eax,eax ret WndProcMain endp CleanUp proc ; in our case, we only have 1 handle to clean up ; in a larger app, we would clean up a lot more here. invoke DeleteObject, hFont ret CleanUp endp
this is the proc the callback EnumChildWindow will call with the handle of the enumerated controls
we set the fonts here.
SetControlFonts proc hWin:DWORD, lParam:DWORD invoke SendMessage, hWin, WM_SETFONT, hFont, FALSE mov eax, TRUE ret SetControlFonts endp
this proc is called when someone clicks our button
just makes a copy of the screen and slides it to the right.
it uses a macro from the MASM32 package rv (return value) to call the proc, and get the return value on one line.
Magic proc mov hScr, 0 ; screen handle is zero mov hDC, rv(GetDC,hScr) ; get its DC mov swid, rv(GetSystemMetrics,SM_CXSCREEN) ; get screen width add eax, eax ; double it mov dwid, eax ; store it in a variable mov shgt, rv(GetSystemMetrics,SM_CYSCREEN) ; get the screen height mov hBmp, rv(CreateCompatibleBitmap,hDC,dwid,shgt) ; make double width bitmap mov cDC, rv(CreateCompatibleDC,hDC) ; create a DC for it mov hOld, rv(SelectObject,cDC,hBmp) ; select compatible bitmap into compatible DC ; ------------------------------------------------ ; blit 2 copies of the current screen side by side ; ------------------------------------------------ invoke BitBlt,cDC,0,0,swid,shgt,hDC,0,0,SRCCOPY invoke BitBlt,cDC,swid,0,swid,shgt,hDC,0,0,SRCCOPY ; -------------------------------------------------------- ; repeatedly blit the shifting image to the current screen ; -------------------------------------------------------- push esi mov esi, swid @@: invoke BitBlt,hDC,0,0,swid,shgt,cDC,esi,0,SRCCOPY ;invoke Sleep, 25 ; slow it up a bit sub esi, 8 jns @B pop esi invoke SendMessage,0,WM_PAINT,hDC,0 ; clean up the mess after invoke DeleteObject,hBmp ; delete the compatible bitmap invoke SelectObject,cDC,hOld ; reselect the old one invoke DeleteDC,cDC ; delete the compatible DC invoke ReleaseDC,hScr,hDC ; release the screen DC ret Magic endp
the end of our code / entry point
Next up, Our first window using a dialog instead of CreateWindowEx
This app: http://www.gunnerinc...iles/shrest.zip includes source using jmps/cmps/calls and no invoke/.if/.endif if you want to see an alternate way of coding in MASM
Number of downloads: 903