Welcome back! As you may have guessed by the title, in this article I'm going to show you how to use resources in your Windows programs. Simply put, resources are binary data that's appended to your [font="Courier New"][color="#000080"].EXE[/color][/font] file after the actual program code. Using resources is easy to learn and has a lot of advantages. It allows the developer to consolidate a lot of data into one file, include custom icons and such things with their programs, and prevent users from altering that data. Windows supports a large number of resource types, so I'm just going to cover the ones I think are most convenient and easiest to learn: bitmaps, cursors, icons, menus, and string tables. After that, I'll show you how to create a custom resource type, so you can include anything you want.
Again, all you need to understand this article is a basic understanding of the C language. C++ always helps since Windows itself is object-oriented, but most of my code is straight C. Also, I will assume that you have read my previous article, "Beginning Windows Programming," or have the equivalent knowledge. I use and recommend the Microsoft Visual C++ compiler, but if you're using a different one, it's not a big deal. Ready? Here we go!
[size="5"]Resource Scripts
Before we get into any of the specific resource types, we need to go over the method used to tell the compiler what resources to include, and how. This method is to use a special file called a resource script, which is simply a text file either written by the developer or automatically generated by Visual C++, or whatever IDE you happen to be using. Your script file should have the file extension [font="Courier New"][color="#000080"].rc[/color][/font]. Most of a script file is taken up by lines which define or specify the resources to include. The simplest of these lines is used by several resource types, and looks like this:
[font="Courier New"][color="#000080"][bquote][identifier] [resource type] [filename][/bquote][/color][/font]
The identifier can be one of two things: a string representing the resource, or a numeric constant that's [font="Courier New"][color="#000080"]#defined[/color][/font] in a header file meant to accompany the resource script file. If you use numeric constants, which is usually a good idea, you can use the [font="Courier New"][color="#000080"]#include[/color][/font] directive in your script file to include the header that corresponds to it. You can also use C-style comments to make things a little easier to understand. That said, here's what a very simple resource script file might look like:#include "resource.h"
// icons
ICON_MAIN ICON myicon.ico
// bitmaps
IMG_TILESET1 BITMAP tileset.bmp
IMG_TILESET2 BITMAP tileset2.bmp
Don't worry about the actual lines themselves just yet; I'll explain each type of entry when I get to that particular resource. If you don't want to bother with resource scripting at all, you can just insert the resources from your IDE (in Visual C++, go to "Resource..." under the Insert menu) and a resource script will be generated automatically. I prefer to do it myself with good old Notepad, but don't ask me why because I can't think of a good reason. Now that you know the basics of creating a resource script, let's get started on the specific resource types.
[size="5"]Icons and Cursors
Most of the Windows programs you use every day have their own icons built in, and now you know how it works: they're simply resources included in the EXE file. Custom cursors that are used by those programs are also included as resources. You've already seen an example of the script line that includes an icon resource, and the line for cursors is very similar. Here they are:
[font="Courier New"][color="#000080"][bquote][identifier][nbsp][nbsp]CURSOR[nbsp][nbsp][filename]
[identifier][nbsp][nbsp]ICON[nbsp][nbsp][nbsp][nbsp][filename][/bquote][/color][/font]
After adding a line such as this to your script file -- make sure to include the script file in your project -- the icon or cursor specified by [font="Courier New"][color="#000080"][filename][/color][/font] will be included as a resource in your EXE file. That's all there is to it! You can use any icon/cursor editor to generate the files you want to include. I use the one that's included in Visual C++.[identifier][nbsp][nbsp]ICON[nbsp][nbsp][nbsp][nbsp][filename][/bquote][/color][/font]
Including the resources doesn't do a whole lot for your program, though, because you don't know how to use them yet! To get an idea for how icon and cursor resources are utilized in a program, let's revisit the window class we developed in the last article:
WNDCLASSEX sampleClass; // declare structure variable
sampleClass.cbSize = sizeof(WNDCLASSEX); // always use this!
sampleClass.style = CS_DBLCLKS | CS_OWNDC |
CS_HREDRAW | CS_VREDRAW; // standard settings
sampleClass.lpfnWndProc = MsgHandler; // message handler function
sampleClass.cbClsExtra = 0; // extra class info, not used
sampleClass.cbWndExtra = 0; // extra window info, not used
sampleClass.hInstance = hinstance; // parameter passed to WinMain()
sampleClass.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Windows logo
sampleClass.hCursor = LoadCursor(NULL, IDC_ARROW); // standard cursor
sampleClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // a simple black brush
sampleClass.lpszMenuName = NULL; // no menu
sampleClass.lpszClassName = "Sample Class" // class name
sampleClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // Windows logo again
HICON LoadIcon(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpIconName // icon-name string or icon resource identifier
);
HCURSOR LoadCursor(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpCursorName // name string or cursor resource identifier
);
[font="Courier New"][color="#000080"]HINSTANCE hInstance[/color][/font]: This is a handle to the instance of your application. To load resources from your program, just pass the [font="Courier New"][color="#000080"]HINSTANCE[/color][/font] that is passed to your [font="Courier New"][color="#000080"]WinMain()[/color][/font] function when the program is executed. To use standard Windows resources like we did in the window class above, set this to NULL.
[font="Courier New"][color="#000080"]LPCTSTR lpIconName, lpCursorName[/color][/font]: This is a string identifier that identifies the resource you want to load. If your script file refers to resources by string, simply pass the string. But if you're using numeric constants, the Windows header files include a macro that changes an integer to a form compatible with this parameter called [font="Courier New"][color="#000080"]MAKEINTRESOURCE()[/color][/font].
As an example, let's look at the line that sets the icon to represent the program. Suppose your resource script file looks like this:
#include "resource.h"
ICON_MAIN ICON myicon.ico
CURSOR_ARROW CURSOR arrow.cur
sampleClass.hIcon = LoadIcon(hinstance, "ICON_MAIN");
#define ICON_MAIN 1000
#define CURSOR_ARROW 2000
sampleClass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(ICON_MAIN));
or...sampleClass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(1000));
or...int ident = 1000;
sampleClass.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(ident));
That's about all you need to know about including icons and cursors in your programs, but I'll mention one more thing while we're on the topic. If you want to set a cursor sometime other than at the beginning of the program, there's a simple Windows function you can use to accomplish this:HCURSOR SetCursor(HCURSOR hCursor);
[size="5"]Bitmaps
Including bitmap resources is probably the easiest way to add images to your program. Bitmaps are native to Windows and so there are functions included to deal with loading and manipulating them, but remember, if you include too many, you'll end up with an enormous [font="Courier New"][color="#000080"].EXE[/color][/font] file. In any case, you include bitmaps in your resource script file in basically the same way you handle icons and cursors:
[font="Courier New"][color="#000080"][bquote][identifier] BITMAP [filename][/bquote][/color][/font]
There is a function called [font="Courier New"][color="#000080"]LoadBitmap()[/color][/font] that is analagous to [font="Courier New"][color="#000080"]LoadCursor()[/color][/font] and [font="Courier New"][color="#000080"]LoadIcon()[/color][/font]; it is used to retrieve a handle to a bitmap, but since I haven't talked about graphics yet, I won't describe this function here. You can probably guess exactly how it works, but once you have a handle to a bitmap, what would you do with it? More to come on that in the future, don't worry! For now, I just wanted to show you how to include a bitmap resource. Now let's look at something you can use right away.[size="5"]String Tables
The string table is one of my favorite resource types. It's exactly what you're thinking: a giant table full of strings. There are any number of purposes for using a string table. You can use it to store data filenames, character dialogue for a game, message-box text, text for menus that are generated by the program, anything you want. Creating a string table in your script file is easy. Here's what it looks like:
STRINGTABLE
{
// entries go here
}
// program information
STRINGTABLE
{
1, "3D Space Game v1.0"
2, "Written by The Masked Coder"
3, "(C) 2000 WienerDog Software"
}
int LoadString(
HINSTANCE hInstance, // handle to module containing string resource
UINT uID, // resource identifier
LPTSTR lpBuffer, // pointer to buffer for resource
int nBufferMax // size of buffer
);
[font="Courier New"][color="#000080"]HINSTANCE hInstance[/color][/font]: Once again, this is the instance of your application.
[font="Courier New"][color="#000080"]UINT uID[/color][/font]: This is the number that identifies the particular string you want to load.
[font="Courier New"][color="#000080"]LPTSTR lpBuffer[/color][/font]: This is a pointer to the location you want the string copied to.
[font="Courier New"][color="#000080"]int nBufferMax[/color][/font]: This is the size of the buffer in bytes. If the string to be loaded is longer than the buffer can hold, the string is truncated and null-terminated.
For example, to load WienerDog Software's copyright message, the following code would be used:
char buffer[80];
LoadString(hinstance, 3, buffer, sizeof(buffer));
#define ST_WIENERDOGCOPYRIGHT 3
[size="5"]Menus
This is the last type of Windows resource I'll go over, and it's also one of the most useful. Menu resources are used to define the menu bar that would appear underneath the title bar of your application, and are loaded during the definition of the window class. Looking back, in the window class we developed during the last article, there was a line that looked like this:
sampleClass.lpszMenuName = NULL;
If you're creating a windowed application, chances are that you'll want to have a menu bar of some sort. This is done using the menu resource. The script file entry for this one can get a little complicated, but here is its most basic form:[identifier] MENU
{
POPUP [menu name]
{
MENUITEM [item name], [identifier]
}
}
MAIN_MENU MENU
{
POPUP "&File"
{
MENUITEM "&New", MENUID_NEW
MENUITEM "&Open...", MENUID_OPEN
MENUITEM "&Save", MENUID_SAVE
MENUITEM "Save &As...", MENUID_SAVEAS
MENUITEM "E&xit", MENUID_EXIT
}
POPUP "&Help"
{
MENUITEM "&Contents", MENUID_CONTENTS
MENUITEM "&Index...", MENUID_INDEX
MENUITEM "&About", MENUID_ABOUT
}
}
HMENU LoadMenu(
HINSTANCE hInstance, // handle to application instance
LPCTSTR lpMenuName // menu name string or menu-resource identifier
);
sampleClass.lpszMenuName = MAKEINTRESOURCE(MAIN_MENU);
BOOL SetMenu(
HWND hWnd, // handle to window
HMENU hMenu, // handle to menu
);
[font="Courier New"][color="#000080"]HWND hWnd[/color][/font]: This is the handle to the window to which you want to attach the menu. Pass the handle that was returned when you called [font="Courier New"][color="#000080"]CreateWindowEx()[/color][/font].
[font="Courier New"][color="#000080"]HMENU hMenu[/color][/font]: To identify the menu, pass the handle returned by [font="Courier New"][color="#000080"]LoadMenu()[/color][/font]. If you pass [font="Courier New"][color="#000080"]NULL[/color][/font], the specified window's menu is removed.
This resource is particularly nice because all the functionality of the menu is defined by that simple scripting. But what happens when the user selects a menu option? The answer is that Windows sends a [font="Courier New"][color="#000080"]WM_COMMAND[/color][/font] message informing the program that it must take action. Let's pay a visit to our message-handling function and see if we can't figure out how to handle this.
[size="5"]Handling Menu Events
As you probably remember, Windows messages are handled by a special callback function usually called [font="Courier New"][color="#000080"]WindowProc()[/color][/font] or something similar. The simple one we wrote last time was called [font="Courier New"][color="#000080"]MsgHandler()[/color][/font], and its prototype looked like this:
LRESULT CALLBACK MsgHandler(
HWND hwnd, // window handle
UINT msg, // the message identifier
WPARAM wparam, // message parameters
LPARAM lparam, // more message parameters
};
#define LOWORD(l) ((WORD) (l)) #define HIWORD(l) ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))
short int x = 244, y;
y = x << 4;
Contents of y: [font="Courier New"][color="#000080"]0000 1111 0100 0000[/color][/font]
Anyway, use [font="Courier New"][color="#000080"]LOWORD()[/color][/font] to extract the low word of [font="Courier New"][color="#000080"]wparam[/color][/font], and you have the ID of the menu item that was selected. So, somewhere in your [font="Courier New"][color="#000080"]MsgHandler()[/color][/font] function, you should have something like this:
// handle menu selections
if (msg == WM_COMMAND)
{
switch (LOWORD(wparam))
{
case MENUID_NEW:
// code to handle File->New goes here
break;
case MENUID_OPEN:
// code to handle File->Open goes here
break;
// the rest of the option handlers go here
}
// tell Windows you took care of it
return(0);
}
[size="5"]Custom Resources
The standard Windows resources are those which have special functions for loading and handling them, but they are not the only types you can use. Resources can be any data you want them to be! Working with custom resources requires a little more work since you must locate and read the resource data manually, but it's not too bad. The script file entry for a custom type follows the basic format you're already used to:
[font="Courier New"][color="#000080"][bquote][identifier] [resource type name] [filename][/bquote][/color][/font]
The resource type name is a string that defines your custom resource, and can be whatever you want. For the purposes of this example, let's say you want to include a data file called [font="Courier New"][color="#000080"]p1config.dat[/color][/font] that contains information necessary to initialize a character in a game program. We'll call the custom resource type [font="Courier New"][color="#000080"]CHARCONFIG[/color][/font]. With that in mind, here's an example of what the script file entry might look like for your data file:DATA_PLAYERINIT CHARCONFIG p1config.dat
HRSRC FindResource(
HMODULE hModule, // module handle
LPCTSTR lpName, // pointer to resource name
LPCTSTR lpType // pointer to resource type
);
[font="Courier New"][color="#000080"]HMODULE hModule[/color][/font]: The [font="Courier New"][color="#000080"]HMODULE[/color][/font] data type is simply an [font="Courier New"][color="#000080"]HINSTANCE[/color][/font]. Don't ask me why they felt they needed another name for it, but you should simply pass the instance of your application. You don't even need a typecast because the data types are exactly the same.
[font="Courier New"][color="#000080"]LPCTSTR lpName[/color][/font]: This is the resource identifier. Remember to use [font="Courier New"][color="#000080"]MAKEINTRESOURCE()[/color][/font] on this one if you're using numeric constants to define your resources.
[font="Courier New"][color="#000080"]LPCTSTR lpType[/color][/font]: This is the resource type, so pass the string you used to define your resource type. In our case, this is [font="Courier New"][color="#000080"]CHARCONFIG[/color][/font].
A sample function call looks like this:
HRSRC hRsrc = FindResource(hinstance, MAKEINTRESOURCE(DATA_PLAYERINIT), "CHARCONFIG");
HGLOBAL LoadResource(
HMODULE hModule, // resource-module handle
HRSRC hResInfo // resource handle
);
[font="Courier New"][color="#000080"]HMODULE hModule[/color][/font]: Again, simply the application instance.
[font="Courier New"][color="#000080"]HRSRC hResInfo[/color][/font]: Pass the handle that was returned by [font="Courier New"][color="#000080"]FindResource()[/color][/font].
Now that you have a handle to the resource, you can finally get a pointer to the data that was in the resource file you included. This is achieved with a call to [font="Courier New"][color="#000080"]LockResource()[/color][/font], shown here:
LPVOID LockResource(HGLOBAL hResData);
UCHAR* LoadCustomResource(int resID)
{
HRSRC hResInfo;
HGLOBAL hResource;
// first find the resource info block
if ((hResInfo = FindResource(hinstance, MAKEINTRESOURCE(resID), "CUSTOMRESOURCETYPE")) == NULL)
return(NULL);
// now get a handle to the resource
if ((hResource = LoadResource(hinstance, hResInfo)) == NULL)
return(NULL);
// finally get and return a pointer to the resource
return ((UCHAR*)LockResource(hResource));
}
[size="5"]Closing
Well, that about does it for resources! See, programming for Windows is fun. Even with all this knowledge of resources, you're still pretty limited in what you can actually get your programs to do, so next time I'll be going over some basic Windows GDI (Graphics Device Interface) functions, so you can start using all this stuff to put some demo programs together. As always, send me your comments, your ideas, your death threats:
E-mail: [email="ironblayde@aeon-software.com"]ironblayde@aeon-software.com[/email]
ICQ: UIN #53210499
Farewell everyone, until we meet again...