by Peter G. Aitken ©1996
Originally published in Visual Developer magazine .
If you have done much C++ programming you'll know what I'm talking about when I mention resources. Those of you whose experience has been limited to Visual Basic may not be familiar with the term, even though you use resources every time you write a program. A resource is nothing more than a data item that the program uses internally. Icons, cursors, menu captions, and pictures are examples of resources. Visual Basic has two ways of handling program resources, one of which you may not know about. It can be extremely useful in certain situations, and you'll be doing yourself a favor if you become familiar with it.
In the traditional method of Visual Basic programming resource data is, for the most part, added to the program as properties and ends up embedded in the EXE file. Sure, you can keep some pictures as separate files and load them at run time for example, using the LoadPicture() function to set a PictureBox control's Picture property from an image on disk but usually you'll use object properties.
While this method of handling resources works well enough, it can have some drawbacks. When a form or module is loaded, all of its associated resource data is loaded as well, even those items that do not end up being used in any way. Often this extra overhead is trivial, but this is not always the case. Here's an example from my own experience. I was writing a large application, and I wanted to place several Image controls on a form, each loaded with a different picture. Depending on what the user was doing, a different Image control would be made visible, with the other remaining hidden until when and if they were needed. I did not want to distribute the original bitmap files with the application, so using LoadPicture() to load the needed images at run-time was not an option. I was, therefore, limited to loading each Image control with its picture at design time. The resulting overhead of having half a dozen large bitmaps load each time the form loaded, even though only one or two of them might actually be displayed, was too much of a performance hit and I had to abandon what I thought was a pretty good idea. Had the techniques I am about the describe been available to me I would have had another choice.
The second drawback of the standard method of dealing with resources arises when a program is intended for international distribution. You want the English version to have English labels and menu captions and an opening screen showing the Statue of Liberty. The French version needs French labels and captions and a picture of the Eiffel tower. Do you need to create a compile a separate source project for each language version? You had better hope not it would be a nasty job!
Both of these shortcomings can be avoided by using a resource script. A resource script is a text file that lists all of the resource data used by the program. The resource script is then compiled into a resource file, and the resource file is made part of your Visual Basic project. During program execution, the individual resources are loaded, when and if needed, by code in the program. Let's take a quick look at how this works, then we'll get to the details.
The following code shows a section of a resource script that defines a bitmap, an icon, two text literals, and a cursor:
101 BITMAP "C:\\BITMAPS\\EIFFEL.BMP"
201 ICON "C:\\ICONS\\SYMBOL1.ICO"
301 "The Eiffel Tower"
401 CURSOR "C:\\CURSORS\\CROSS.CUR"
Why the double backslashes in the file names? It's because resource scripts use the C preprocessor language, and the backslash is an escape character in C. You can see that each individual resource entry consists of a numeric identifier, a resource type specifier, and the file name or the actual data of the resource. Once this resource script has been compiled and included in your Visual Basic project, you could access these resources like this:
Image1.Picture = LoadResPicture(101, vbResBitmap)
MainForm.Icon = LoadResPicture(201, vbResIcon)
Label1.Caption = LoadResString(302)
Image1.MouseIcon = LoadResPicture(401, vbResCursor)
After this code executes, the control Image1 will display EIFFEL.BMP, the form MainForm's icon will be SYMBOL1.ICO, the control Label1 will display the caption "The Eiffel Tower", and the mouse cursor will display CROSS.CUR while over the image control.
There are four steps to using a resource script:
Now let's look at the details. I'll point out at the start that I will not be covering all the details of resource files, since that's a topic beyond the scope (and page limitation!) of a single column. I'll try to explain those aspects of resource files that are most likely to be useful to Visual Basic programmers.
You can use any text editor to create the resource script. I have not figured out a way to use the Visual Basic editor for this task, but you can use Notepad or Wordpad if you don't have a specialized programming editor. Remember to give the resource script the .RC extension, and keep it in your project directory. A Visual Basic project can have at most one resource file, so you need to define all of the project's resources in a single script file.
Each entry in a resource script contains an identifier, a type specifier, and the data itself. The identifier, which is used when you are later loading the resource, can be a number or a string. Each identifier must, of course, be unique within the resource script. You cannot use the identifier 1 because it is reserved by Visual Basic for the application icon. While using string identifiers may be more convenient, they do take up more space than numeric identifiers.
The resource type specifier can be either BITMAP, CURSOR, or ICON, each referring to the indicated type of data (string literals are handled a bit differently, as we'll see in a minute). The type specifier follows the resource identifier on the same line.
Bitmap, cursor, and icon resources originate in disk files. You can create your own, use the ones that come with Visual Basic, or obtain them from some other source. In the resource script, the file name must be enclosed in quotation marks, and can include path information if needed. Remember to use double backslashes in the path or the resource compiler will not be happy!
String literal resources are handled a bit differently. First of all, string resources must use numeric identifiers string identifiers are not permitted. Then, rather than identifying each individual string resource with a keyword, string resources are grouped together in a STRINGTABLE block as follows:
You can have more than one STRINGTABLE block in a resource script. Remember that each identifier must be unique.
To compile your resource script you need (guess what!) a resource compiler. Where do you get one? Surprise, there's one - actually, two - on your Visual Basic CD-ROM. They are not installed during Visual Basic installation, so you'll have to do it yourself, which is a simple matter of copying the EXE file and its one associated DLL to an appropriate location on your hard disk. The resource compilers are located in the \TOOLS\RESOURCE folder on the CD-ROM. Both 16 bit and 32 bit versions are provided, which causes a bit of a problem because the two programs not only have the same name (RC.EXE) but use DLL's with the same name, RCDLL.DLL. If you will be doing both 16- and 32-bit Visual Basic development you'll need to place each resource compiler version and its associated DLL in their own folder and then create batch files to call them for example, RC32.BAT and RC16.BAT.
Did I say batch file? Indeed I did the resource compilers belong to that vanishing breed of programs that are run from the command prompt. You'll have to fire up an MS-DOS window and work from there. The basic syntax for compiling a resource script is:
rc /r filename
where filename is the name of the resource script file. You can omit the .RC extension. The /r switch tells the compiler to output a .RES file. You wouldn't think this should be an option, but there are some more specialized uses for the resource compiler (which I won't be covering) that do not involve creating a .RES file. The one other command line switch that I recommend you use is /v, which stands for Verbose. This switch causes the compiler to display its progress on the screen. Without /v the compiler will simply do its job without displaying anything on the screen (unless there is an error) and you may think nothing is happening.
By default the resource file that the compiler creates has the same name as the resource script, with the .RES extension. If you want the resource file to have a different name use the /fo switch:
rc /fo outfile scriptfile
In this syntax, scriptfile is the name of the resource script file and outfile is the desired name for the compiled resource file.
Once you have compiled the resource file, adding it to your project is a simple matter of selecting Add File from the File menu. Compiled resource files are by default included in the Add File dialog box listing, so there's no problem locating them. When a resource file has been added to a project it will be listed in the Project window. When it is highlighted, however, both the View Form and View Code buttons are dimmed because viewing the contents of a compiled resource file makes no sense.
Visual Basic includes functions to access resource data that has been compiled into a resource file. Two of these functions are most often used, and the one you need depends on the type of resource data. LoadResPicture() is used for picture data bitmaps, icons, and cursors. Its syntax is:
The index argument is the numeric or string identifier associated with the resource in the resource script. The format argument specifies the type of picture, and must be one of the following defined Visual Basic constants:
You call this function and assign its return value to an appropriate object property, as shown in these examples:
Image1.Picture = LoadResPicture(101, vbResBitmap)
Form1.Icon = LoadResPicture(201, vbResIcon)
Picture1.MouseIcon = LoadResPicture("myCursor", vbResCursor)
To load string resource data, use LoadResString():
where index is the numerical identifier associated with the string in the resource script file (remember, string resources can have only numeric identifiers). The function returns the string, and its return value can be assigned to any property or variable for which string data is appropriate. For example, assigning string resource data to menu caption properties is the best way to provide menu commands in different languages.
If you're using a resource script only for the sake of efficiency, then the approach you'll take is straightforward. Put all of the project's resources in your resource script file, then use the Load...() functions to load the resource data as needed during program execution. Often, but not always, this will done be in a form's Form_Load() event procedure.
If you're trying to provide multiple language support, however, it gets a bit more complicated. You'll have one set of resources icons, label captions, pictures, menu command captions, etc. for each language. How do you incorporate the correct set of resources into your program? Remember, the goal is to require no changes, or at most trivial changes, to the project's source code. There are three methods you can use. For these examples I'll suppose that we are developing a product for the English, French, and Spanish language markets.
The first and least desirable method is to include all of the resources for all three languages in your resource script, differentiating them by their identifiers. For example:
enBackground BITMAP "ENGLAND.BMP"
spBackground BITMAP "SPAIN.BMP"
frBackground BITMAP "FRANCE.BMP"
201 "Buenos dias"
Then, define a global constant in your Visual Basic project to reflect the language target for the current compilation, and use Basic's conditional statements to load the proper resources:
const LANGUAGE = "ENGLISH" ' or FRENCH or SPANISH
Select Case LANGUAGE
Picture1.Picture = LoadResPicture(enBackground, vbResPicture)
Label1.Caption = LoadResString(101)
Picture1.Picture = LoadResPicture(spBackground, vbResPicture)
Label1.Caption = LoadResString(201)
Picture1.Picture = LoadResPicture(frBackground, vbResPicture)
Label1.Caption = LoadResString(301)
While this methods works it is somewhat clunky and has the disadvantage of making it difficult to add a new language, since you'll have to do a lot of editing of the Basic source code.
A second method, which is much more desirable than the one I just described, is to have a separate resource script file for each language. A given program resource is always identified by the same number or string, and each resource script file associates the identifier with the resource appropriate for the language. Thus, in ENGLISH.RC you would have
Background BITMAP "ENGLAND.BMP"
and in SPAIN.RC you would have
Background BITMAP "SPAIN.BMP"
Each script file is compiled to its own .RES file. In the Basic program, only one statement is required to load this resource:
Picture1.Picture = LoadResPicture(Background, vbResPicture)
When you are compiling for the English market, you would add ENGLISH.RES to the project. When it's time to compile for the Spanish market, simply remove ENGLISH.RES from the project and add FRENCH.RES. No changes to the Basic source code are required.
The third method, and this is the one I prefer, is to use conditional compilation in the resource script. The syntax for the conditional compilation statements is that of the C preprocessor language, but fortunately it is not all that different from Visual Basic's conditional compilation syntax and you should have no problem understanding it. Conditional compilation is based on whether or not certain symbols have been defined. Here's an example:
// Statements here are compiled if ENGLISH is defined.
// Statements here are compiled if FRENCH is defined.
// Statements here are compiled if SPANISH is defined.
To define a symbol you use the /d switch on the resource compiler command line. The command line
rc /r /d ENGLISH myproj.rc
compiles MYPROJ.RC with the symbol ENGLISH defined. Note that we are not concerned with what the symbol is defined as, simply that it is defined and that other symbols are not defined.
Continuing with our English/French/Spanish example, you would require only a single resource script file which would contain statements like this:
Background BITMAP "ENGLAND.BMP"
Background BITMAP "FRANCE
Background BITMAP "SPAIN.BMP"
101 "Buenos dias"
The resource identifiers are the same for each language version, which makes your Basic code simpler. The conditional compilation serves to associate a different resource with each identifier depending on which symbol is defined. To automate things even more, you could create a batch file to perform the various compilations using the /fo switch to create a separate compiled resource file for each language. Here is the batch file to create separate English, French, and Spanish resource files from the same resource script:
rc /r /v /d ENGLISH /fo english.res myproj.rc
rc /r /v /d FRENCH /fo french.res myproj.rc
rc /r /v /d SPANISH /fo spanish.res myproj.rc
Once you have the individual compiled resource files, it's a simple matter to compile separate versions of your Visual Basic project for the various language markets.
Resource scripts have additional capabilities, but you'll have to investigate on your own. The Win32 Software Development Kit documentation is a good source of information. I don't use a resource script for all of my Visual Basic projects or even for most of them, but in certain situations I have found that it makes my life a whole lot easier.
In my April/May column I mentioned how I had found Quarterdecks Magna RAM program to be a useful utility. Magna RAM is one of several "RAM extender" programs on the market that purport to speed Windows operations by compressing data in memory, permitting your RAM to hold more data thereby lessening time-consuming accesses to virtual memory on disk. My subjective impression was that Magna RAM did in fact decrease disk accesses when I had several large programs running at the same time on a 16 MB system. Since then, however, several objective tests of these programs have appeared in the mainstream computer press, and the general verdict is thumbs-down. Under certain specific conditions these programs may provide some improvement in performance - and perhaps my configuration just happened to fall in this category - but according to the tests they provide little if any overall benefit and in fact can sometimes slow things down. You should check the original reports for details, but I suggest that if you want to try one of these programs to see if it works for you, be sure that you have a no-questions-asked return privilege.
I always keep my eye on the offerings of Crescent Software, since they have been producing terrific Basic tools since way back when. In fact, there was a time before Visual Basic when Crescent, run then by Ethan Winer, was the only firm developing tools for Basic programmers. Ethan was a strong believer in Basic, never mind the sneers from the C and assembler programmers, and I am one of many whose Quick Basic projects benefited from Crescent tools.
One of Crescent's latest releases is called ClassAction, and with all the attention that classes and other object oriented tools have been getting lately I thought it worth a look. I admit to being a bit puzzled at first because it does not actually have anything to do with classes, at least not in the sense of helping the programmer to create and use classes. What is does is provide an object-based interface to the Windows API.
The API, as most of you know, is the vast collection of functions that is part of Windows itself. Whenever a program needs something done, whether it be resizing a window, staking claim to some memory, or sending data to the printer, it does so by means of API calls. Visual Basic's built-in functions, properties, and methods provide access to most but not all of the API's functionality. If you want the freedom to use the entire API you must declare each function in your program and then call it directly, dealing with the complexities of arguments and return values of a complex set of function that was, let's face it, designed more with the C/C++ programmer in mind.
ClassAction places a layer of objects between your program and the API. There are about two dozen objects, each one dealing with a related subset of API calls. For example, the foDeviceContext object (one of the most complex objects) provides access to all the API functions for drawing graphics, creating printer output, and manipulating bitmaps in memory. Likewise, the foWindow object provides access to the API's window-related calls. The process is delightfully simple. Rather than fighting with declarations and direct calls you need only to declare an instance of the desired ClassAction object and then deal with the object's methods and properties. Error handling is greatly improved as well. Errors are automatically trapped and an error raised using Visual Basic's Err object. Despite its misleading name it is a very useful product and should be welcomed by Visual Basic programmers who like to turn to the API for additional functionality.
Assistance with VBAssist
A few issues back Irene Smith critiqued VBAssist 4.0, a Visual Basic add-on from Sheridan. VBAssist is a combination of interface design tool and project management utility, and as Irene pointed out it contains a wide range of capabilities that range from the merely convenient to the incredibly helpful. If you tried VBAssist, however, I'll bet you soon noticed that the product wasn't quite ready for prime time. It did what it was supposed to do (usually), but it slowed lots of operations down to a crawl and caused occasional and seemingly random crashes. I'm glad to report that Sheridan did not try to hide from these problems, but 'fessed up and worked at correcting them. There have been two major patches, both made available at various on-line locations. The second patch, version 4.0b, also appeared on a CD which I assume is being automatically sent to all registered VBAssist users. The product's behavior is much improved, and if you were avoiding VBAssist because of reports of flaky performance there's no need to any more. It's an extremely useful product, and I heartily second Irene's original raves. What's more, Sheridan deserves a big wet kiss for providing such exemplary support. Any volunteers?