[ Nomad Programming, Graphics, and Algorithms Tutorial ] [ Compiler : MS Visual C++ 6.0 ] [ Part 6 - Win32 API Basics ] [ http://nomad.openglforums.com ] <<------------------------------------------------------>> [ DISCLAIMER ] I will hold no responsibility to whatever happens to you, your computer, your pet, your sanity, or whatever that may happen to your existence for your reading these texts and for the outcome of the various source code(s) given in each tutorial. So in short, read at your own risk! [ INTRODUCTION ] Let's face it, DOS is gone. And so, we must go with the flow and/or trend many people are following, that is, MS Windows Programming! If you feel like you've been cheated, going through parts 1 to 5, just to abandon everything...well, no, you don't abandon everything. And hey, not many people really know those old stuffs nowadays. They make you more...'learned'...;). SideNote: What I will discuss to you here is but a small part of Windows programming. I will deal with the Win32 API directly, rather than using the popular MFC. The knowledge here will be enough for us to be comfortable in a windowing environment...which OpenGL requires...;). [ TUTORIAL ] So I won't confuse you guys, I'll be using the words "MS Windows" when I'm referring to the OS, and "window(s)" when I'm referring to a...well, window!...:). Well, every program under MS Windows is created by a group of windows. A window could be those boxes you see with a minimize-maximize-close button on the upper right corner, or it could be a dialog box, radio box, or a check box too (among others). I'll discuss how to create one of those window with a min-max-close button on the upper right corner. Well, every window has a "class" (NOT the "class" that you know from object-oriented programming). This "class" is actually a structure that you fill information and register with. This structure has the following fields: //----8<----[ CODE BEGIN ]--------// typedef struct { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS; //----8<----[ CODE END ]--------// This structure can be found in the WINUSER.H file. Note that you'll actually see two structures named WNDCLASSA and WNDCLASSW in the file rather than a WNDCLASS structure. The WNDCLASS structure is merely "unifying" two structures...if you're using Unicode, you're actually using WNDCLASSW, else, you're using the ASCII (or WNDCLASSA)...but don't worry about that...just use WNDCLASS to simplify your life! Now in the structure you might see some new data types. And you will also see the odd prefix they add to each of the structure's fields (i.e. the "h" in "hCursor", or the "lpsz" in the "lpszMenuName"). This is because they use the Hungarian Notation. Briefly, Hungarian Notations are really a convention you use to know what data type each variable / field is. The following is a list of prefixes and their data type: //----8<----[ LIST BEGIN ]--------// PREFIX DATA TYPE c char or WCHAR or TCHAR by BYTE (unsigned char) n short i int x, y int used as x or y coordinate cx, cy int used as x or y length; 'c' stands for 'count' b or f BOOL (int); 'f' stands for 'flag' w WORD (unsigned short) l LONG (long) dw DWORD (unsigned long) fn function s string sz string terminated by 0 character h handle p pointer //----8<----[ LIST END ]--------// The list was taken from the book "Programming Windows, Fifth Edition" by Charles Petzold. So going back to our structure, the field named lpfnWndProc might be read as a "long pointer to a function that will be the window's WndProc". I'll discuss about WndProc (read as "win prock") later. Describing each field: style // describe the window through flags lpfnWndProc // pointer to windows' WndProc cbClsExtra // disregard this for now cbWndExtra // disregard this for now hInstance // handle to the instance of the class hIcon // handle to the icon of the application hCursor // handle to the (mouse) cursor hbrBackground // handle to the background "brush" lpszMenuName // disregard this for now lpszClassName // name used to refer to this class If you're wonderin' what a handle or flag is right now, well, it'll be clear to you later. Now that we have a structure, we fill it with values. You'd want to fill it inside the WinMain() function. WinMain() is MS Windows' equivalent to the main() function most of you probably are so used to...:). Here's how you might do it: //----8<----[ CODE BEGIN ]--------// int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wndclass; // remember this? wndclass.style = CS_HREDRAW | CS_VREDRAW; // window's style wndclass.lpfnWndProc = (WNDPROC)WndProc; // pointer to WndProc wndclass.cbClsExtra = 0; // disregard... wndclass.cbWndExtra = 0; // disregard... wndclass.hInstance = hInstance; // copy from WinMain() wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // window icon // IDI_ASTERISK // IDI_EXCLAMATION // IDI_HAND // IDI_QUESTION // IDI_WINLOGO wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // window's mouse cursor // IDC_CROSS // a...'cross'...:) // IDC_NO // slashed circle // IDC_WAIT // hourglass // IDC_HELP // arrow with question mark // IDC_UPARROW // arrow pointing up wndclass.hbrBackground = NULL; // handle to bground brush wndclass.lpszMenuName = NULL; // window's menu name wndclass.lpszClassName = "MyWindow"; // window's name } //----8<----[ CODE END ]--------// A bit long huh? If you're starting to be discouraged by now because this is waaaaayyyy too much information to store in one's head, well, guess what? Nobody really remembers each field to each structure. You'd want to just copy and paste the code then replace the value to each field...:). You might notice from the line: wndclass.style = CS_HREDRAW | CS_VREDRAW; Two thingi-ma-dingies being OR'ed with each other. They are the flags! You combine two or more flags by OR'ing them (I mean the bitwise-OR...NOT the logical one...:) ). CS_HREDRAW means that the window will be redrawn when it is resized horizontally. While CS_VREDRAW...uhmm, you SHOULD get the flow...:). Now in the lines: wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // IDI_ASTERISK // IDI_EXCLAMATION // IDI_HAND // IDI_QUESTION // IDI_WINLOGO wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // IDC_CROSS // IDC_NO // IDC_WAIT // IDC_HELP // IDC_UPARROW You "load" to wndclass.hIcon with what it needs...a "handle" to an icon! Think of handles as a pointer that points to a specific thing only...in this case, an icon...:). The first parameter to LoadIcon()/LoadCursor() was set to NULL to use MS Window's standard icons. (Just to let you know, the first parameter accepts a value of type HINSTANCE. This "HINSTANCE" is read as a "Handle to an Instance". What you place in the first parameter should be the handle to the program/module where a desired icon/cursor is stored...:) ) The code above also defines the handle of our mouse cursor to be an arrow (IDC_ARROW). If you want to change it, just choose one from the commented IDC_xxx flags below LoadCursor(). The same goes for the LoadIcon() too...:). After giving values to our WNDCLASS structure, we register our class to make MS Windows know what our window looks like. We do it like this: if(RegisterClass(&wndclass) == 0) return FALSE; Obviously, if there was an error in registering our class, we quit...:). But if we are successful in registering our class, the next step we do is to actually CREATE the window!!! Let's recap first the steps so far: o We create a variable of type WNDCLASS o We put values to each field of our WNDCLASS variable o We register our WNDCLASS variable See? That's just three steps! Rather long, yes, but just three steps! Think big and these stuff will look small...:). The up and coming step is: o Create a window using CreateWindow() CreateWindow() has some parameters that you might want to know about. They are listed below: //----8<----[ CODE BEGIN ]--------// HWND CreateWindow( LPCTSTR lpClassName, // copy from wndclass.lpszClassName LPCTSTR lpWindowName, // caption to appear on title bar DWORD dwStyle, // describe the window through flags int x, // x coordinate (upper-left) of window int y, // y coordinate (upper-left) of window int nWidth, // width of the window int nHeight, // height of the window HWND hWndParent, // disregard for now... HMENU hMenu, // disregard for now... HANDLE hInstance, // from WinMain()...again... LPVOID lpParam // disregard for now... ); //----8<----[ CODE END ]--------// Note that it returns a type HWND. This is the handle to our window! So we must create another variable of type HWND. So far our code would look something like the one below: //----8<----[ CODE BEGIN ]--------// int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wndclass; HWND hWindow; // Initialize values of wndclass' fields // ... // Code omitted for brevity // ... // Register wndclass if(RegisterClass(&wndclass) == 0) return FALSE; // Create our window and get its handle hWindow = CreateWindow ("MyWindow", // from wndclass.lpszClassName "My Title Bar", // this will show on title bar WS_OVERLAPPEDWINDOW, // border, title, min-max-close button // WS_OVERLAPPED // border, title // WS_POPUP // no title, no button // WS_VISIBLE // window is visible from start // WS_VSCROLL // window has vertical scroll bar // WS_HSCROLL // window has horizontal scroll bar // WS_MAXIMIZE // window is created in maximized state // WS_MINIMIZE // window is created in minimized state 0, // starting x-coord (upper-left) 0, // starting y-coord (upper-left) CW_USEDEFAULT, // window's x-size CW_USEDEFAULT, // window's y-size NULL, // parent window's handle NULL, // handle to a menu hInstance, // copy from WinMain() NULL); // disregard... } //----8<----[ CODE END ]--------// Again, the third parameter of CreateWindow() accepts flags, which means you can OR it with the "commented-out" flags below WS_OVERLAPPEDWINDOW to define your window...:). Oh yeah, if you're an adventurous guy and thought of OR'ing WS_OVERLAPPEDWINDOW with WS_OVERLAPPED...well, you'll get the one with the min-max-close button...guess why...:). CW_USEDEFAULT lets you use the default value MS Windows wants to give it. It can also be used in the x/y coordinate parameter. Now that we've created a window, let's show it shall we? But first let's check if it really WAS created...it's done through: if(hWindow == NULL) return FALSE; Showing the window is as easy as: //----8<----[ CODE BEGIN ]--------// ShowWindow(hWindow, SW_SHOW); // show created window // SW_HIDE // hide created window // SW_RESTORE // show window in restored state // SW_MAXIMIZE // show window in maximized state // SW_MINIMIZE // show window in minimized state //----8<----[ CODE END ]--------// No, you can't BITWISE-OR the second parameter here...guess why...;). Oh yeah, it seems I haven't discussed anything about that WndProc...well, here goes... When I initialized my wndclass variable above inside WinMain(), I had a line like this: wndclass.lpfnWndProc = (WNDPROC)WndProc; WndProc is actually a function that MS Windows calls within our program to know what to do whenever something happens (i.e. when user resizes the window, presses a key, closes the window, etc.). It's the "event handler" for our window. You can give it any name you want, but for clarity's sake, I'll stick to "WndProc" here. Here's how WndProc looks like: //----8<----[ CODE BEGIN ]--------// LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_CREATE: // window is created //--[ DO STUFFS HERE... break; case WM_DESTROY: // window is destroyed //--[ DO STUFFS HERE... PostQuitMessage(0); // add "quit" to msg queue break; case WM_SIZE: // window is resized //--[ DO STUFFS HERE... break; default: return(DefWindowProc(hWnd, message, wParam, lParam)); } return(0L); } //----8<----[ CODE END ]--------// So you see, WndProc's got nothing but a sequence of switch statements! What happens is MS Windows calls our program whenever something happens while our window is active. As an example, when we resize our window, MS Windows would call WndProc and pass as its parameter the message with value WM_SIZE and the handle of the window that was active when we resized our window. The "PostQuitMessage(0);" line stops our window from getting message. Think of it as a line that lets the GetMessage() function return FALSE (more on GetMessage() below). wParam and lParam are used to get more information about what happened...but we won't deal with them here...they're for Part 7...;). LRESULT is equivalent to a long data type. Think of CALLBACK as a keyword that lets MS Windows know that it's a dependable function that it can call on when needed...:). There ARE other types of messages, but I chose to gradually show them as we progress (we don't need all of them anyway...). DefWindowProc() takes care of all other messages that might be passed to our WndProc. Without it, messages would be lost, and that's a bad, bad, and I mean BAD thing...so keep it there...:). So how do we keep on getting messages? We make a loop in WinMain(). Here's the code: //----8<----[ CODE BEGIN ]--------// while(GetMessage(&msg, hWindow, 0,0)) // keep on getting msgs { TranslateMessage(&msg); // convert to "known" msg DispatchMessage(&msg); // send to msg queue } //----8<----[ CODE END ]--------// TranslateMessage() "converts" the message into something MS Windows recognizes...then the message is dispatched to our window's message queue where it will be used in our WndProc. GetMessage() has the following parameters: //----8<----[ CODE BEGIN ]--------// BOOL GetMessage( LPMSG lpMsg, // the message information HWND hWnd, // handle to our window UINT FilterMsgMin, // disregard... UINT FilterMsgMax // disregard... ); //----8<----[ CODE END ]--------// The reason I said to disregard FilterMsgMin and FilterMsgMax is because we want to receive all of the message that MS Windows sends us...;). Anyway... The msg passed to our GetMessage() function is of type MSG. MSG has the following structure: //----8<----[ CODE BEGIN ]--------// typedef struct tagMSG { HWND hWnd; // passed to WndProc's parameter UINT message; // passed to WndProc's parameter WPARAM wParam; // passed to WndProc's parameter LPARAM lParam; // passed to WndProc's parameter DWORD time; // time stamp (in milliseconds) POINT pt; // mouse coordinate } MSG; //----8<----[ CODE END ]--------// The "POINT pt;" line stores the x and y coordinates of the mouse when the message occurred. So you see, we use GetMessage() to get values to be used for our window's WndProc. It stores the message value (i.e. they are the WM_CREATE, WM_DESTROY, WM_MOVE, etc.), the wParam, and so on. And now we're done!!! [ ENDING ] And that's that! Not hard eh? If there is anything that's not clear to you, your mind, or to that part of you that lets you comprehend the things here, you may email me at (according to priority): willietang@hehe.com or willietang@yahoo.com While Windows Programming may seem tedious, everything that was shown here are but overhead codes you'll have to deal with. And not only that, these codes are just copy-and-paste codes, so what's important is for you to know what each part is doing...NOT remember each part...:). Oh yeah, the code below (under the NOMAD.SOURCE Section) creates a window alright. But the window's content is dependent on the background on which it first appeared. Run it and you will know what I mean. It's stupid too, try resizing and moving the window...;). Part 7 will be about DCs, GDIs, and how to use the wParam and lParam inside the WndProc. You will see there what I mean by copy-and-paste codes. We'll try to discuss how to draw a pixel and show texts on our window. Briefly, DC stands for "Device Context". It's like an ID tag that gives you the right to draw on a specific window. GDI means "Graphics Device Interface" and is used to draw basic stuffs on our window...so watch out for Part 7! [ REFERENCE ] I want to recommend the following books to you: "Programming Windows, Fifth Edition" Author : Charles Petzold Publisher : Microsoft Press "Programming Microsoft Visual C++, Fifth Edition" Authors : David Kruglinski George Shepherd Scott Wingo Publisher : Microsoft Press If you want to learn the Win32 API in a much deeper appreciation than the ones my tutorials offer, go for the first book. But if you're more interested about using MFC (Microsoft Foundation Class) in Visual C++, go for the second one...:). [ NOMAD.SOURCE ] /* Author : Willie Tang * Compiler : MS Visual C++ 6.0 * Created : December 02, 2001 * Modified : January 03, 2002 * * Notes : The code below works as a Win32 Application. * To run the code, create a new project (File->New). * In the Projects Tab, select "Win32 Application", * give the project a name them click OK. Then select * the "An Empty Project" radio button, click Finish. * * To insert the code to the project. Click on the * Project menu->Add to Project->New. On the Files * Tab, select "C++ Source File", give any name to * the file, then click OK. After that, copy and paste * the code below to the editor. ************************************************************/ #include LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_CREATE: // window is created //--[ DO STUFFS HERE...] break; case WM_DESTROY: // window is destroyed //--[ DO STUFFS HERE...] PostQuitMessage(0); // add "quit" to msg queue break; case WM_SIZE: // window is resized //--[ DO STUFFS HERE...] break; default: return(DefWindowProc(hWnd, message, wParam, lParam)); } return(0L); // return a zero } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wndclass; HWND hWindow; MSG msg; // Initialize values of wndclass' fields wndclass.style = CS_HREDRAW | CS_VREDRAW; // window's style wndclass.lpfnWndProc = (WNDPROC)WndProc; // pointer to WndProc wndclass.cbClsExtra = 0; // disregard... wndclass.cbWndExtra = 0; // disregard... wndclass.hInstance = hInstance; // copy from WinMain() wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // window icon // IDI_ASTERISK // IDI_EXCLAMATION // IDI_HAND // IDI_QUESTION // IDI_WINLOGO wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // window's mouse cursor // IDC_CROSS // a...'cross'...:) // IDC_NO // slashed circle // IDC_WAIT // hourglass // IDC_HELP // arrow with question mark // IDC_UPARROW // arrow pointing up wndclass.hbrBackground = NULL; // handle to bground brush wndclass.lpszMenuName = NULL; // window's menu name wndclass.lpszClassName = "MyWindowName"; // window's name // Register wndclass if(RegisterClass(&wndclass) == 0) return FALSE; // Create our window and get its handle hWindow = CreateWindow ("MyWindowName", // from wndclass.lpszClassName "My Title Bar", // this will show on title bar WS_OVERLAPPEDWINDOW, // border, title, min-max-close button // WS_OVERLAPPED // border, title // WS_POPUP // no title, no button, no border // WS_VISIBLE // window is visible from start // WS_VSCROLL // window has vertical scroll bar // WS_HSCROLL // window has horizontal scroll bar // WS_MAXIMIZE // window is created in maximized state // WS_MINIMIZE // window is created in minimized state 0, // starting x-coord (upper-left) 0, // starting y-coord (upper-left) CW_USEDEFAULT, // window's x-size CW_USEDEFAULT, // window's y-size NULL, // parent window's handle NULL, // handle to a menu hInstance, // copy from WinMain()'s parameter NULL); // disregard... // Check if window was created if(hWindow == NULL) return FALSE; // Show our window ShowWindow(hWindow, SW_SHOW); // show created window // SW_HIDE // hide created window // SW_RESTORE // show window in restored state // SW_MAXIMIZE // show window in maximized state // SW_MINIMIZE // show window in minimized state // Keep on grabbing messages while(GetMessage(&msg, hWindow, 0,0)) // keep on getting msgs { TranslateMessage(&msg); // convert to "known" msg DispatchMessage(&msg); // send to msg queue } return 0; } <<------------------------------------------------------>> [ Written By: Willie Tang ] [ Written On: 12.02.2001 ] [ Updated On: 01.03.2002 ]