Toby Opferman http://www.opferman.net programming@opferman.net Win32 Assembly Intermediate Tutorial (TASM v5.0) "How to make a Win32 Program in Assembly" This program is for those who know assembly, know Win32 API and know how to declare and call functions and define variable types & structures for Win32. A good idea is to read my Intro to Win32 Assembly Tutor before reading this tutor. In this tutor, I will tell you how to set up your assembly program and even help you make a generic template so that it's very simple to create your Win32 Assembly programs with absurd ease! First thing you need it the processor. 386, 486 or better is a must. .486 or .486p I go with 486, but you can do what you want. The processor you define only limits you in what instructions you can use and there aren't that many new instructions on the 486, or even that many on a pentium. But, you can still define those processors but it doesn't really matter too much. The 386 instruction set defines the most instructions for pmode operations, the 486 and 586 just picked up a few tricks (And CPU cycles!). Next, you want to define your model. .MODEL FLAT, STDCALL You want to define FLAT mode! That is just wonderful because FLAT mode is Soooooo easy to use. Also define Standard Calls, that is just how windows will react with your functions, there's nothing to think about there, it really isn't nesscary to know why you declare it with STDCALL. I'm not so sure myself why you don't use PASCAL like in Win16 assembly, I haven't gone that far in depth but it doesn't really matter, you can get by! Now, you can include your WIN32.INC file! INCLUDE WIN32.INC That's it. That will include all your functions and structures. I will talk a little more about the defitions later, as since you define at lot but don't use them all, there's a little note about that later in this tutor. Then, just define your code and data. Here is what your file should look like so far: .486p .MODEL FLAT, STDCALL INCLUDE WIN32.INC .DATA ; Define your variablesHere .CODE START: ; This is where your code exeuction begins. ; You may also call this WinMain if you want. ; Put Code Here END START ; This goes at the very end of the Text File, exactly like a DOS program. That is a shell. We will fill it in now and we should come up with a template. For Data, all we need are: TitleName db 'First Windows Program', 0 ClassName db 'FirstWindow', 0 Msg MSGSTRUCT <?> ; The Message Struct WC WNDCLASS <?> ; The Windows Class hwnd dd ? ; Handle to Window hInstance dd ? ; Handle to Instance We begin our code with a call to GetModuleHandle. It returns a Module, which is the same thing as a HINSTANCE. PUSH L 0 CALL GetModuleHandle MOV [hInstance], EAX ; Get The Instance Now, we have our hInstance, and now we can register the class. CALL RegClass ; Register The Class We can make this a seperate procedure just to make our WinMain look cleaner. With this procedure, you can populate the Windows Class To What you want. RegClass PROC MOV EAX, [hInstance] MOV [WC.clsHInstance], EAX ; Populate the Windows Class Structure MOV [WC.clsStyle], 0 MOV [WC.clsLpfnWndProc], OFFSET WndProc ; Your Windows CallBack Function (Get Into This Later) MOV [WC.clsCbClsExtra], 0 MOV [WC.clsCbWndExtra], 0 PUSH L IDI_APPLICATION ; Load The Icon PUSH EAX CALL LoadIcon MOV [WC.clsHIcon], EAX MOV [WC.hIconSm], EAX PUSH L IDC_ARROW ; Load Cursor PUSH L 0 CALL LoadCursor MOV [WC.clsHCursor], EAX PUSH L BLACK_BRUSH CALL GetStockObject ; Black Background MOV [WC.clsHbrBackground], EAX MOV [WC.clsLpszMenuName], 0 MOV [WC.clsLpszClassName], OFFSET ClassName PUSH OFFSET WC CALL RegisterClass ; Register the Window RET ENDP Then, we must Create The Window. CALL CreateWind ; Create The Window We can make this also a seperate procedure just to clean up WinMain. CreateWind PROC PUSH L 0 PUSH [hInst] PUSH L 0 PUSH L 0 PUSH L CW_USEDEFAULT PUSH L CW_USEDEFAULT PUSH L CW_USEDEFAULT PUSH L CW_USEDEFAULT PUSH L WS_OVERLAPPEDWINDOW PUSH OFFSET TitleName PUSH OFFSET ClassName PUSH L 0 ; These are the parameters to CreateWindowEx, Just loaded ; BackWards. CALL CreateWindowEx ; Create The Window MOV [hwnd], EAX ; The Create Window Returns the Handle to the window. RET ENDP Now, we call ShowWindow and UpDate Window PUSH L SW_SHOW PUSH [hwnd] CALL ShowWindow PUSH [hwnd] CALL UpdateWindow This will create our window and display it on the screen. Last thing we need to do in the WinMain is the Windows Message Loop. This will poll for it's messages, when it finds a message for it's window, it will translate it into a code, then dispatch it to it's Window Message Handler function. In Pseudo Code: WAIT FOR MESSAGE: IF MESSAGE GET MESSAGE IF MESSAGE IS QUIT THEN EXIT TRANSLATE THE MESSAGE SEND MESSAGE TO WINDOW ROUTINE GO BACK TO WAIT FOR MESSAGE EXIT You have already seen the message loop in the beginner's tut, so I will just paste it here with some explianations for it. MessageLoop: PUSH L 0 PUSH L 0 PUSH L 0 PUSH OFFSET Msg CALL GetMessage ; Call To GetMessage TEST EAX, EAX ; Quit Loop? JZ SHORT EndProgram PUSH OFFSET Msg CALL TranslateMessage ; Translate Message PUSH OFFSET Msg CALL DispatchMessage ; Dispatch Message JMP SHORT MessageLoop EndProgram: PUSH [Msg.wParam] CALL ExitProcess ; End Program The above will do exactly what the Pseudo code says. Gets the windows message, translates it and sends it out. For most windows programs, the above will not change. There is a variation to the loop that is more advanced and there are sometimes things you want to do between translateing message and dispatching or before. But, those are not in general applications. Many applications are just fine with the above and changing the above is not likely in the interest of a novice windows programmer. When the time comes that you may need to change it for a speical case program, you would most likely be in expert level and already know exactly what to do and how to do it. So, this is what your program should look like so far: .486p .MODEL FLAT, STDCALL INCLUDE WIN32.INC .DATA Msg MSGSTRUCT <?> ; The Message Struct WC WNDCLASS <?> ; The Windows Class hwnd dd ? ; Handle to Window hInstance dd ? ; Handle to Instance .CODE START: ; This is where your code exeuction begins. PUSH L 0 CALL GetModule MOV [hInstance], EAX ; Get The Instance CALL RegClass ; Register The Class CALL CreateWind ; Create The Window PUSH L SW_SHOWNORMAL PUSH [hwnd] CALL ShowWindow ; Show Window PUSH [hwnd] CALL UpdateWindow ; Update The Window MessageLoop: PUSH L 0 PUSH L 0 PUSH L 0 PUSH OFFSET Msg CALL GetMessage ; Call To GetMessage TEST EAX, EAX ; Quit Loop? JZ SHORT EndProgram PUSH OFFSET Msg CALL TranslateMessage ; Translate Message PUSH OFFSET Msg CALL DispatchMessage ; Dispatch Message JMP SHORT MessageLoop EndProgram: PUSH [Msg.wParam] CALL ExitProcess ; End Program ; END OF WinMain. ; Your Proc's Go Here (I left them out for space reasons, so you can view the top part in peace. END START ; End of File. ExitProcess will terminate the program and it will not return. Next, we will add error checking to the WinMain. CALL RegClass ; Register The Class CALL CreateWind ; Create The Window These two calls can return an error if the window does not register. All you have to do is check EAX. If EAX is 0, then call ExitProcess with a 0 in the parameter. CALL RegClass ; Register The Class TEST EAX, EAX ; Check Window Register Error JNZ SHORT NO_ERROR PUSH L 0 CALL ExitProcess ; Exit NO_ERROR: CALL CreateWind ; Create The Window TEST EAX, EAX ; Check Window Creation Error JNZ SHORT WINDOW_CREATED PUSH L 0 CALL ExitProcess ; Exit WINDOW_CREATED: Now, in C you may have seen: return FALSE; and not return 0; Well, do this in your WIN32.INC FALSE equ <0> TRUE equ <1> Then, in your programs, just do PUSH L FALSE or PUSH L TRUE for those parameters. Yes, defining 0 to mean a bunch of differnt things (i.e. NULL, FALSE, etc) can be redundent, but it does not add any code to your program. It will only add tokens to your assembler and will not effect your assembled code. It just just more readable to see NULL's and FALSE's. Where you can tell a NULL you can replace with a pointer and a FALSE you can replace with a value or TRUE. Now, you basically have your template. You can change the RegClass proc and CreateWind proc to customize the window. The rest of it can basically stay in place. ** As a side note, you may see something like a call to FindWindow in the Tasm examples. The logic there is just to make sure that if there is another instance of the program, it will put a space in it's own name to make it unique. You can do that if you want, there is no _MAJOR_ need to. You may also change the code to instead of putting a space in, to exit if it is already in memory, to allow only one of your programs to run at a time. Force them not to be able to have multiple sessions of your program loaded. Depending on the type of program you have, this may be a good idea. For the most part, you can really ignore that though. Your general applications probably don't matter, especially for a beginner. The programs you write probably won't be as complex as to want to force 1 instance of itself for device sharing reasons, or memory reasons. Now, we will define our windows call back function. This is the function that windows calls when it wants to tell our window to do something, or that the user wants to do something to the window. An event has occured pertaining to that window. WndProc PROC USES EBX EDI ESI, hwnd:DWORD, wmsg:DWORD, wparam:DWORD, lparam:DWORD ; Code Here ENDP Once you define the above, you are ready to insert your code. You would want to make sure any WM_messages are defined in your WIN32.INC For intermediate level, I would hope you can figure out the rest. It's just plain old windows code, if you've programmed windows in C, you should have no problem. Here is a quick look: MOV EAX, [Msg] CMP EAX, WM_CREATE ; Check Messages JE W_CREATE PUSH [lParam] PUSH [wParam] PUSH [Msg] PUSH [hwnd] CALL DefWindowProc ; Default RET W_CREATE: ; WM_CREATE Message ; Code XOR EAX, EAX RET Now you should be ready to move onto advanced Win32 Assembly Tutor. You should know how to: 1. Create a WinMain with all your window defines 2. Create the outline of a Call back Function. 3. Have your own template for Win32. In Advanced Win32 we will go into detail on WndProc. We will also learn how to compile this program! And we will also cover resources breifly and how to include them in your programs!