One default blank form in an application produces the following EXE size:
Delphi 2: 157,184 bytes
Delphi 3: 179,712 bytes
Delphi 4: 282,112 bytes
Delphi 5: 294,912 bytes
Delphi 6: 359,424 bytes
Delphi 7: 368,128 bytes (+134.2% against Delphi 2!!)
Frightening, isn't it? - so, how could you make your EXE files smaller?
Use CASE.. statments rather than IF..ELSE.. clauses.
In every .PAS-file that will be linked in your project, place the following line to the top of your code:
{$D-,L-,O+,Q-,R-,Y-,S-}
Also add this line to your project source (.DPR).
{$D-} will prevent placing Debug info to your code.
{$L-} will prevent placing local symbols to your code.
{$O+} will optimize your code, remove unnecessary variables etc.
{$Q-} removes code for Integer overflow-checking.
{$R-} removes code for range checking of strings, arrays etc.
{$S-} removes code for stack-checking. USE ONLY AFTER HEAVY TESTING !
{$Y-} will prevent placing smybol information to your code.
After doing this, recompile the whole project - your .EXE-size should magically have been reduced by 10-20 %..
If you link in graphics, they don't need to have more than 16 or 256 colors.
If the goal is really and only .EXE size, load your graphics by code ("manually") instead of embedding them already during design time in each and every form which uses them. A detailled description of this tip can be found on this page after clicking here.
If you include resource-files, they should only content the resources you really need - and nothing more.
If you use Delphi 1, finally run W8LOSS.EXE on your .EXE file (not required for 32bit-Apps).
Delphi 2 produces larger .EXE sizes than Delphi 1 - 32 bit code demands its tribute..
Delphi 3 produces larger .EXE sizes than Delphi 2 - main reason: market pressure leads to more "fundamental" support of "bells and whistles" - quality and usefulness/efficiency/productivity doesn't seem to be a real criteria in M$-times...
Delphi 4 produces larger .EXE sizes than Delphi 3 - main reason: market pressure leads to more "fundamental" support of "bells and whistles" - (..to be continued like above)
check the "Show Hints" and "Show Warnings" options on the Project|Options|Compiler page, then rebuild your project. It will show you every variable and proc/func that isn't being used. You might be able to trim a little there, too.
Clean your USES.. clauses for all unneeded units!
The so-called "SmartLinking" doesn't always remove all unused code. In a large project you could possibly spare 100k in the EXE by that.
Place Bitmaps/Glyphs/etc. in DLL's instead of in *.RES/*.DCR files !
If you place fx some large bitmaps in a .RES, these will compiled into the EXE. Remove the .RES-declarations, instead place them in a new project like this:
LIBRARY My_Extern_RESes;
USES EmptyUnit;
{$R MyRes1.RES}
{$R MyRes2.RES}
...
INITIALIZATION
END.
The unit AEmptyUnit is a completely empty unit. You have to use a unit like this because any other unit will place unnecessary code in the final DLL. When you want to use an image (and typically, you only want to load it once), you can do it like this :
MyHandle := LoadLibrary('My_Extern_RESes.DLL');
TButton1.Glyph.Handle := LoadBitmap(MyHandle,'My_Glyph');
Placing Glyphs, Bitmaps, String-const's etc. in DLL's can really reduce a projects EXE-size. (Tip came from David Konrad)
Basis rule: the more forms and units you add, the larger your exe becomes. When you had one huge form, you only had one form class - even when you had 500+ things on it! This creates only one runtime type information for that class. Now, when you split this into 17 units, each class in that unit requires its own RTTI. Now, this runtime information can get large, collectively, with a lot of redundant information compared to when you only had one huge class. Also, if each of the 17 units had its own form, your end product must contain resource information for all 17 of those forms even though most of that information may be redundant. One reason why this happens is because the Delphi compiler doesn't optimize resources - but I don't know of any compiler that does. So, if you had two separate forms which are identical in look, you'll have 2 copies of the resource in your .EXE.
This leaves some work for the programmer to be creative in maximizing resource reuseability. (Tip came from Young Chung)
Delphi 3 and up allows you to use packages (runtime libraries) which can be easily enabled in your Project options. Especially if you have to update your application often, this could be interesting for you: the runtime libraries just have to be deployed once (there is even a chance that important Delphi packages like vcl30.dpl or vcl40.bpl already exist on your user's computer) and to update your (then shrinked) executable whenever an update is required (please consult the helpfile for further details).
If your EXE file has to be super-small and you can go without Delphi's powerful IDE and design features, you can also program without including the VCL (visual component library) in your USES clause. Here is a small EXE program which compiles to about 30 kb (by Frank Peelo):
Program HelloWin;
{ Standard Windows API application written in Object Pascal. }
Uses
Windows,
messages,
MMSystem;
Const
AppName : pChar = 'HelloWin';
Function WindowProc(Window:HWnd; AMessage, WParam, LParam:LongInt):
LongInt; StdCall; Export;
{ The message handler for the new window }
Var
h : hdc;
ps: tPaintStruct;
r : tRect;
Begin
Result := 0;
Case AMessage of
WM_Create:Begin
PlaySound('C:\WINDOWS\MEDIA\Musica Windows Start.wav', 0,
Snd_FileName or Snd_Async);
Exit;
End;
WM_Paint: Begin
h := BeginPaint(Window, Ps);
GetClientRect(Window, r);
DrawText(h, 'Hello Winblows!', -1, r,
DT_SingleLine or DT_Center or DT_VCenter);
EndPaint(Window, ps);
Exit;
End;
WM_Destroy:Begin
PostQuitMessage(0);
Exit;
End;
End;
Result := DefWindowProc(Window, AMessage, WParam, LParam);
End;
{ Register the window class }
Function WinRegister:Boolean;
Var
WindowClass: TWndClass;
Begin
WindowClass.Style := cs_HRedraw or cs_VRedraw;
WindowClass.lpfnWndProc := @WindowProc;
WindowClass.cbClsExtra := 0;
WindowClass.cbWndExtra := 0;
WindowClass.hInstance := HInstance;
WindowClass.hIcon := LoadIcon(0, idi_Application);
WindowClass.hCursor := LoadCursor(0, idc_Arrow);
WindowClass.hbrBackground := HBrush(GetStockObject(White_Brush));
WindowClass.lpszMenuName := NIL;
WindowClass.lpszClassName := AppName;
Result := RegisterClass(WindowClass)<>0;
End;
Function WinCreate:HWnd;
Var
HWindow:HWnd;
Begin
hWindow := CreateWindow(AppName,
'The Hello Program',
WS_OverlappedWindow,
CW_UseDefault,CW_UseDefault,
CW_UseDefault,CW_UseDefault,
0,0,HInstance,NIL);
If hWindow<>0 then Begin
ShowWindow(hWindow, CmdShow);
UpdateWindow(hWindow);
End;
Result := hWindow;
End;
{ Main: Set up window, then give it messages until done.}
Var
AMessage: TMsg;
hWindow: HWnd;
Begin
If not WinRegister then Begin
MessageBox(0, 'Register Failed', NIL, mb_Ok);
Exit;
End;
hWindow := WinCreate;
If hWindow=0 then begin
MessageBox(0, 'Register Failed', NIL, mb_Ok);
Exit;
End;
While GetMessage(AMessage, 0, 0, 0) do begin
TranslateMessage(AMessage);
DispatchMessage(AMessage);
End;
Halt(AMessage.WParam);
End.
Learning how to read a detailed .map file could easily allow you to reduce code size. First you can scan it for VCL units that you no longer use, as mentioned above (e.g., you had a grid on one form in the past, but have grids on no forms now, Delphi is not smart enough to remove the reference, hence all the VCL classes in Grids unit bloat your EXE). Second you can scan it to see which units are the largest. You may find ways to specifically optimize those units for size to gain the greatest benefit.
See this link, or below for some info: Understanding Linker Generated 32-bit MAP files
A sample map file line would be:
0001:000C2340 00012E74 C=CODE S=.text G=(none) M=jpeg
Headers for this would look like:
Address Size Segment SegType SegGroup Unit
The most important part to you is the size. For example, the jpeg unit above has a size of $12E74 or about 77K! Are you using Jpeg support, or do you only have one small JPEG? Might a BMP be a more compact solution when the unit's file size is considered as part of the equation? Of course, if you have many images, JPEG is sure to pay for itself many times over (in most cases, anyway).
Also important is the SegType. '.text' segments are compiled code, '.data' segments are initialized constants and global variables, '.bss' are zero initialized variables. Both '.text' and '.data' segments use up about the same amount of space in the .EXE. '.bss' segments use virtually zero space in the .exe, but they do use the listed amount of space at run-time. (explanation by Colin P. Sarsfield given in the Borland Newsgroups)
Tip: an IDE expert is included in the Jedi Code Library which adds a new menu item to the Delphi Project menu: "Analyze Project". It creates a detailed MAP file, rebuilds the project and displays the list of used units, forms and their size! [Screenshot]
a VCL replacement project named XCL exists, linked from my "Delphi Tools" page, which could be of help here, too.
If your main goal is distributable size, you could also consider
using EXE/DLL packers like Blinker, ASPack, NeoLite etc. for program compression
or at least stripping the relocation section from Win32 PE EXE files with utilities like StripReloc
and, to shrink the complete package to a minimum: use alternative setup programs like our award-winning INF-Tool to create your distribution package. INF-Tool creates today's smallest setup packages available! Since users usually prefer to download smaller programs from the Internet (rather than to 'control' the installed program size after setup), you can focus on functionality and features instead of having to think about where to save a few code lines or if using a helpful VCL component "really pays".
(links to all mentioned programs can be found on my Delphi Tools page.)