Resource files contain data separated from executable code. The purpose of using resource files are to define user interface components and store localisable data.
The application framework defined by the Uikon Core expects features of the application UI, such as menus, to be supplied in a resource file. The framework reads these resources itself. Other user interface components, such as dialogs, can also be easily created from resources, without the application needing to use this API itself.
The API has two key concepts - resource file reader and resource component reader.
Resource files contain data in numbered resources. The resource file reader allows such files to be opened and closed, and individual resources within them, as identified by symbolic identifiers, to be read into binary descriptors. Data items are then extracted from these descriptors using the resource component reader.
The resource
file can be read using RResourceFile
.
A resource is defined as a struct. Struct members can be simple, such as integers and strings, or complex, such as other structs, or arrays of structs. The resource component reader is used to read these struct members from a binary descriptor holding the resource into C++ variables. The application must ensure that it places the struct members into variables of appropriate types.
The resource
component header information can be accessed using TResourceReader
.
The basic concepts involved with using resource files are as follows:
In a resource file, define structs or use appropriate pre-defined structs and then define resources that use those structs.
Run the resource compiler to produce both a generated header file and a resource file. The generated header file defines the symbols which a program uses to refer to the resources.
A program which requires a resource file then performs the following:
Includes the generated header file in the appropriate source file to get symbolic access to the IDs of the resources contained within the file.
Opens a RResourceFile
object,
specifying the generated resource file name.
Reads any resource that
is required. The resource is identified by a symbolic ID which has been #define
d
as a number; the content of the resource is read into a binary descriptor,
derived from TDesC8
.
Converts the binary descriptor into whatever target format is appropriate for the data in the resource file.
Discards the descriptor, if appropriate, when the binary descriptor has been fully converted into its target format.
Closes the RResourceFile
when
all operations on the resource file are complete.
The resource text can be changed and the resources recompiled without altering or recompiling the C++ program. For example, to alter the language used by text strings.
Resource files contain data in numbered resources. A resource file has the following format:
A resource file is generated from text input using the resource compiler. The index can be used to efficiently find a resource given its numeric ID. There can be 1 to 4095 resources in a resource file. Each resource contains binary data whose length is determined by subtracting the start position of the resource from the start of the next resource (or from the start of the index, for the last resource in the file).
Functions for handling
resources in a resource file are encapsulated in the RResourceFile
class.
This class provides functions for:
opening and closing the file
reading resources
support for using multiple resource files
Generally, the process for reading a resource is to read it into a buffer of sufficient length, analyse it and place its data into C++ classes. Then, if appropriate, release the buffer.
The TResourceReader
class
must be used to simplify the process of resource data analysis.
The file types involved in resource file usage are defined in the following diagram:
Figure: File types
These files work together as follows:
the C++ compiler and
linker, together, take .cpp
source files and produce .exe
output
(or .dll
, or .app
, or another such
extension).
the resource compiler
takes a .rss
source file containing RESOURCE
statements
and converts it into a .rsc
resource file which contains
the resources the executable program will use. It also produces a .rsg
generated
header file, which contains the symbolic IDs of these resources.
the .rsg
file
is #include
d by the .cpp
file, so that
the C++ compiler has access to the symbolic IDs of the resources that will
be used.
C++ class definitions
are specified in a .h
file; a typical application will
include several system header files and usually one or more of its own.
resource structs are
specified in a .rh
file. A typical application will use
system-provided resource headers, which define standard controls etc. Only
if the application requires its own structs, will it include its own .rh
file.
flag values are #define
d
in a file which must be available to both the C++ compiler and the resource
compiler: the .hrh
extension is used for this and .hrh
files
are typically #include
d into the .h
file
that defines the classes (for C++) and the .rh
file that
defines the structs (for the resource compiler).
Software systems implemented in C++ are designed to be type-safe. Proper use of header files, make utilities, type declarations, classes and templates makes it difficult to pass data of the wrong type to functions, to assign data of the wrong type to variables, or to construct a program whose modules are inconsistent with one another. Thus a major source of programming errors is detected while the program is being built, before running it.
A program which uses resources must have a resource file which matches that program's requirements. However, the resource file and the executable program are only loosely bound together. A variety of errors are possible while dealing with resources, which cannot be detected during the build process. Programmers should be aware of these and take appropriate measures to avoid them.
Additional sources of error include:
Having a program and resource file which do not correspond.
This can arise because the resource file formats were updated, or the program was updated, but the resource file was not updated. When the program is run, the resources it loads are in the wrong format.
The structures in the resource file may be of a different format from that expected by the C++ program.
If
the C++ program expects a LONG
while a WORD
is
present in the resource file, then the C++ program will read two extra bytes
from the data item in the resource file. Any subsequent data read from that
resource will be an error.
This kind of error can only be avoided by C++ programs knowing which structs they are dealing with, knowing the data layouts of those structs in a resource file and writing code which works in that context.
Having resources of unexpected type.
The most likely reason for this to arise is because
there is no type safety in the resource compiler. Thus, it is possible for
a programmer to wrongly guess what kind of struct a particular resource must
use. Appropriate comments in the .rh
files and the documentation
of the resource file, will help to reduce this kind of error.
The resource compiler does provide some elementary safety features:
All resources are represented by a symbolic ID. This ensures that the resource IDs used by the C++ program and those expected by the compiler match, if the recommended build procedures are followed.
Flag values, maximum
length constants and some other values, may be defined in a .hrh
file.
This ensures that C++ programs uses correct resource.