December 1997

Reconnoitering resources

by Sam Azer

A few questions seem to pop up over and over again on The Cobb Group's CPB-Thread list. For example, "Is there another resource editor for C++Builder?" The answer is, "Many--but you don't often need them." (Participation on CPB-Thread is a useful way to keep up with C++Builder issues and learn about its diverse range of features. To subscribe, visit www.cobb.com/cpb.)

If you're using Borland C++ 4.5, you already have a copy of Resource Workshop. (If you use version 5, it's built into the IDE.) In fact, any editor that will produce RC files will do--and such editors are readily available. For example, search a Delphi Super Page mirror for Triplex++, a freeware package by John Howe. Triplex includes a stand alone resource editor and a great Grep utility.

However, look closely at the resources you use most often, and you'll find you already have the tools you need--C++Builder does most of the work for you. It can edit dialog boxes and menus better than any other utility. The image editor is fairly basic, but it can create bitmaps, cursors and icons. That leaves string tables, version information, user defined resource objects, and such items as fonts and audio wave files. You'll find that these remaining resources are easy to create manually, if you know how.

Included with C++Builder is the RCC32 resource script compiler, which can compile any standard RC file. You don't have to do anything special to use the resource compiler--simply add any resource script to your project and C++Builder will automatically compile it and bind it to your DLL or EXE.

In this article, we'll make and use some resources. We'll start with a bit of background information, then we'll build a string table and add version information. Finally, we'll add a wave and play it. You can download our sample files from www.cobb.com/cpb as part of the file dec97.com; click the Source Code hyperlink. (Note that the resource part was so easy that the example was too boring--I jazzed it up with a splash screen, volume controls, and a demonstration of the three different ways to use PlaySound().) Let's get started.

An introduction to resources

Resource files are a convenient way to gather the various little objects a project needs: cursors, icons, bitmaps, meta files, MIDI and wave sounds, to name a few. You can also store user-defined objects and data as resources. Windows takes care of everything--memory allocation, loading, and unloading are all handled for you. In many cases, all you do is specify what resource you want to use. A resource script is a simple text file that tells Windows about your resources. Each resource has an ID--either a unique number or a string to identify the resource. Small resources like cursors can be converted to text and saved with the script. However, most resources are prepared using specialized tools that have nothing to do with programming. For example, you might create a small video for your application using Corel's PhotoPaint. It makes no sense to convert the video into text and store it in the resource file--it's easier to put the video's filename into the script. Later, the resource compiler will bind that file to your EXE or DLL.

Resource scripts support several preprocessor directives: #define, #include, #if/else/endif, #ifdef/ifndef, and #undef are all available. If you want to add some notes to your script, you can use the old C-style comments (/*...*/).

You can identify resources using a name, but the code runs faster if you use a unique numeric ID, instead. It's common practice to #include in your project and in your script a header that defines ID numbers. Doing so makes it easier to change the ID numbers later (in case of a collision with another resource ID).

String resources

You can embed any number of string tables within your resource script. Each string table keeps related strings together to help Windows better manage available memory. A string table, as with all resources, has a simple structure. Here's a resource header file:
/* resource header file */

#define IDS_NOWAVE    1001
#define IDS_SOUNDOFF  1002

And the following lines illustrate a resource script:
#include "resources.h"

Welcome Wave "welcome.wav"
Splash  Bitmap "splash.bmp"

stringtable
begin
    IDS_NOWAVE, "No wave devices"
    IDS_SOUNDOFF, "Sorry - Sound Disabled"
end

As you can see, each string resource consists of nothing more than an ID for each string, a comma, and the string itself. To use one of these strings in your code, load it using the LoadStr() function provided by VCL, as follows:
Edit1->Text = LoadStr( IDS_NOWAVE );
The LoadStr() function searches the currently executing task for a string table resource with the specified numeric ID. It returns an AnsiString result. If the resource isn't found, the function returns a null string.

Version information

You can precisely identify each product and executable file you release using version information. To do this, you need only include a version table in your resource script. The easy way is to copy an existing version script, then edit it. Listing A shows the version resource for our sample program.

Listing A: Version Information in Version.Rc

VERSIONINFO_1 VERSIONINFO
FILEVERSION 1, 2, 3, 4 /*file vers#, 4 parts*/
PRODUCTVERSION 1, 5, 6, 7 /* product vers#*/ 
FILEOS VOS_NT_WINDOWS32 /* WIN 32/NT */
FILETYPE VFT_APP /* use VFT_DLL for a DLL */
{
 BLOCK "StringFileInfo" /* Don't Touch This! */
 {
  BLOCK "040904E4" /* Don't Touch This! */
  {
   /* edit this for your app */
   VALUE "CompanyName", "Sam Azer\000\000"
   VALUE "FileDescription", "PlayResource Demo 
     shows how to make a resource file by 
     hand (and use the resources!)\000"
   VALUE "FileVersion", "1.2.3.4\000\000"
   VALUE "InternalName", "PlayRes\000"
   VALUE "LegalCopyright", "Copyright \
     251 1997\000\000"
   VALUE "OriginalFilename", "PlayRes.exe\000"
  }
 }

 BLOCK "VarFileInfo"
 {
  /* U.S. English, Windows ANSI code page */
  VALUE "Translation", 0x409, 1252 
 }
}
Your file and product version numbers can consist of up to four parts. If you don't need that many, set the unused values to zero. C++Builder will only produce code that runs under Windows 95 or NT, so you don't have to change the FILEOS value. If your project output is a DLL, change the FileType from VFT_APP to VFT_DLL. Don't touch the two block headers--Windows looks for them. The two Translation values at the bottom are Language and Code Page; the values shown are for U.S. English under the U.S. ANSI code page for Windows. Other values are listed in the Win32 Reference.

After you've built your project, you can view the version information from the Windows Explorer. Use the Explorer to find your project's EXE or DLL file. Right-click on the file, choose the Properties option at the bottom of the speed menu, then click on the Version tab.

Other resources

The remaining resources you might want to bind to your application probably already exist in a file somewhere. Using a single line of text in a resource script, you can assign an identifier and specify the filename. Later, the resource will be bound to your project. The format of this line is as follows:
id type [PRELOAD] filename
The ID can be a unique resource number or a name. Type is a string that describes the resource. There are a few standard types, such as BITMAP, CURSOR, ICON, and FONT, or you can make up your own. The PRELOAD option forces Windows to load the resource right away, which can be handy if you want to avoid delays with an often-used resource. If you use the following code in a form's OnPaint method, it will load and draw the sample bitmap Splash.BMP:
TBitmap *Splash = new Graphics::TBitmap;
Splash->LoadFromResourceName( (int)HInstance,
"Splash" );
// Set the form size to match the splash image
ClientWidth = Splash->Width;
ClientHeight = Splash->Height;
Canvas->CopyMode = cmSrcCopy;
Canvas->Draw( 0, 0, Splash );
Note the use of HInstance, a global variable provided by VCL that gives the handle for the currently executing task. Windows searches that EXE or DLL file for the requested resource. The next line of code will play the sample wave file Welcome.WAV. Take a look:
PlaySound("Welcome", HInstance, SND_RESOURCE);
What about all those resources that Windows doesn't understand? For user-defined data, the standard resource type is RCDATA. If you want to include the data in the resource script, it will look like this:
MyResource RCDATA
Begin
   "this is a string\000" /* note the null */
   1,2,3,4		/* integers */
   0x01, 0x02, 0x03, 0x04 /* Hex ints */
   '01', '02 03 04'	/* Hex bytes */
End
Windows provides five functions to handle user defined resources. FindResource()finds any type of resource, LoadResource()brings it into memory, and LockResource()returns a pointer to it and makes it stay put. SizeofResource() returns the size of a resource in bytes.

Finally, you don't have to get all your resources out of the currently executing application--you can use the LoadLibrary() function to get an HInstance handle for any DLL or EXE. Check the Win32 Platform SDK help files for details. Note that if you get an error return value after calling one of these functions, you can use the GetLastError() function to find out what went wrong. Use VCL's SysErrorMessage() function to translate the information into English.

Wrap up

We've covered only the basics of resources in this article--it's a big topic and there's plenty more ground to cover. The main thing to keep in mind is that it doesn't take much effort to create or use resources. And, you won't require many extra tools--C++Builder comes with just about everything you need.