Every windows program written in C (at least within the scope of this tutorial) has at least one .c file, at least one .rc file, and other data like icons.
The rc file is resource script file, and is compiled to .RES file which is then linked into the EXE file. These scripts may sound stupid at first but when you look at them, they have almost exactly the same format as the windows API calls for doing the same stuff, like creating windows. Every windows-capable C kit has some kinds of visual tools for building .rc files. Learn to use them.
I don't go into details of compiling the C and RC files. You'll have to find out how to do those things with your tools. In Watcom windows IDE you just insert all the source files into a project file and build the program.
All sources that use windows API will have the windows.h file included. This file is huge. You'll save literally weeks of compile time by using precompiled headers option of your compiler.
Here is what I'd call a hello-world windows program:
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
MessageBox(NULL,"Whee!","Hello World!",MB_OK);
return 0;
}
Since that source doesn't bring out anything new, and is pretty silly in any case, we'll need a lot more.
Now, to the real bare-bones windowed win32 program. The following code makes a program which displays a window which shows a title line and a small client area, and it can be minimized and most important of all, it can be closed. (It is pretty easy to make windows programs which can only be shut down via control-alt-del, or cad. Computer aided design? Whoever thought of that?).
#include <windows.h>
char progname[]="First program";
// forward declaration:
LRESULT CALLBACK winproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
WNDCLASSEX winclass;
HWND hWnd;
MSG msg;
winclass.cbSize=sizeof(WNDCLASSEX);
winclass.style=CS_DBLCLKS;
winclass.lpfnWndProc=&winproc;
winclass.cbClsExtra=0;
winclass.cbWndExtra=0;
winclass.hInstance=hInst;
winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
winclass.hCursor=LoadCursor(NULL,IDC_ARROW);
winclass.hbrBackground=GetSysColorBrush(COLOR_APPWORKSPACE);
winclass.lpszMenuName=NULL;
winclass.lpszClassName=progname;
winclass.hIconSm=NULL;
if (!RegisterClassEx(&winclass))
return 0;
hWnd=CreateWindow(
progname,
progname,
WS_SYSMENU|WS_CAPTION|WS_BORDER|WS_OVERLAPPED|WS_VISIBLE|WS_MINIMIZEBOX,
CW_USEDEFAULT,
0,
320,
200,
NULL,
NULL,
hInst,
NULL);
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
LRESULT CALLBACK winproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc (hWnd, uMsg, wParam, lParam) ;
break;
}
return 0;
}
Yep, that's just the bare bones. Don't be alarmed by the ammount of stuff in here. Doing something, even something relatively simple, usually takes lots of code lines in windows. This is not a bad thing, though, since it gives you a lot of stuff to mess with, should you need to do so sometime in the future (extremely likely, that is).
This chapter will only go through this source in detail.
Windows program has a WinMain instead of the useful main-function we used to be used to use
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
The WinMain uses WINAPI calling conventions, which include stack-based pascal-style and other stuff you shouldn't need to bother yourself with. The first two parameters, hInst and hPrevInst are handles to this instance and previous instance of this program. Again, as far as I know, the hPrevInst thing is from 16bit windows world, where, if you loaded same program twice, it used the same memory space for code, and thus could run lots more stuff in the massive 640k we had. In win32 world, each program has its own memory space (literally, since every program has its own 4G address space), so you don't need to care about this one. The hInst parameter is your handle to this program instance. Some functions need it, so you may wish to store it in global variable.
lpszCmdLine is a asciiz string of the commandline parameters, not including the executable filename. nCmdShow tells whether the program should start up minimized, normal or maximized.
In general sense you shouldn't need to care where the executable lays, but if you are like me and like to have a single datafile or even better, single EXE with the data appended, you'll wish to know how to find the exe. This is probably one of those overly generic questions that they don't need to be explained anywhere, so I wasted about one full day going through the win32 help file finding it out. Here it is for you:
char * buf;
buf=malloc(512);
GetModuleFileName(hInst,buf,512);
Simple, eh? (In defence I have to say that I built my first working windows game, cameleon/w32, in two days from scratch).
Now then. Windows is somewhat pseudo-object oriented, and has window classes. That is, every time you make up something new, you build a class first, then ask windows to create a window of this and that class. There are plenty of pre-defined window classes, such as BUTTON, SCROLLBAR etc.
winclass.cbSize=sizeof(WNDCLASSEX);
winclass.style=CS_DBLCLKS;
winclass.lpfnWndProc=&winproc;
winclass.cbClsExtra=0;
winclass.cbWndExtra=0;
winclass.hInstance=hInst;
winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
winclass.hCursor=LoadCursor(NULL,IDC_ARROW);
winclass.hbrBackground=GetSysColorBrush(COLOR_APPWORKSPACE);
winclass.lpszMenuName=NULL;
winclass.lpszClassName=progname;
winclass.hIconSm=NULL;
Lots of windows' structures include this cbSize parameter, mostly (I guess) since the API grows. Note that we are filling WNDCLASSEX structure, which is already EXpanded from the WNDCLASS-structure.
Style includes some flags, of which we have here CS_DBLCLKS which tells windows to check whether the window is double-clicked and to send the appropriate message to the window if it is. Different flags are ORed together. For a full list, consult the win32.hlp file.
lpfnWndProc points to the function which will ultimately do most of the work in the program, as it handles the messages sent to our program.
cbClsExtra and cbWndExtra tell windows to allocate extra zero-initialized bytes after class and window structures. Set to zero in most (all?) cases.
hInstance is, as you might guess, the instance we get as a winmain parameter.
hIcon is the handle to the icon we wish to use. As we haven't linked in any resources yet, we'll just load standard application icon from windows. Same goes for hCursor. If you want to have hourglass cursor when the mouse is on top of the window, use IDC_WAIT instead.
hbrBackground tells how to draw the window background. If you would wish to draw the window background yourself (as we will in the future), you would set this to NULL. In here we use one of the default brushes instead.
lpszMenuName points to the menu we don't have here, again since we haven't built any resources yet, so it's put to NULL. lpszClassName points to the name of our window class, which in this case is the same as our application name.
Finally, hIconSm is the handle to 16x16 icon used in the top left corner of the window. We don't have this, so let's set it to NULL as well, and windows will generate its own.
if (!RegisterClassEx(&winclass))
return 0;
Here we register the class (note the Ex end of the call, which again means that it is extended call). If the registration fails, we just terminate the program.
hWnd=CreateWindow(
progname,
progname,
WS_SYSMENU|WS_CAPTION|WS_BORDER|WS_OVERLAPPED|WS_VISIBLE|WS_MINIMIZEBOX,
CW_USEDEFAULT,
0,
320,
200,
NULL,
NULL,
hInst,
NULL);
Now, here we finally create the window. We could use CreateWindowEx as well, which would give us some new windows 4.0-specific options to choose from.
Two first parameters - the class name and window name. The window name will be shown at the title line, and the class name should be the same as we used when registering the window class. In our example these are the same.
Next parameter is the style, which, yet again, is a pile of flags. Here we have several in use. WS_SYSMENU gives us this top-left system menu where we can close, move, resize etc. the window (if applicable). WS_CAPTION gives us the title line, WS_BORDER gives the window a thin-line (non-resizeable) border, WS_OVERLAPPED makes this a overlapped window, whatever that means =), WS_VISIBLE is just what it says, and WS_MINIMIZEBOX gives the window the minimize box in the top right corner of the window, next to the closing box. I don't think I need to say that you can find other styles in the win32 API help, do I?
Next four parameters: x,y, x-size and y-size. CW_USEDEFAULT lets windows figure out some default location for the window. If x is usedefault, then y is ignored. I selected 320x200 sized window for this example. Please note that the 320x200 here is not the client area size, but includes borders, title line and possibly menus as well.
Next NULL would be the handle of the parent window if this were a child one, and the next one would be handle of the window menu or child window if we would have either one. Next we have our old friend the instance handle, and the final NULL would point to window creation data passed to our message handler function if we would need to do so. I still haven't needed to.
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
Next up is the call to set our window visibility, and since we don't have any reason not to, we pass on the order we got as winmain parameter. The updatewindow call sends a WM_PAINT call to our message handler, practically repainting the window. Or at least dirty areas of our window. Which just might at this point be the whole window.
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
The rest of our winmain is the message polling loop. Windows has control while GetMessage call is running, TranslateMessage translates any scancodes into legible characters, and DispatchMessage sends the message to our message handler. If GetMessage "fails", it means that we have received a WM_QUIT message, and we should terminate the program with the errorcode contained in msg.wParam.
If we would use accelerator keys (those key combinations that activate menu options wihout using the menus - like F2 to redeal in solitaire) we would use TranslateAccelerator instead of TranslateMessage.
LRESULT CALLBACK winproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Now here is our real heart of the window program, the window procedure. You should already know what the hWnd is. umsg is the message identifier, which could be WM_TIMER for instance, which would mean that we have received a timer message. The wParam and lParam are both 32bit ints instead of 16 and 32bit. This is another of those 16-bit windows legacies. These two parameters are used to carry lots of needed informations, usually even so that one parameter carries two 16-bit parameters, easily accessed with HIWORD and LOWORD predefined macros.
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc (hWnd, uMsg, wParam, lParam) ;
break;
}
return 0;
}
The WM_DESTROY message should always be answered with PostQuitMessage. If you need to stop the shutdown somewhere, better stop it at earlier stage, ie. at WM_CLOSE message, or even earlier if you manage. When you handle WM_DESTROY you should de-allocate all timers and open device contexts and other "borrowed" stuff.
The DefWindowProc has default actions for most messages (generally just ignored), and it saves you the trouble of writing most answers.
That's it! In the next chapter we'll build a simple framebuffer window and render a RGB plasma in it. I thought you might like that.