2008. szeptember 24., szerda
Embedding files as resources in a Delphi executable
Problem/Question/Abstract:
This article attempts to explain how to include files inside a Delphi application as different kinds of resources, and how to manage them.
Answer:
It is possible to embed any kind of file in an executable using resource files (*.RES). Certain kinds of resources are recognized by the API and can be used directly. Others are simply taken as binary data and its up to you to use them. In this article we will see examples of both kinds.
To create the resource file we start with the source file (*.RC), for example named RESOURCES.RC, which is a simple text file that contains the resource entries (name, class and file):
sample_bmp
BITMAP
sample.bmp
sample_ico
ICON
sample.ico
sample_cur
CURSOR
sample.cur
sample_ani
ANICURSOR
sample.ani
sample_jpg
JPEG
sample.jpg
sample_wav
WAVE
sample.wav
sample_txt
TEXT
sample.txt
The names of the resources (sample_bmp, sample_ico, etc.) are arbitrary. The kind of resource may be one recognized by the APIs (BITMAP, ICON, CURSOR) or arbitrary (JPEG, WAVE, TEXT). The file names specify the files that will be included in the .RES file (and later in the .EXE).
Now we have to compile the .RC file to produce the .RES file. For that we can use the Borland Resource Compiler (brcc32.exe) that you can probably find in Delphi's BIN folder. It's a simple command-line utility that expects the name of the source file as parameter:
C:\DELPHI\P0025>brcc32 resources
Borland Resource Compiler Version 5.40
Copyright (c) 1990, 1999 Inprise Corporation. All rights reserved.
C:\DELPHI\P0025>_
To instruct the linker to embed the resource file in the executable, we use the resource file directive ($R or $RESOURCE) in our Pascal ource code:
{$R resources.res}
Loading the resources in your application is easy for the "recongnized" resources like BITMAP, ICON and CURSOR since the Windows API provides functions (LoadBitmap, LoadIcon and LoadCursor respectively) to get handles for these elements, that for example we can assign to the Handle property of the corresponding object:
Image1.Picture.Bitmap.Handle := LoadBitmap(hInstance, 'sample_bmp');
Icon.Handle := LoadIcon(hInstance, 'sample_ico');
Screen.Cursors[1] := LoadCursor(hInstance, 'sample_cur');
For more alternatives when loading image resources, see the API LoadImage.
Other resources are little bit more difficult to manage. Let's start with JPEG images. The following function uses TResourceStream to load the resource as a stream that will be loaded into a TJPEGImage object:
function GetResourceAsJpeg(const resname: string): TJPEGImage;
var
Stream: TResourceStream;
begin
Stream := TResourceStream.Create(hInstance, ResName, 'JPEG');
try
Result := TJPEGImage.Create;
Result.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
Example:
var
Jpg: TJPEGImage;
begin
// ...
Jpg := GetResourceAsJpeg('sample_jpg');
Image2.Picture.Bitmap.Assign(Jpg);
Jpg.Free;
// ...
end;
For WAV files we need a pointer to the resource loaded in memory, and for a text file we need to load a resource in a string. We can do it using TResourceStream, but let's see an example using the API:
function GetResourceAsPointer(ResName: pchar; ResType: pchar;
out Size: longword): pointer;
var
InfoBlock: HRSRC;
GlobalMemoryBlock: HGLOBAL;
begin
InfoBlock := FindResource(hInstance, resname, restype);
if InfoBlock = 0 then
raise Exception.Create(SysErrorMessage(GetLastError));
size := SizeofResource(hInstance, InfoBlock);
if size = 0 then
raise Exception.Create(SysErrorMessage(GetLastError));
GlobalMemoryBlock := LoadResource(hInstance, InfoBlock);
if GlobalMemoryBlock = 0 then
raise Exception.Create(SysErrorMessage(GetLastError));
Result := LockResource(GlobalMemoryBlock);
if Result = nil then
raise Exception.Create(SysErrorMessage(GetLastError));
end;
function GetResourceAsString(ResName: pchar; ResType: pchar): string;
var
ResData: PChar;
ResSize: Longword;
begin
ResData := GetResourceAsPointer(resname, restype, ResSize);
SetString(Result, ResData, ResSize);
end;
Sample calls:
var
sample_wav: pointer;
procedure TForm1.FormCreate(Sender: TObject);
var
size: longword;
begin
{...}
sample_wav := GetResourceAsPointer('sample_wav', 'wave', size);
Memo1.Lines.Text := GetResourceAsString('sample_txt', 'text');
end;
Once we have the wave resource loaded into memory we can play it as many times as we want by using the API sndPlaySound declared in the MMSystem unit:
procedure TForm1.Button1Click(Sender: TObject);
begin
sndPlaySound(sample_wav, SND_MEMORY or SND_NODEFAULT or SND_ASYNC);
end;
There are some resources (like fonts and animated cursors) that can't be used from memory. We necessarily have to save these resources to a temporary disk file and load them from there. The following function saves a resource to a file:
procedure SaveResourceAsFile(const ResName: string; ResType: pchar;
const FileName: string);
begin
with TResourceStream.Create(hInstance, ResName, ResType) do
try
SaveToFile(FileName);
finally
Free;
end;
end;
The following function makes use of the previous one to save a resource in a temporary file:
function SaveResourceAsTempFile(const ResName: string;
ResType: pchar): string;
begin
Result := CreateTempFile;
SaveResourceAsFile(ResName, ResType, Result);
end;
The discussion of the function CreateTempFile falls beyond the scope of this article and its implementation can be seen in the example attached to the newsletter.
The following function makes use of SaveResourceAsTempFile to save an animated-cursor resource to a temporary file, then it loads the cursor from the file with LoadImage and finally deletes the temporary file. The function returns the handle returned by LoadImage:
function GetResourceAsAniCursor(const ResName: string): HCursor;
var
CursorFile: string;
begin
CursorFile := SaveResourceAsTempFile(ResName, 'ANICURSOR');
Result := LoadImage(0, PChar(CursorFile), IMAGE_CURSOR, 0,
0, LR_DEFAULTSIZE or LR_LOADFROMFILE);
DeleteFile(CursorFile);
if Result = 0 then
raise Exception.Create(SysErrorMessage(GetLastError));
end;
Sample call:
Screen.Cursors[1] := GetResourceAsAniCursor('sample_ani');
Form1.Cursor := 1;
Well, that's it. I hope you find it useful. You can find more information about resource files in the MSDN Library.
FAQ
1. I was trying to acces some resources like a textfile for example or some variables, but i want to be able to change them and access them at any time. Is this possible??? How?
In some Windows there is an API to update resources in an executable, but the operating system won't allow you to write to an executable file that is running... Basically, take resources as read-only. The first time you can extract them from the executable, but if you need to write to them and preserve their values, I'm afraid that from them on you'll have to use separate files.
2. However I would like to point out something, sndPlaySound is reported as a windows 3.1 api wich of course is great since this makes a program degrade gracefully when run on older windows, but the windows api recommends instead the use of playsound (defined in MMSYSTEM I think) wich even has a SND_RESOURCE flag (yep direct playing from a resource)
PlaySound can play a resource, it's true, however sndPlaySond is not a Windows 3.1 API but a Windows NT 3.1 API. It's a 32-bit function and it doesn't degrade the system's performance as you suggest (actually it's very likely that it is implemented as a call to PlaySound). In certain contexts, for example a game that plays a sound too often, it won't hurt if the resource is loaded in memory instead of playing it from the file.
Copyright (c) 2001 Ernesto De Spirito
Visit: http://www.latiumsoftware.com/delphi-newsletter.php
Feliratkozás:
Megjegyzések küldése (Atom)
Nincsenek megjegyzések:
Megjegyzés küldése