Skip over navigation

How to store files inside an executable program

Why do it

Have you ever needed to distribute one or more critical data files with a program? Often only your program needs to access the data file(s) and they don't need to be changed by it. How do we stop users from deleting the files? One answer is to store the data file inside our executable (or in a DLL) as a custom (RCDATA) resource and to link the resource into our application using the {$R} directive.

This article shows how to create a resource file containing a copy of any file.

How it's done

The resource file we're going to create has the following format:

  • a header that introduces the file
  • a header for our RCDATA resource
  • the data itself - an RCDATA resource is simply a sequence of bytes
  • any padding required so that any following resource begins on a DWORD boundary

It's much simpler to create a resource file that is identified by an ordinal (e.g. 200) than it is to create one identified by a string (e.g. 'MY_RESOURCE'), since the resource header records are a fixed size in the first case and are variable in the second case. We will only consider the first case here. We will also just copy one file into the resource – it's simple to extend this to more than one.

Because we're sticking with ordinal IDs the resource header can be defined as:

  1type
  2  TResHeader = record
  3    DataSize: DWORD;        // size of our data      
  4    HeaderSize: DWORD;      // size of this record
  5    ResType: DWORD;         // lo word = $FFFF => ordinal
  6    ResId: DWORD;           // lo word = $FFFF => ordinal
  7    DataVersion: DWORD;     // *
  8    MemoryFlags: WORD;
  9    LanguageId: WORD;       // *
 10    Version: DWORD;         // *
 11    Characteristics: DWORD; // *
 12  end;
Listing 1

We will not be using the fields marked *.

Here's the code that creates the resource file and copies in a given file:

  1procedure CreateResourceFile(
  2  DataFile, ResFile: string;  // file names
  3  ResID: Integer              // id of resource
  4);
  5var
  6  FS, RS: TFileStream;
  7  FileHeader, ResHeader: TResHeader;
  8  Padding: array[0..SizeOf(DWORD)-1] of Byte;
  9begin
 10
 11  { Open input file and create resource file }
 12  FS := TFileStream.Create(  // to read data file
 13    DataFile, fmOpenRead);
 14  RS := TFileStream.Create(  // to write res file
 15    ResFile, fmCreate);
 16
 17  { Create res file header - all zeros except
 18    for HeaderSize, ResType and ResID }
 19  FillChar(FileHeader, SizeOf(FileHeader), #0);
 20  FileHeader.HeaderSize := SizeOf(FileHeader);
 21  FileHeader.ResId := $0000FFFF;
 22  FileHeader.ResType := $0000FFFF;
 23
 24  { Create data header for RC_DATA file 
 25    NOTE: to create more than one resource just
 26    repeat the following process, using a different
 27    resource ID each time }
 28  FillChar(ResHeader, SizeOf(ResHeader), #0);
 29  ResHeader.HeaderSize := SizeOf(ResHeader);
 30  // resource id - FFFF says "not a string!"
 31  ResHeader.ResId := $0000FFFF or (ResId shl 16);
 32  // resource type - RT_RCDATA (from Windows unit)
 33  ResHeader.ResType := $0000FFFF
 34    or (WORD(RT_RCDATA) shl 16);
 35  // data file size is size of file
 36  ResHeader.DataSize := FS.Size;
 37  // set required memory flags 
 38  ResHeader.MemoryFlags := $0030;
 39
 40  { Write the headers to the resource file }
 41  RS.WriteBuffer(FileHeader, SizeOf(FileHeader));
 42  RS.WriteBuffer(ResHeader, SizeOf(ResHeader));
 43
 44  { Copy the file into the resource }
 45  RS.CopyFrom(FS, FS.Size);
 46
 47  { Pad data out to DWORD boundary - any old 
 48    rubbish will do!}
 49  if FS.Size mod SizeOf(DWORD) <> 0 then
 50    RS.WriteBuffer(Padding, SizeOf(DWORD) -
 51      FS.Size mod SizeOf(DWORD));
 52
 53  { Close the files }
 54  FS.Free;
 55  RS.Free;
 56end;
Listing 2

The above code should be sufficient to illustrate the problem, but it is not very elegant – and the streams should be protected by try … finally blocks. A better solution is to create a class that encapsulates the code. A further improvement would be to permit either strings or ordinals to be used to identify the resource.

On occasion you may want to write formatted data to the resource file rather than just copy a file – this is easy to do. You need to do five things:

  1. Write a place-holder header record for your resource and record its position in the stream.
  2. Write the formatted data to the file (replace the code that copies the file with code that writes the data).
  3. Keep a record of the size of the data you are writing.
  4. Pad the data out to a DWORD boundary.
  5. Store the length of data (excluding padding) in your header record, return to the position of the place-holder.

Of course there's now the problem of getting the file information back out of the executable! This is quite a trivial process and is dealt with in another article.

Demo Code

A demo program to accompany this article (and the related article) can be found in the delphidabbler/article-demos Git repository on GitHub.

You can view the code in the article-02+03 sub-directory. Alternatively download a zip file containing all the available demos by going to the repository's landing page and clicking the Clone or download button and selecting Download ZIP.

The demo demonstrates what has been described here. It contains a pair of projects. The first is a program that embeds a supplied rich text file in a resource file. The second program includes the resource file and displays the rich text in a rich edit component.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.

The demo is open source. See the demo's LICENSE.md file for licensing details.

Feedback

I hope you found this article useful.

If you have any observations, comments, or have found any errors, there are two places you can report them.

  1. For anything to do with the article content, but not the downloadable demo code, please use this website's Issues page on GitHub. Make sure you mention that the issue relates to "article #2".
  2. For bugs in the demo code see the article-demo project's README.md file for details of how to report them.