Tutorial 3: a Simple Window
Theory: Windows programs rely heavily on API functions for their GUI This approach benefits both users and programmers For users, they do not have to learn how to navigate the GUI of each new programs, the GUI of Windows programs are alike For... programmers, 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. Butt can be overcome by Modular Programming OR OOP Paradigm.
I'll Outline The Steps Required to Create A Window on The Desktop Below:
Get the instance handle of your program (required) Get the command line (not required unless your program wants to process a command line) Register window class (required, unless you use predefined window types, eg. MessageBox or a dialog box) Create the window (required) Show the window on the desktop (required unless you do not want to show the window immediately) Refresh the client area of the window Enter an infinite loop, checking for messages from Windows If messages arrive, they are processed by a specialized function that is responsible for the window 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 Aable To Coexist Peacefully with each other. The Must Follow Stricter Rules. You, AS A Programmer, Must Also Be More Strictly 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 do not 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
Option CaseMAP: NONE
INCLUDE /MASM32/INCLUDE/Windows.inc
INCLUDE /MASM32/INCLUDE/USER32.INC
Includelib /masm32/lib/User32.lib; calls to functions in user32.lib and kernel32.libinclude /masm32/include/kernel32.inc
INCLUDELIB /MASM32/LIB/kernel32.lib
Winmain Proto: DWORD,: DWORD,: DWORD,: DWORD
.Data; Initialized Data ClassName DB "SimpleWinclass", 0; The name of our window Class Appname DB "Our First Window", 0; The Name of Our Window
??? .DATA; Uninitialized data hInstance HINSTANCE; Instance handle of our program CommandLine LPSTR .CODE; Here begins our code start: invoke GetModuleHandle, NULL; get the instance handle of our program; Under Win32, hmodule == hinstance mov hInstance. , eax mov hInstance, eax invoke GetCommandLine;. get the command line You do not have to call this function IF; your program does not process the command line mov CommandLine, eax invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT.; Call the main function invoke EXITPROCESS, EAX; Quit Our Program. The exit code is returned in Eax from WinMain.
Winmain Proc Hinst: Hinstance, Hprevinst: Hinstance, Cmdline: lpstr, cmdshow: dword local wc: wndclassex; Create Local Variables On Stack Local MSG: MSG local hwnd: hwnd
mov wc.cbSize, SIZEOF WNDCLASSEX; fill values in members of wc mov wc.style, CS_HREDRAW or CS_VREDRAW 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, NULL mov wc.lpszClassName, OFFSET ClassName invoke 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; register our window class 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, CmdShow; display our window on desktop invoke UpdateWindow, hwnd; refresh the client area.WHILE TRUE; Enter message loop invoke GetMessage, ADDR msg, NULL, 0,0 .BREAK .IF (eax!) invoke TranslateMessage, Addr Msg Invoke DispatchMess Beach
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 Processing Ret .endif Xor Eax, Eax Ret WndProc Endped 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, stdcall
Option CaseMAP: NONE
Winmain Proto: DWORD,: DWORD,: DWORD,: DWORD
Include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib incrudelib /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 Aable 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. HUTCH AND I is Working on It. You can add in new items if..
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 Simple: Just Link To One and Only One File: import32.lib.
.DATA
ClassName DB "SimpleWinClass", 0
Appname DB "Our First Window", 0
.DATA? Hinstance Hinstance? Commandline LPSTR?
Next Are the "data" section.
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 annot to reserve the space forfuture us.
.Code
Start:
Invoke getModuleHandle, NULL
Mov Hinstance, EAX
Invoke getcommandline
Mov Commandline, EAX
Invoke Winmain, Hinstance, Null, CommandLine, SW_SHOWDEFAULT
Invoke EXITPROCESS, EAX
.....
End Start
.CODE contains all your instructions Your codes must reside between
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 outprie.
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 CONSIDED Scratch Registers and Area A Win32 function.
Note: Don't Expect The VALUES OF EAX, ECX, Edx To Be Preserve 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 include 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, THIS Instance is the first one.
Note: You do not have to declare the function name as WinMain In fact, you have complete freedom in this regard You do not 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.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 convern Ourslves with stack frame on function enter and exit.
Local WC: WNDCLASSEX LOCAL MSG: MSG local 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 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 effect. The point is: if You Define Your Own Window, You Must Fill The Desired Characteristics of YO ur 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 do not 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 spend most of your time writing event handlers in window procedure I describe each member of WNDCLASSEX below:.??? WNDCLASSEX STRUCT DWORD cbSize 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: HICON: HICON: HANDLE to THE ICON. HCURSOR: HANDLE TO The CURSOR. Get it from loadcursor call. HbRBackground: Backgrou nd 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 .
invoke CreateWindowEx, NULL, / ADDR ClassName, / ADDR AppName, / WS_OVERLAPPEDWINDOW, / CW_USEDEFAULT, / CW_USEDEFAULT, / CW_USEDEFAULT, / CW_USEDEFAULT, / NULL, / NULL, / hInst, / NULLAfter registering the window class, we can call CreateWindowEx to create Our Window Based on The Submitted Window Class. NOTICE THERE Are 12 parameters to this function.
CreateWindowexa Proto DWEXStyle: DWORD, / LPCLASSNAME: DWORD, / LPWINDOWNAME: DWORD, / DWSTYLE: DWORD, / NWORD, / Y: DWORD, / NWIDTH: DWORD, / NHEIGHT: DWORD, / HWNDPART: 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 do not 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 H ave 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 a . Utomatically 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, eax invoke ShowWindow, hwnd, CmdShow invoke 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 True Invoke GetMessage, Addr MSG, NULL, 0, 0.Break .if (! EAX) Invoke TranslateMessage, Addr Msg Invoke DispatchMessage, Addr Msg .Indw
At this time, our window is up on the screen. But it can not 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 w ith 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 does not process keystrokes. DispatchMessage sends the message data to the window procedure responsible for the Specific window the message is for.mov Eax, Msg.wParam Ret 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 sad side and plays by the rule.
WNDPROC PROC HWND: HWND, UMSG: UINT, WPARAM: WPARAM, LPARAM: LPARAM
This is our window procedure. You do not 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_DESTROY INVOKE POSTQUITMESSAGE, NULL .ELSE INVOKE DEFWINDOWPROC, HWND, UMSG, WPARAM, LPARAM RET .Endif xor EAX, EAX RET WNDPROC ENDP