Resource File Classes
by DelphiDabbler

User Guide

Overview

These classes support read and writing 32 bit binary resource files. Some routines are also included that assist in manipulating resource identifiers.

The code encapsulates the high-level structure of a resource file and its resources. It deals only with raw resource data - the actual format of the raw data depends on the resource type. This code does not provide any support for specific resource types or their data formats.

This document is divided into various sections:

Classes

The classes included in this release are as follows:

Class Description
TPJResourceFile Encapsulates a 32 bit binary resource file and exposes the entries within it. The class allows resources within the file to be accessed, created, read and searched for.
TPJResourceEntry Encapsulates a single resource within a resource file. Permits access to the resource type, name and language. Other header data can be modified. Provides read/write access to the raw resource data through a TStream.
EPJResourceFile Class of exception raised by the above classes to report errors.

Although we use the word "file" in these notes, this term also covers binary resource data stored in a stream.

Detailed descriptions of the classes now follow.

TPJResourceFile

This class is used to encapsulate a 32 bit resource file, to find which resources in contains and to add new and delete existing resources. The number of resources in the file is given by the EntryCount property while the Entries[] property provides access to them. Resource entries are not created directly by the user but via the AddEntry() method of this class.

Resource entries accessed via this class are TPJResourceEntry objects and have methods and properties that can be used to interogate and update them.

Methods
constructor Create;

Class constructor. Creates a new empty resource file object.

destructor Destroy;

Class destructor. Usually called via the Free method. Destroying the object frees all the resource entries currently in the file.

procedure Clear;

Clears all resources from the file object. The resources are freed.

function DeleteEntry(const Entry: TPJResourceEntry): Boolean;

Deletes a resource entry from the resource file object if it exists. The entry object is not freed and must be freed by the user since it will no longer be freed by the Clear or Destroy methods. The recommended way to delete and free a resource entry is to free the entry object since this automatically unlinks it from the resource.

function IndexOfEntry(const Entry: TPJResourceEntry): Integer;

Gets the index number of the given resource entry in the resource file's Entries[] property.

procedure LoadFromFile(const FileName: TFileName);

Loads a resource file from the named file, replacing any existing resource.

procedure LoadFromStream(const Stm: TStream);

Loads a resource "file" from the current location in the given stream.

procedure SaveToFile(const FileName: TFileName);

Saves data as a 32 bit resource file.

procedure SaveToStream(const Stm: TStream);

Saves data in resource file format on the given stream at the current location.

class function IsValidResourceStream(const Stm: TStream): Boolean;

Checks if the given stream contains data representing a valid 32 bit resource file starting at the current location. This method checks that a 32 bit resource file header is present but does not validate the whole of the file. Note that the stream is not rewound to the starting position after the check is made.

function AddEntry(const ResType, ResName: PChar;
  const LangID: Word = 0): TPJResourceEntry;

Adds a new, empty, resource to the current "file" object.

function AddEntry(const Entry: TPJResourceEntry; const ResName: PChar;
  const LangID: Word = 0): TPJResourceEntry;

Adds a copy of the given resource entry to the current "file" object with a new resource name and language id. The new entry has the same resource type as the one being copied.

function FindEntry(const ResType, ResName: PChar;
  const LangID: WORD = $FFFF): TPJResourceEntry;

Finds a resource entry with the given type, name and language id. The search can ignore the resource name and or language id in which case first entry that matches the provided information is found.

function FindEntryIndex(const ResType, ResName: PChar;
  const LangID: WORD = $FFFF): Integer;

Finds the Entries[] property index of a resource entry with the given type, name and language id. The search can ignore resource name or language id in which case first entry that matches the provided information is found.

function EntryExists(const ResType, ResName: PChar;
  const LangID: WORD = $FFFF): Boolean;

Checks whether a resource entry matching given search criteria exists.

Properties
property EntryCount: Integer;

Read-only property that provides the number of resources in the resource file (i.e. the number of entries in the Entries[] property).

property Entries[Idx: Integer]: TPJResourceEntry;

Read-only property that provides access to all the resources in the resource file by index.

Back to top

TPJResourceEntry

Encapsulates a resource within a resource file. Object of this class must not be directly instantiated. TPJResourceFile automatically creates the required objects when they are read from a file or can create new instances in its AddEntry methods. TPJResourceFile is actually an abstract class that provides the required interface for manipulating resource entries. The actual concrete class that implements the resource entry is private. Therefore all valid resource objects are accessed via TPJResourceFile.

TPJResourceEntry objects are used to manipulate and interogate resource entries. This is done mainly via properties which give access to resource header information, allow some header information to be set and give read/write access to the raw resource data via a TStream.

Care should be taken not to use a resource entry after the resource file object has been cleared or destroyed or a new resource file has been read since all these actions free previous resource entries. A TPJResourceEntry object can be freed directly - doing so removes the entry from any resource file object it belongs to.

Methods
function IsMatching(const ResType, ResName: PChar;
  const LangID: Word = $FFFF): Boolean;

Checks if the resource entry has the given type, name and language id. The resource name and/or language id can be ignored in which case only the values provided will be matched. For example to match only a resource type use IsMatching(MyResType, nil);

function IsMatching(const Entry: TPJResourceEntry): Boolean;

Checks if the resource entry has the same type, name and language ID as the given resource entry.

Properties
property DataSize: DWORD;

Read-only. Provides the size of the resource data (same as calling Data.Size).

property HeaderSize: DWORD;

Read-only. Provides the size of the resource header (which varies depending on the type and size of the resource type and name).

property DataVersion: DWORD;

Gets or sets the predefined data resource version information.

property MemoryFlags: Word;

Gets or sets the attribute bitmask that specifies the state of resource.

property LanguageID: Word;

Read-only. Gets the language used by the resource (value of 0 is language-neutral).

property Version: DWORD;

Gets or sets the user specified version number for resource data.

property Characteristics: DWORD;

Gets or sets the user specified characteristics of the resource.

property ResName: PChar;

Read-only. Gets the name of the resource. This value is either a pointer to a zero-terminated string or an ordinal value as returned from MakeIntResource().

property ResType: PChar;

Read-only. Gets the type of the resource. This value is either a pointer to a zero-terminated string or an ordinal value as returned from MakeIntResource().

property Data: TStream;

Read-only. Gets a reference to the stream that contains the resource's raw data. The stream can be used to read or write the data. Any padding bytes that follow the resource's data are not included in the stream.

Back to top

EPJResourceFile

This is the class of exceptions that are raised directly by TPJResourceFile and TPJResourceEntry. Note that some methods may raise exceptions of other classes.

The class defines no new methods or properties.

Back to top

Routines

The helper routines provided with this code can be used to assist in manipulating resource identifiers. They can be useful for working with Windows API routines as well as the classes presented here. Here are the routines.

function IsIntResource(const ResID: PChar): Boolean;

A clone of the IS_INTRESOURCE macro defined on MSDN that checks if a resource identifier is ordinal or a string. Complements the MakeIntResource "macro" defined in Windows.pas.

function IsEqualResID(const R1, R2: PChar): Boolean;

Checks for equality of two resource identifiers. To be equal the identifiers either be ordinal and have the same value or must both point to strings that have the same text when case is ignored.

function ResIDToStr(const ResID: PChar): string;

Converts a resource identifier into its string representation as defined on MSDN.

Back to top

Constants

Some constants are defined to assist in setting some of the class properties.

Memory Flags Constants

The following flags are used to form the bitmask in a resource entry's MemoryFlags property. The first four constants in the table can or ORd together to form the bitmask. The final three constants are complements of the first three and are ANDed againgst the bitmask to remove their complement from the bitmask.

Constant Value Description
RES_MF_MOVEABLE $0010 The system can move the resource in memory. If this flag is not present the resource is fixed in memory.
RES_MF_PURE $0020 The resource contains DWORD aligned data so that padding is not required. If this flag is not present the resource is not DWORD aligned and must be padded.
RES_MF_PRELOAD $0040 The resource is to be loaded in memory just after the application itself has been loaded. If not present then loading of the resource may be deferred until required by the application.
RES_MF_DISCARDABLE $1000 If set then on low memory conditions, the resource can be removed from memory and then reloaded when the application needs it, otherwise the resource must remain in memory.
RES_MF_FIXED $FFEF Complement of RES_MF_MOVEABLE: used to remove this flag from the bitmask.
RES_MF_IMPURE $FFDF Complement of RES_MF_IMPURE: used to remove this flag from the bitmask.
RES_MF_LOADONCALL $FFBF Complement of RES_MF_LOADONCALL: used to remove this flag from the bitmask.

Note that Windows NT ignores RES_MF_MOVEABLE, RES_MF_IMPURE and RES_MF_PRELOAD.

Predefined Resource Types

Delphi's Windows unit defines all the predefined resources types known at the time of writing except RT_HTML and RT_MANIFEST. Therefore these two type identifiers are defined in this unit for convenience:

RT_HTML = MakeIntResource(23);
RT_MANIFEST = MakeIntResource(24);

See Appendix 1 for a description of all the predefined resource types.

Back to top

Examples

Example 1: Loading a resource file

In this first example we demonstrate how to create a resource file object and how to load a file into it. The following code fragment shows how this is done.

var
  ResFile: TPJResourceFile;
  ...
begin
  ResFile := TPJResourceFile.Create;
  try
    ResFile.LoadFromFile('MyResFile.res');
    ...
    // Do something with resource file object
    ...
  finally
    ResFile.Free;
  end;
end;

First we create a TPJResourceFile object and then use its LoadFromFile method read a file from disk. We then process the file in some way and once we are finished we free the resource file object. That's all there is to it. Note that if the given file does not contain a valid 32 bit resource file an exception will be raised.

We can also read resource data from a stream rather than loading from file by using the LoadFromStream method of TPJResourceFile in place of LoadFromFile.

Back to top

Example 2: Accessing all resources in a file

In our next example we show how to scan through all the resources in a file and how to list some information about each one. The following example assumes we have created a resource files object ResFile and have loaded a resource file into it (as in example 1). We also assume that the form contains a memo named Memo1. Here is the code:

var
  ResFile: TPJResourceFile;
  ResEntry: TPJResourceEntry;
  EntryIdx: Integer;
begin
  ...
  // Assume ResFile contains a loaded resource file
  ...
  Memo1.Clear;
  for EntryIdx := 0 to Pred(ResFile.EntryCount) do
  begin
    ResEntry := ResFile.Entries[EntryIdx];
    Memo1.Lines.Add(
      Format(
        'Type: "%s"   Name: "%s"   LanguageID: %0.4X',
        [ResIDToStr(ResEntry.ResType), ResIDToStr(ResEntry.ResName),
        ResEntry.LanguageID]
      )
    );
  end;
  ... 
  // Don't forget to free ResFile at some stage.
end;

This code uses both TPJResourceFile and TPJResourceEntry objects. The resource file's EntryCount property tells us how many resources there are in the file. Each resource is represented by a TPJResourceEntry object made available from the integer indexed Entries[] array property. So we loop through all the valid indexes in Entries[] and store a reference to each resource entry in turn. Having got the resource entry object we now access its ResType, ResName and LanguageID properties to get the information we want to display.

The details of each entry are formatted by Delphi's Format function and added to Memo1. Note that we use the ResIDToStr helper function to get a string representation of the resource type and name. We display the language ID as a four digit hex number since the value is a Word.

You may have noticed that we have not freed any of the resource entry objects. This is not necessary since they are all freed automatically when the resource files object is freed (as shown in example 1).

Back to top

Example 3: Finding a resource

To find a resource we use either the FindEntry or FindEntryIndex methods. The difference is that FindEntry returns the TPJResourceEntry object for the entry (or nil if not found) while FindEntryIndex returns the index of the entry in the Entries[] property.

Let's assume a resource file is loaded into the TPJResourceFile variable ResFile. We want to find a RT_HTML resource named INDEX_HTML. The following code checks if such a resource exists and displays its data size in a message box, or a message saying the reosurce doesn't exist. This first version of the code uses FindEntry:

// Version using FindEntry
var
  ResFile: TPJResourceFile;
  Entry: TPJResourceEntry;
begin
  ...
  // Assume ResFile contains a loaded resource file
  ...
  Entry := ResFile.FindEntry(RT_HTML, 'INDEX_HTML', $0809);
  if Assigned(Entry) then
    ShowMessageFmt(
      'Data size for INDEX_HTML is %d',
      [Entry.DataSize]
    )
  else
    ShowMessage('Can''t find resource');
  ... 
  // Don't forget to free ResFile at some stage.
end;

This second version of the code shows how the same result is obtained with FindEntryIndex:

// Version using FindEntryIdex
var
  ResFile: TPJResourceFile;
  Entry: TPJResourceEntry;
  Idx: Integer;
begin
  ...
  Idx := fResFile.FindEntryIndex(RT_HTML, 'INDEX_HTML');
  if Idx >= 0 then
  begin
    Entry := fResFile.Entries[Idx];
    ShowMessageFmt(
      'Data size for INDEX_HTML is %d', [Entry.DataSize]
    );
  end
  ...
end;

Note that we have used the "short form" of the FindEntry and FindEntryIndex methods above: they find the first resource with the given type and name, irrespective of language. The long version of the methods finds a specific resource type, name and language. For example the following code finds a RT_HTML resource named INDEX_HTML with language $0809:

// "Full" version of FindEntry
var
  ResFile: TPJResourceFile;
  Entry: TPJResourceEntry;
begin
  ...
  Entry := ResFile.FindEntryIndex(
    RT_HTML, 'HTMLRES_HTML', $0809
  );
  if Assigned(Entry) then
    ... etc ...
end;

Back to top

Example 4: Listing specific resources from a file

We can use the IsMatching method of TPJResourceEntry to check if a specific resource matches given criteria. IsMatching can match just a resource type, and resource type and name or can uniquely identify a resource in a file by matching its type, name and language. Like TPJResourceFile.FindEntry, the language ID parameter is optional. The resource name parameter can be nil if we don't want to specify the name in the match.

Given the above description of IsMatching, we can list all the RT_HTML resources in a resource file in a TMemo with this code:

var
  ResFile: TPJResourceFile;
  Entry: TPJResourceEntry;
  Idx: Integer;
begin
  ...
  // Assume ResFile contains a loaded resource file
  ...
  for Idx := 0 to Pred(ResFile.EntryCount) do
  begin
    Entry := ResFile.Entries[Idx];
    if Entry.IsMatching(RT_HTML, nil) then
      Memo1.Lines.Add(
        Format('%s', [ResIDToStr(Entry.ResName)])
      );
  end;
  ...
end;

To list only all the different language versions of the RT_HTML resource named INDEX_HTML we simply change the IsMatching method call in the for loop to:

Entry.IsMatching(RT_HTML, 'INDEX_HTML')

Back to top

Example 5: Adding or modifying a resource's data

While the code in this unit does not understand the various resource data formats it does assist in reading, adding, updating and deleting the raw resource data. The TPJResourceEntry class's Data object exposes the resource data as a TStream which means that we can use normal stream handling techniques to access the data.

Reading the data

The following code fragment shows how to read all the data from the resource to a buffer.

var
  Entry: TPJResourceEntry;
  Buf: PByte;
begin
  ...
  // Make sure Entry references a resource object
  ...
  // Create buffer of required size
  GetMem(Buf, Entry.DataSize);
  try
    // Make sure resource data stream at start
    Entry.Data.Position := 0;
    // Read all resource data into buffer
    Entry.Data.ReadBuffer(Buf^, Entry.DataSize);
    // Rewind data stream again
    Entry.Data.Position := 0;
    ...
    // Do something with Buf
    ...
  finally
    // Release buffer
    FreeMem(Buf);
  end;
end;

We first set the buffer to the required size using TPJResourceEntry's DataSize property (The Data property's own Size property also gives this information). We now ensure the data stream is positioned at the start (you can't assume this!) and then read al the data into the buffer using TStream's ReadBuffer method, and finally reposition the stream again ready for the next use. Having processed the data in the buffer in some way we finally free the buffer.

It may be more convenient to copy the data stream to another stream. The next example illustrates this by storing the resource data in a file named 'ResEntry.dat':

var
  Entry: TPJResourceEntry;
  FS: TFileStream;
begin
  ...
  // Make sure Entry references a resource object
  ...
  // Open stream onto new file
  FS := TFileStream.Create('ResEntry.dat', fmCreate);
  try
    // Copy resource data to file
    FS.CopyFrom(Entry.Data, 0);
    Entry.Data.Position := 0;
  finally
    // Close the file
    FS.Free;
  end;
end;

Here we first open a stream onto a new file. We then use TStream's CopyFrom method to copy the whole of the resource data to the file stream. Note by specifying a size of 0 to the CopyFrom method, TStream automatically positions the resource data stream to the start and copies the whole stream, so we don't need to position it first. Once again we reset the resource data stream once we are done.

Deleting data

It is very easy to delete all the data in a resource: simply set the data stream's size to 0 as follows.

var
  Entry: TPJResourceEntry;
begin
  ...
  // Make sure Entry references a resource object
  ...
  Entry.Data.Size := 0;
  ...
end;

Note that you must set the Size property of the entry's Data property here: you can't set the DataSize property since it is read only.

Writing data

We can add data to an exisiting resource simply. Let's first look at how to overwrite the existing data and then show how to append data to an existing resource. For the purposes of this example, assume we have a user defined resource that stores some plain text. Again, Entry is a TPJResourceEntry object that references our resource. We will replace any existing data with the text "Hello World".

var
  Entry: TPJResourceEntry;
  Text: string;
begin
  ...
  // Make sure Entry references a resource object
  ...
  // Delete any existing data
  Entry.Data.Size := 0;
  // Write the required text
  Text := 'Hello World';
  Entry.Data.WriteBuffer(Text[1], Length(Text));
  // Position data ready for reading
  Entry.Data.Position := 0;
  ...
end;

The only point of note here is the use of one of the normal idioms is writing a string to a stream: we can't just do WriteBuffer(Text, Length(Text)), but must either specify Text[1] or PChar(Text)^. Since this is an attribute of TStream rather than the resource file code, it is outside the scope of this document to discuss why!

Now let's look at how we add more text to the end of the resource data:

var
  Entry: TPJResourceEntry;
  Text: string;
begin
  ...
  // Make sure Entry references a resource object
  ...
  // Move to end of existing data
  Entry.Data.Seek(0, soFromEnd);
  // Write new text
  Text := ' From DelphiDabbler';
  Entry.Data.WriteBuffer(Text[1], Length(Text));
  Text := #13#10'www.delphidabbler.com';
  Entry.Data.WriteBuffer(Text[1], Length(Text));
  // Reposition stream to start
  Entry.Data.Position := 0;
  ...
end;

Here we move the stream pointer to the end of the stream so the text we write is appended to any existing data. Given the two examples above the data ends up holding the following two lines of text:

Hello World from DelphiDabbler
www.delphidabbler.com

Back to top

Example 6: Adding a new resources to a file

We can add a new resource to an existing file using TPJResourceFile's AddEntry method. The new resource must be unqiuely named within the resource file (i.e. its combined resource type, name and language id must be unique) otherwise an exception will be raised. There are two versions of the AddEntry method - the first simply adds a new empty resource with zeroed header properties while the second version adds a renamed copy of an existing resource. Both versions of AddEntry take an optional language identifier. If this is not specified the resource is language neutral (LanguageID = 0).

The following code snippet adds four new resource to an existing resource file object:

  1. An empty, language neutral, RCDATA resource with ordinal name 42. The MemoryFlags property is then set to "Discardable" and its data is set to "Hello World"
  2. A new language neutral RCDATA resource that is a copy of the first entry but named FORTYTWO. This resource has the same MemoryFlags and Data.
  3. A new RCDATA resource also named FORTYTWO but with language ID of $0809. Again this resource is a copy of the first resource.
  4. A new empty RCDATA resource named 42 with language ID of $0809.
var
  ResFile: TPJResourceFile;
  Entry1, Entry2,
  Entry3, Entry4: TPJResourceEntry;
const
  s42 = 'FORTYTWO';
  ord42 = MakeIntResource(42);
  sHello = 'Hello World';
begin
  ...
  // Assume ResFile references a valid object
  ...
  // Create 1st entry: empty
  Entry1 := fResFile.AddEntry(RT_RCDATA, ord42);
  // now set mem flags and data
  Entry1.MemoryFlags := RES_MF_DISCARDABLE;
  Entry1.Data.WriteBuffer(PChar(sHello)^, Length(sHello));
  Entry1.Data.Position := 0;
  // Create 2nd entry as copy of entry 1
  Entry2 := fResFile.AddEntry(Entry1, s42);
  // Create 3rd entry as copy of entry 1 with language id
  Entry3 := fResFile.AddEntry(Entry1, s42, $0809);
  // Create 4th entry: empty with language id
  Entry4 := fResFile.AddEntry(RT_RCDATA, ord42, $0809);
  ...
  // Do something with the entries
  ...
end;

The entries we have created have the following properties:

  Entry 1 Entry 2 Entry 3 Entry 4
Type RT_RCDATA RT_RCDATA RT_RCDATA RT_RCDATA
Name 42 FORTYTWO FORTYTWO 42
LanguageID 0 0 $0809 $0809
MemoryFlags $0100 $0100 $0100 0
Data "Hello World" "Hello World" "Hello World" <empty>

Back to top

Example 7: Checking is a resource exists

We noted above that an exception is raised in a duplicate resource is added to a file. To prevent this we may need to check if a resource exists and we can do this with the EntryExists method of TPJResourceFile as follows:

var
  ResFile: TPJResourceFile;
  Entry: TPJResourceEntry;
begin
  ...
  // Assume ResFile references a valid object
  ...
  if not ResFile.EntryExists(RT_RCDATA, 'FORTYTWO', $0809) then
    Entry := ResFile.AddEntry(RT_RCDATA, 'FORTYTWO', $0809);
  ...
end;

Note that the language id parameter to EntryExists is optional and if ommitted the function checks if any resource with the given type and name exists. Furthermore, specifying nil for the resource name makes the routine check if any resource of the given type exists. So to check if a resource file contains any RCDATA resources use:

if ResFile.EntryExists(RT_RCDATA, nil) then
  // We have RCDATA resources in the file

Back to top

Example 8: Deleting resources

We can delete all resources from a resource file simply by calling the Clear method of TPJResourceFile. In addition to deleting the resources it also frees all the TPJResourceEntry instances.

var
  ResFile: TPJResourceFile;
begin
  ...
  // Assume ResFile is a valid resource file object
  ...
  // Delete all resources
  ResFile.Clear;
  ...
end;

A single resource can be deleted from the resource file using the TPJResourceFile.DeleteEntry method. This checks if the file contains the resource and deletes it if so. However, the resource entry object is not freed. While this behaviour may be useful, it is not recommended. The preferred method is simply to free the resource entry instance. Freeing a TPJResourceEntry object autmatically removes it from the resource file.

So, to remove a single resource ResEntry from the resource file use the following code:

var
  Entry: TPJResourceEntry;
begin
  ...
  // Assume ResEntry references a valid object
  ...
  // Delete the object from its resource file
  Entry.Free;
  ...
end;

Back to top

Example 9: Saving a resource file

Now that we have learned how to modify a resource file, it's time to know how to save it. We simply use the SaveToFile or SaveToStream methods of TPJResourceFile. The following code shows how to use SaveToFile:

var
  ResFile: TPJResourceFile;
begin
  // Assume ResFile references a valid object
  ...
  // Save the file to 'MyResource.res'
  ResFile.SaveToFile('MyResource.res');
end;

Back to top

Example 10: A practical example

Having given some contrived examples of most of the functionality in the PJResFile unit, let us close by presenting a useful example that uses several of the methods we have reviewed.

We will create a routine that takes a list of HTML and related files and create a resource file which has a unique resource HTML resource for each file. Such resources can be used for display in Internet Explorer, using the res:// protocol. See my article "How to create and use HTML resource files" for more information on this subject.

Here is the code of the routine:

procedure BuildHTMLResFile(const Files: TStrings;
  const ResFileName: string);
var
  ResFile: TPJResourceFile; // res file object
  Entry: TPJResourceEntry;  // a resource entry
  ResName: string;          // a resource name
  SrcFileName: string;      // a source file name
  SrcStm: TFileStream;      // source file stream
  FileIdx: Integer;         // loops thru Files
begin
  // Create new empty resource file object
  ResFile := TPJResourceFile.Create;
  try
    // Loop thru all source files
    for FileIdx := 0 to Pred(Files.Count) do
    begin
      // Record source file name
      SrcFileName := Files[FileIdx];
      // Get resource name from source name
      ResName := ExtractFileName(SrcFileName);
      // Ensure res name is not a duplicate
      while ResFile.EntryExists(
        RT_HTML, PChar(ResName), $0809
      ) do
        ResName := '_' + ResName;
      // Create new resource
      Entry := ResFile.AddEntry(
        RT_HTML, PChar(ResName), $0809
      );
      // Copy source file into resource data
      SrcStm := TFileStream.Create(SrcFileName, fmOpenRead);
      try
        Entry.Data.CopyFrom(SrcStm, 0);
        Entry.Data.Position := 0;
      finally
        SrcStm.Free;
      end;
    end;
    // Save resource file
    ResFile.SaveToFile(ResFileName);
  finally
    // Free resource file object
    ResFile.Free;
  end;
end;

This routine is passed a list of files that are to be included in the resource file (as a string list). The name of the output file is also provided. We first create a new resource file object to store the HTML resources. We then loop through all the files in the list and add a new resource for each file. The resource is named with the name of the file (file name only, no path). To ensure the resource names are not duplicated we repeatedly append underscore characters to duplicate names until they are unique. Having got a unique resource name we create a new entry with the required name and then copy the file's contents into the resource data. Finally we save the resource file and free the resource file object.

Back to top

This user guide and the software it refers to are copyright © Peter Johnson,2004. http://www.delphidabbler.com/.