HomePage Delphi Library Shopping
Resource Management Step by Step
This article shows how to create a resource file and use it from within your Delphi App.It creates a resource file containing an AVI,a string,a bitmap and a sound.
1.Create a new directory as C:\Res.
2.Copy RW32CORE.DLL and BRCC32.exe from Delphi Dir/Bin to C:\Res
3.Create a bitmap(Bitmap1.bmp) ,A Wave file (Engine.wav),An AVI File(MyAVI.avi) and
a Text File with Notepad (Test.txt and in this file write a string for example Hello.).
And Copy all these file to C:\Res.
4.Open a new project and Save it As C:\Res\Project1.
5.From File/New open Text.
6.Write exactly these lines :
Bitmap_1 Bitmap "Bitmap1.bmp"
MyWavFile Wave "engine.wav"
Hello String "TEST.TXT"
AVI_1 AVI "MyAVI.avi"
You can copy and paste these lines.
7.Save this txt file as MyRes.rc.Don't forget to set Save Dialog's filter to Any File.Don't set it to Text Files.
Now you should have Project1.exe,BRCC32.exe,RW32CORE.DLL,Bitmap1.bmp,Test.txt,engine.wav, MyAVI.avi,MyRes.rc all under C:\Res directory.
8.Now open MSDOS Prompt,change directory to C:\Res and type BRCC32 MyRes.rc and then press enter.
Now MyRes.res file will be created under C:\Res.
9.Return to your project's unit and add {$R MyRes.res} after {$R *.DFM} directive.
It will look like this:
{$R *.DFM}
{$R MyRes.res}
10.Now Run your project to recreate your exe so that it will link MyRes.res to itself..
11.Now project1.exe contains all the resource files.
If you want to check it open Resxplor.exe from Delphi Dir/Demos/Resxplor Directory.From File/Open open your project's exe project1.exe and you will see your resources listed.
12.Now you can use your resources by calling apropos API calls.
13.Place an Image,an Animate and four Buttons to your form.
OnClick events are as follows:
procedure TForm1.Button1Click(Sender: TObject);
begin
Animate1.ResHandle := 0;
Animate1.ResName := 'AVI_1';
Animate1.Active := True;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Image1.Picture.Bitmap.Handle:=LoadBitmap(HInstance,'Bitmap_1');
end;
To use PlaySound add MMSystem to your uses clause;
procedure TForm1.Button3Click(Sender: TObject);
begin
PlaySound(('MyWavFile'),HInstance,snd_resource or snd_sync);
end;
procedure TForm1.Button4Click(Sender: TObject);
var
hRes : THandle; {handle to the resource}
pRes : pointer; {pointer to the resource}
ResSize : longint; {aligned size of the resource}
i : integer; {counting variable}
{$IFDEF WIN32}
s : string; {a string to play with}
{$ELSE}
// s : string; {a string to play with}
{$ENDIF}
begin
{find the resource}
hRes := FindResource(hInstance, 'Hello', 'String');
if hRes = 0 then begin
ShowMessage('Could not find the resource');
exit;
end;
{get the aligned size of the resource}
ResSize := SizeOfResource(hinstance, hRes);
if ResSize = 0 then begin
ShowMessage('Nothing to load - size = 0');
Exit;
end;
{load the resource}
hRes := LoadResource(hInstance, hRes);
if hRes = 0 then begin
ShowMessage('Resource Load Failure');
Exit;
end;
{Get a pointer to the resource}
pRes := LockResource(hRes);
if pRes = nil then begin
ShowMessage('Resource Lock Failure');
FreeResource(hRes);
Exit;
end;
{convert the resource pointer to a string}
s:='';
i := 0;
while pChar(pRes)[i] <> '' do begin
s := s + pChar(pRes)[i];
inc(i);
end;
{prove it works}
ShowMessage(s); // show the string in the test.txt
{unlock and free the resource}
UnLockResource(hRes);
FreeResource(hRes);
end;
Written by CEVAHÝR PARLAK
http://www.macrotech.bigstep.com
Copyright (c) by Bob Swart
Programming in Delphi is fun. It can also be dangerous, especially when it comes to freeing memory and resources. Although Delphi has a powerful mechanism of safeguarding the allocation and deallocation of memory and resources using a try-finally block, it's still up to the programming to do so. And in case you forget to deallocate a component, you'll lose memory and especially resources fast. How can we be sure our applications don't leak? We need a resource monitor, to watch things behind our back, and report the percentage of free resources to us. In this article, we'll design and implement a simple resource monitor that will be able to assist us in watching resource and memory leaks.
Free Resources
function GetFreeSystemResources(SysResource: Word): Word;
Where the SysResource parameter specifies the type of resource to be checked: GFSR_SYSTEMRESOURCES for the percentage of free space for system resources; GFSR_GDIRESOURCES for the percentage of free space for GDI resources (device-context handles, brushes, pens, regions, fonts, and bitmaps) or GFSR_USERRESOURCES for the percentage of free space for USER resources (window and menu handles).
Free Memory
Resource Monitor: Design
The source code for the form declaration is as follows:
unitMonform; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; Type TFormResourceMonitor = class(TForm) GroupBoxMemory: TGroupBox; GroupBoxResources: TGroupBox; LabelMemAvail: TLabel; LabelBaseMem: TLabel; LabelFreeSystemResources: TLabel; LabelFreeUserResources: TLabel; LabelFreeGDIResources: TLabel; Timer1: TTimer; X: TLabel; procedure Timer1Timer(Sender: TObject); private { Private declarations } public { Public declarations } end; var FormResourceMonitor: TFormResourceMonitor;
Resource Monitor: Implementation
implementation usesToolHelp; {$R *.DFM} procedure TFormResourceMonitor.Timer1Timer(Sender: TObject); var Tmp: String; BaseMem: LongInt; begin { free memory } FmtStr(Tmp,'MemAvail: %d KBytes',[MemAvail div 1024]); LabelMemAvail.Caption := Tmp; FmtStr(Tmp,'Base Mem: %d KBytes (%d)',[GetBaseMem div 1024,GetBaseMem]); LabelBaseMem.Caption := Tmp; { free resources } FmtStr(Tmp,'Free System Resources: %d%%', [GetFreeSystemResources(gfsr_SystemResources)]); LabelFreeSystemResources.Caption := Tmp; FmtStr(Tmp,'Free User Resources: %d%%', [GetFreeSystemResources(gfsr_UserResources)]); LabelFreeUserResources.Caption := Tmp; FmtStr(Tmp,'Free GDI Resources: %d%%', [GetFreeSystemResources(gfsr_GDIResources)]); LabelFreeGDIResources.Caption := Tmp; { show that we're still alive and running... } if (X.Caption = 'x') then X.Caption := '+' else X.Caption := 'x'; end; end.
Note that the GetBaseMem function (not displayed here) is adapted from Matt Pietrek's "Below1mb.C" as it appeared in July 94 Microsoft Developer's Network CD.
Result
If you look carefully to the screenshot of the Resource Monitor in action, you'll see a tiny '+' sign at the bottom right part of the form. If you run the application, this '+' sign will seem to change in a 'x' and back at every update of the form. I've included this to ensure (or proof) that the application is actually updating the resource and memory information. In the listing above you already saw the last two lines of procedure Timer1Timer that are responsible for this behaviour.
A Resource Monitor for Delphi -- more --
Last time in DDJ...
Little Brother
Out of my way...
procedure TFormResourceMonitor.FormCreate(Sender: TObject);
begin
Left := GetSystemMetrics(SM_CXFULLSCREEN) - Width;
Top := GetSystemMetrics(SM_CYFULLSCREEN) + GetSystemMetrics(SM_CYCAPTION) - Height;
end {FormCreate};
When a third one claimed he prefered the ResMon to start in the upper right corner of the screen, I decided to let them figure it out by themselves, and enable ResMon to save it's position at closedown in a RESMON.INI file, to be able re-read and use this position again at startup. Again, a tiny modification in FormCreate and FormDestroy:
procedure TFormResourceMonitor.FormCreate(Sender: TObject);
var INI: TIniFile;
begin
INI := TIniFile.Create(ChangeFileExt(ParamStr(0),'.INI'));
Top := INI.ReadInteger('window','WindowOrg.Y',0);
Left := INI.ReadInteger('window','WindowOrg.X',0);
INI.Free
end {FormCreate};
procedure TFormResourceMonitor.FormDestroy(Sender: TObject);
var INI: TIniFile;
begin
INI := TIniFile.Create(ChangeFileExt(ParamStr(0),'.INI'));
INI.WriteInteger('window','WindowOrg.Y',Top);
INI.WriteInteger('window','WindowOrg.X',Left);
INI.Free
end {FormDestroy};
You saw me using ParamStr(0) to get the base name of the application. This is a convinient and documented way to get the filename (there are other ways, but I prefer this one). Changing the extention of this file to .INI and we have an instant ini-filename next to our executable!
The third parameter to the INI.ReadInteger functions specify the default value if the inifile (or the specified section) does not exist yet. This yields a default position for ResMon at the upper left corner, but as soon as the user moves it to another position and closes the application, the inifile is updated, and this last position will be used for the next startup.
Don't lose your head!
procedure TFormResourceMonitor.CreateParams(var Params: TCreateParams);
{ if you remove the SW_CAPTION from the Style, you can elimiate the title bar }
begin
inherited CreateParams(Params);
with Params do Style := Style AND NOT WS_CAPTION
end {CreateParams};
However, this does not have the desired effect unless you have given the BorderStyle property the value bsNone. With a value of bsDialog or any other value, the caption just remains where it is. Furthermore, you will get the effct at run-time only, and not at design time (so at design time you still see the caption). Personally, I feel that if we use a BorderStyle of bsNone, the form looks a little lost, like the figure below, but you get used to it after a while:
I said move out of my way!
Actually, we can cheat by adding a message handler for the WM_NCHITEST message and return HTCAPTION instead of HTCLIENT. That way, Windows will think it's on the caption, when in fact it's on the client area. That way, we can move the window by just clicking anywhere in it and dragging it away. The declaration for the WM_NCHITEST message handler (inside the TFormResourceMonitor) is as follows:
procedure WMNCHitText(var Msg: TWMNCHitTest); message WM_NCHITTEST;
The implementation is just as easy, just return HTCAPTION if Windows thinks it's HTCLIENT:
procedure TFormResourceMonitor.WMNCHitText(var Msg: TWMNCHitTest);
begin
inherited;
if Msg.Result = HTCLIENT then Msg.Result := HTCAPTION
end {WMNCHitText};
The final touch
Source Code
unitMonform; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, INIFiles; Type TFormResourceMonitor = class(TForm) Timer1: TTimer; Bevel1: TBevel; LabelMemAvail: TLabel; LabelFreeResources: TLabel; X: TLabel; ColorDialog1: TColorDialog; procedure Timer1Timer(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormKeyPress(Sender: TObject; var Key: Char); private { Private declarations } procedure CreateParams(var Params: TCreateParams); override; procedure WMNCHitText(var Msg: TWMNCHitTest); message WM_NCHITTEST; public { Public declarations } end; var FormResourceMonitor: TFormResourceMonitor; implementation uses ToolHelp; {$R *.DFM} function GetBaseMem: LongInt; { Code for this routine adapted from Matt Pietrek's "Below1mb.c" Oct 93 Microsoft Systems Journal, as it appeared in July 94 Microsoft Developer's Network CD } var EndOfBlock,EndOfMem,Total: LongInt; GE: TGlobalEntry; RC: Bool; begin Result := 0; EndOfBlock := $7FFFFFFF; GE.dwSize := Sizeof(GE); RC := GlobalFirst(@GE, Global_All); with GE do begin while RC do begin dwAddress := dwAddress AND $7FFFFFFF; if dwAddress > $100000 then begin dwAddress := EndOfMem; hBlock := $FFFF; wType := GT_Sentinel end; if (wType <> gt_Free) AND (hBlock <> 0) AND (((hBlock AND 1) <> 0) OR (wcLock <> 0) OR (wcPageLock <> 0)) then begin if EndOfBlock < dwAddress then Inc(Result, dwAddress - EndOfBlock); if hBlock = $FFFF then Break; EndOfBlock := dwAddress + dwBlockSize end; EndOfMem := dwAddress + dwBlockSize; RC := GlobalNext(@GE, Global_All) end end end {GetBaseMem}; procedure TFormResourceMonitor.Timer1Timer(Sender: TObject); var BaseMem: LongInt; begin { free memory } BaseMem := GetBaseMem; LabelMemAvail.Caption := Format('Mem: %d Kb (Base: %d)',[MemAvail div 1024,BaseMem]); { free resources } LabelFreeResources.Caption := Format('System: %d%% User: %d%% GDI: %d%%', [GetFreeSystemResources(gfsr_SystemResources), GetFreeSystemResources(gfsr_UserResources), GetFreeSystemResources(gfsr_GDIResources)]); { show that we're still alive and running... } if X.Caption = 'x' then X.Caption := '+' else X.Caption := 'x' end; procedure TFormResourceMonitor.FormCreate(Sender: TObject); var INI: TIniFile; begin INI := TIniFile.Create(ChangeFileExt(ParamStr(0),'.INI')); Top := INI.ReadInteger('window','WindowOrg.Y',0); Left := INI.ReadInteger('window','WindowOrg.X',0); Color := INI.ReadInteger('window','Colour',clTeal); INI.Free end; procedure TFormResourceMonitor.FormDestroy(Sender: TObject); var INI: TIniFile; begin INI := TIniFile.Create(ChangeFileExt(ParamStr(0),'.INI')); INI.WriteInteger('window','WindowOrg.Y',Top); INI.WriteInteger('window','WindowOrg.X',Left); INI.WriteInteger('window','Colour',Color); INI.Free end; procedure TFormResourceMonitor.CreateParams(var Params: TCreateParams); { if you remove the SW_CAPTION from the Style, you can elimiate the title bar } begin inherited CreateParams(Params); with Params do Style := Style AND NOT WS_CAPTION end {CreateParams}; procedure TFormResourceMonitor.WMNCHitText(var Msg: TWMNCHitTest); { Note that you won't be able to *move* the resulting window. However, if you add another message handler for the "WM_NCHITEST" message, you can make the window movable by "grabbing" anywhere in its client area... } begin inherited; if Msg.Result = HTCLIENT then Msg.Result := HTCAPTION end {WMNCHitText}; procedure TFormResourceMonitor.FormKeyPress(Sender: TObject; var Key: Char); begin ColorDialog1.Color := Color { start with old color }; if ColorDialog1.Execute then Color := ColorDialog1.Color end; end.
Form Layout
objectFormResourceMonitor: TFormResourceMonitor Left = 221 Top = 164 Hint = 'Dr.Bob'#39's Resource Monitor (Esc = colour)' BorderIcons = [biSystemMenu, biMinimize] BorderStyle = bsNone ClientHeight = 29 ClientWidth = 206 Color = clTeal Font.Color = clBlack Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [fsBold] FormStyle = fsStayOnTop PixelsPerInch = 96 ShowHint = True OnCreate = FormCreate OnDestroy = FormDestroy OnKeyPress = FormKeyPress TextHeight = 13 object Bevel1: TBevel Left = 0 Top = 0 Width = 206 Height = 29 Align = alClient ParentShowHint = False ShowHint = False Style = bsRaised end object LabelMemAvail: TLabel Left = 2 Top = 1 Width = 86 Height = 13 Caption = 'LabelMemAvail' ParentShowHint = False ShowHint = False end object LabelFreeResources: TLabel Left = 2 Top = 14 Width = 117 Height = 13 Caption = 'LabelFreeResources' ParentShowHint = False ShowHint = False end object X: TLabel Left = 192 Top = 1 Width = 9 Height = 13 Caption = 'X' ParentShowHint = False ShowHint = False end object Timer1: TTimer OnTimer = Timer1Timer Left = 144 end object ColorDialog1: TColorDialog Left = 120 end end
If you have any more wishes for ResMon, don't hesitate to ask. As you've seen by now, with Delphi we're only limited by our imagination (and Windows itself).
You can download the executable version of
RESMON (82,750 bytes) to see it work for yourself.This webpage (c) 2000 by webmaster drs. Robert E. Swart (aka
Dr.Bob - www.drbob42.com). All Rights Reserved.Creating a Resource only DLL
FAQ1595D.txt Creating a Resource only DLL
Category :Miscellaneous
Platform :All
Product :All 32 bit
Question:
How do I create a Resource only DLL?
Answer:
Create and build an empty DLL project, that contains a resource
link reference to the .res file that contains your resources.
Example:
library ResTest;
uses
SysUtils;
{$R MYRES.RES}
begin
end.
To use your resource only DLL, simply load the dll and the
resources
you wish to use:
Example:
{$IFDEF WIN32}
const BadDllLoad = 0;
{$ELSE}
const BadDllLoad = 32;
{$ENDIF}
procedure TForm1.Button1Click(Sender: TObject);
var
h : THandle;
Icon : THandle;
begin
h := LoadLibrary('RESTEST.DLL');
if h <= BadDllLoad then
ShowMessage('Bad Dll Load')
else begin
Icon := LoadIcon(h, 'ICON_1');
DrawIcon(Form1.Canvas.Handle, 10, 10, Icon);
FreeLibrary(h);
end;
end;
Creating Resource Files
This is helpful if you do not want to have a wave file in the directory of the program you distribute. One problem with inserting a wave file into your .exe is that it makes your programs .exe larger, so it is a good idea if your program uses small wave files or maybe one or two large wave files.
The Delphi example below should work on Delphi version above Delphi 2.
First open an editor like Notepad.exe, for this example we will use a wave file called Laser.wav
and this is the wave file we will insert into our programs .exe
Type these words into the text file:
TheLaser Wave "Laser.wav"
Then save the text file as:
mywave.rc
We will use BRCC32.exe that comes with Delphi, it should be in Delphi's Bin directory to compile the file.
In order to use BRCC32.exe you have to use a DOS window, using DOS is not hard at all.
Whenever you use DOS all you have to do, to get back to Windows (Your desktop) is type the word exit, then press the Enter key.
You should have your wave files and your .rc file in the same folder/directory.
So we will type this sentance in the DOS window:
(We are in the c:\Delphi 3\sounds directory)
c:\Delphi 3\sounds>BRCC32 MYWAVE.RC
If this does not work or you get an error, you may not have Delphi in your computers path.
A way around this is for you to copy the BRCC32.exe and RW32CORE.DLL into your folder/directory that you are using, then try again.
(The above files, BRCC32.exe and RW32CORE.DLL, may be different, for different versions of Delphi)
Use Explorer or another File Manager and have a look in your directory, you should find that you have an extra file called mywave.RES.
Start Delphi, then start a new project (Application), add MMSystem in the Uses clause like this:
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, MMSystem;
You cannot play sounds without the MMSystem unit, in other words PlaySound will not work without the MMSystem unit.
Look for {$R *.DFM} in Unit1 and add {$R MYWAVE.RES} next to it.
Next drop a Button onto your Form, double-click on the Button and add this code:
PlaySound(PChar('TheLaser'), hInstance, snd_Sync or snd_Resource);
Note we use 'TheLaser' to call the wave file.
Run the program and then click on the Button, you should now here the Laser.wav file.
Your complete unit should look like this:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, MMSystem;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var Form1: TForm1;
implementation
{$R *.DFM} {$R MYWAVE.RES}
procedure TForm1.Button1Click(Sender: TObject);
begin
PlaySound(PChar('TheLaser'), hInstance, snd_Sync or snd_Resource);
Loading Bitmaps and Cursors from RES files
Bitmaps and cursors can be stored in a resource (RES) files and linked into your application's EXE file. RES files can be created
with Delphi's Image Editor or Borland's Resource Workshop that comes with the Delphi RAD Pack. Bitmaps and cursors stored in RES files
(after being bound into an EXE or DLL) can be retrieved by using the API functions LoadBitmap and LoadCursor, respectively.
Loading Bitmaps
The LoadBitmap API call is defined as follows:
function LoadBitmap(Instance: THandle;
BitmapName: PChar): HBitmap;
The first parameter is the instance handle of the module (EXE or DLL) that contains the RES file you wish to get a resource from. Delphi
provides the instance handle of the EXE running in the global variable called Hinstance. For this example it is assumed that the module that
you are trying to load the bitmap from is your application. However, the module could be another EXE or DLL file. The following example
loads a bitmap called BITMAP_1 from a RES file linked into the application's EXE:
procedure TForm1.Button1Click(Sender: TObject);
var
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
Bmp.Handle := LoadBitmap(HInstance,'BITMAP_1');
Canvas.Draw(0, 0, Bmp);
Bmp.Free;
end;
There is one drawback to using the LoadBitmap API call though LoadBitmap is a Windows 3.0 API call and loads in bitmaps only as
DDBs (Device Dependent Bitmaps). This can cause color palette problems when retrieving DIBs (Device Independent Bitmaps) from
RES files. The code listed below can be used to retrieve DIBs from RES files. This code loads the bitmap as a generic resource, puts
it into a stream, and then does a LoadFromStream call which causes Delphi to realize the color palette automatically.
procedure TForm1.Button1Click(Sender: TObject);
const
BM = $4D42; {Bitmap type identifier}
var
Bmp: TBitmap;
BMF: TBitmapFileHeader;
HResInfo: THandle;
MemHandle: THandle;
Stream: TMemoryStream;
ResPtr: PByte;
ResSize: Longint;
begin
BMF.bfType := BM;
{Find, Load, and Lock the Resource containing BITMAP_1}
HResInfo := FindResource(HInstance, 'BITMAP_1', RT_Bitmap);
MemHandle := LoadResource(HInstance, HResInfo);
ResPtr := LockResource(MemHandle);
{Create a Memory stream, set its size, write out the bitmap header, and finally write out the Bitmap }
Stream := TMemoryStream.Create;
ResSize := SizeofResource(HInstance, HResInfo);
Stream.SetSize(ResSize + SizeOf(BMF));
Stream.Write(BMF, SizeOf(BMF));
Stream.Write(ResPtr^, ResSize);
{Free the resource and reset the stream to offset 0}
FreeResource(MemHandle);
Stream.Seek(0, 0);
{Create the TBitmap and load the image from the MemoryStream}
Bmp := TBitmap.Create;
Bmp.LoadFromStream(Stream);
Canvas.Draw(0, 0, Bmp);
Bmp.Free;
Stream.Free;
end;
Loading Cursors
The LoadCursor API call is defined as follows:
function LoadCursor(Instance: THandle;
CursorName: PChar): HCursor;
The first parameter is the Instance variable of the module that contains the RES file. As above, this example assumes that the
module that you are trying to load the cursor from is your application. The second parameter is the name of the cursor.
Under the interface section declare:
const
crMyCursor = 5; {Other units can use this constant}
Next, add the following two lines of code to the form's OnCreate event as follows:
procedure TForm1.FormCreate(Sender: TObject);
begin
Screen.Cursors[crMyCursor] := LoadCursor(HInstance, 'CURSOR_1');
Cursor := crMyCursor;
end;
or you may want to change one of the standard Delphi cursors as follows (the Cursor constants can be found in the On-line Help
under Cursors Property):
procedure TForm1.FormCreate(Sender: TObject);
begin
{This example changes the SQL Hourglass cursor}
Screen.Cursors[crSQLWait] := LoadCursor(HInstance, 'CURSOR_1');
end;
Note: Normally it is necessary to delete any cursor resources with the DeleteCursor, however, in Delphi this is not necessary because
Delphi will delete the all cursors in the Cursors array.