Resource File

String Tables

From: "Joe C. Hecht" <jhecht@corp.borland.com> Stringtable resources are a very useful tool when your application must store a large number of strings for use at runtime. While you may be tempted to directly embed strings into your executable, using a stringtable resource offers two advantages: 1) The strings contained in the stringtable do not consume memory until they are loaded by your application. 2) Stringtables are easily edited, providing an easy path to internationaly localized versions of your application.

Stringtables are compiled into a ".res" file that is attached to your application's exe file at build time. Even after you distribute your appliaction, the stringtable contained in your application's exe file can be edited with a resource editor. My favorite resource editor is Borland's Resource Workshop that ships with the RAD pack. It can produce and edit both 16 and 32 bit resources that are self contained, standalone, or embedded in a .exe or .dll in full WYSIWYG fashion.

It's worth noting that all versions of Delphi ship with the Borland Resource Command Line Compiler (BRCC.EXE and BRCC32.EXE), and can be found in Delphi's Bin directory.

For our example, we will build an internationalized application that displays two buttons. The buttons will have captions for "Yes" and "No" presented in English, Spanish, and Swedish.

It's worth noting that if you want to build international applications using Delphi, you should take a look at Borland's Delphi Translation Suite and Language Pack software. These packages can make porting your application to international markets a snap!

Example: We first must create a text file containing our string resources in the applications build directory. You may name the file anything you wish, so long as it has the file extension ".rc" and the filename without the extension is not the same as any unit or project filename. This is very important, as Delphi also will create a number of resource files for your project automatically.

Here is the contents of the .rc file for our example. It contains the words "Yes" and "No" in English, Spanish, and Swedish:

STRINGTABLE
{
 1, "&Yes"
 2, "&No"
 17, "&Si"
 18, "&No"
 33, "&Ja"
 34, "&Nej"
}
The file starts with the key word stringtable denoting that a string table resource will follow. Enclosed in the curly braces are the strings. Each string is listed by it's index identifier, followed by the actual string data in quotes. Each string may contain up to 255 characters. If you need to use a non-standard character, insert the character as a backslash character followed by the octal number of the character you wish to insert. The only exception is when you want to embed a backslash character, you will need to use two backslashes. Here are two examples:

1, "A two\012line string"

2, "c:\\Borland\\Delphi"
The Index numbers that you use are not important to the resource compiler. You should keep in mind that string tables are loaded into memory in 16 string segments.

To compile the .rc file to a .res file that can be linked with your application, simply type on the dos command line the full path to the resource compiler, and the full path to the name of the .rc file to compile. Here is an example:

c:\Delphi\Bin\brcc32.exe c:\Delphi\strtbl32.rc
When the compiler is finished, you should have a new file with the same name as the .rc file you've compiled, only with an extension of ".res".

You can link the resource file with your application simply by adding the following statement to your application's code, substituting the name of your resource file:

{$R ResFileName.RES}

Once the .res file is linked to your program, you can load the resource from any module, even if you specified the $R directive in the implementation section of a different unit.

Here is an example of using the Windows API function LoadString(), to load the third string contained in a string resource into a character array:

  if LoadString(hInstance,
                3,
                @a,
                sizeof(a)) <> 0 then ....
In this example, the LoadString() function accepts the hInstance of the module containing the resource, the string index to load, the address of the character array to load the string to, and the size of the character array. The LoadString function returns the number of characters that where actually loaded not including the null terminator. Be aware that this can differ from the number of bytes loaded when using unicode.

Here is a complete example of creating an international application with Borland's Delphi. The application is compatible with both 16 and 32 bit versions of Delphi.

To do this, you will need to create two identical .rc files, one for the 16 bit version, and the other for the 32 bit version, since the resources needed for each platform are different. In this example. we will create one file named STRTBL16.rc and another called STRTBL32.rc. Compile the STRTBL16.rc file using the BRCC.exe compiler found in Delphi 1.0's bin directory, and compile STRTBL32.rc using the BRCC32.exe compiler found in Delphi 2.0's bin directory.

We have taken into account the language that Windows is currently using at runtime. The method for getting this information differs under 16 and 32 bit Windows. To make the code more consistant, we have borrowed the language constants from the Windows.pas file used in 32 bit versions of Delphi.


{$IFDEF WIN32}
   {$R STRTBL32.RES}
{$ELSE}
   {$R STRTBL16.RES}
   const LANG_ENGLISH = $09;
   const LANG_SPANISH = $0a;
   const LANG_SWEDISH = $1d;
{$ENDIF}


function GetLanguage : word;
{$IFDEF WIN32}
{$ELSE}
  var
    s : string;
    i : integer;
{$ENDIF}
begin
{$IFDEF WIN32}
  GetLanguage := GetUserDefaultLangID and $3ff;
{$ELSE}
  s[0] := Char(GetProfileString('intl',
                                'sLanguage',
                                'none',
                                @s[1],
                                sizeof(s)-2));
  for i := 1 to length(s) do
    s[i] := UpCase(s[i]);
  if s = 'ENU' then GetLanguage := LANG_ENGLISH else
  if s = 'ESN' then GetLanguage := LANG_SPANISH else
  if s = 'SVE' then GetLanguage := LANG_SWEDISH else
    GetLanguage := LANG_ENGLISH;
{$ENDIF}
end;


procedure TForm1.FormCreate(Sender: TObject);
var
  a : array[0..255] of char;
  StrTblOfs : integer;
begin

 {Get the current language and stringtable offset}
  case GetLanguage of
    LANG_ENGLISH : StrTblOfs := 0;
    LANG_SPANISH : StrTblOfs := 16;
    LANG_SWEDISH : StrTblOfs := 32;
   else
    StrTblOfs := 0;
  end;

 {Load language dependent "Yes" and set the button caption}
  if LoadString(hInstance,
                StrTblOfs + 1,
                @a,
                sizeof(a)) <> 0 then
    Button1.Caption := StrPas(a);

 {Load language dependent "No" and set the button caption}
  if LoadString(hInstance,
                StrTblOfs + 2,
                @a,
                sizeof(a)) <> 0 then
    Button2.Caption := StrPas(a);
end;


Please email me and tell me if you liked this page.

This page has been created with