Link to home
Start Free TrialLog in
Avatar of itamar
itamar

asked on

Small EXE challenge !

Hi all,

i need the smallest executable possible for the following task:

- Read a INI file to get a string entry in the format:
[Param]
parLastBKP=09/12/99 16:58:43

- If Now is more then 24 hours later, show a warning Windows with the parLastBKP content.

Obs: I can change the storage format of the INI parameter if it helps.

- My executable is now 71 Kb in Delphi 4. Please, just send code that generates smaller executables.

- The smallest executable wins.

- You can do anything that works in Win 9x AND NT.

- Usage of code copied from Delphi units are valid.

Thanks
Itamar
Avatar of itamar
itamar

ASKER

Edited text of question.
Avatar of itamar

ASKER

Edited text of question.
Avatar of kretzschmar
hi itamar,

first version

62 kb with d3

program little_exe;

uses
  sysutils,inifiles, windows;
{$R *.RES}

var
  IniFile : TIniFile;
  DT      : TDateTime;
  S       : String;
begin
  IniFile := TiniFile.Create('c:\Test.Ini');
  S := IniFile.ReadString('Param','parLastBKP',DateTimeToStr(now-2));
  DT := StrToDateTime(S);
  if DT+1 < now then
    windows.MessageBox(0,PChar('Last BackUp '+s),'Warning',MB_ICONWARNING or MB_OK);
end.

now more optimizing

meikl
hehe.. just my kinda challenge, ill go code right away :)
Avatar of itamar

ASKER

Edited text of question.
Im almost done.. exe size = 15.3 kb so far.. :)
Avatar of itamar

ASKER

Amazing !!!
well,
then i'm out of the challenge (d3 or d5)
brainware, let see
meikl
d3, and Using No delphi units :)
well System.Pas is impossiple to Exclude.. else im writing a Tiny header file for it.. doing a few more optimizations right now.. to skip SysUtils's DateTime funcs..
kretzschmar .. dont give up..
i do not yet know how big Final will be :) SysUtils is NASTY..
My two cents:

You could do away with IniFiles too if you use ReadPrivateProfileString instead of TIniFile.Create.....

Also, a Delphi 2 compiled exe is usually smaller than 3, 4.

And, instead of using SysUtils, just copy the implementation of StrToDateTime into your unit.

Cheers,
Phil.
hehe.. i already did that Profile Stuff :)) that was the first i did :)
EXE = 45 Kb.. but if we can copy needed DateTime Functions it can get ALOT Lower,

+ at last u could use a EXE Compression Utility if u want..
high phil,

>instead of using SysUtils, just copy the implementation of
>StrToDateTime into your unit.

thats not so easy because,
it uses functions scantime, scandate, scancha, which uses then DoDecode function and so on, that will be much code, which will be pasted then

meikl
yeah.. else it should be written as a single Assembler rutine :) tho im not much good at assembler anymore..
Avatar of itamar

ASKER

testing comment...
hmm dont give up.. im gonna grab a smoke while thinking.. i never failed to get below 20 kb in any case :)
Meikl, Yeah, I thought that might be the case after I posted the comment. Oh well, here are my new thoughts.

>>>>>
Obs: I can change the storage format of the INI parameter if it helps.
<<<<<

So instead of writing a string to the ini file in a date/time human readible format, you could just write out an integer. After all, a TDateTime is a double. (day = int part, minutes = frac part).

Since you don't need minute level precision, you could write out (Round(Now * 100.0) as an integer. When you read it in, get your time by dividing by 100.0. (Use / not div).

BTW, I say write an integer and not a double because the profile functions don't do floating point numbers. You'd have to write out the double as a string, and read in a string. And of course StrToFloat is in SysUtils!!!

Cheers,
Phil.

PS: If this doesn't make sense, I'll throw together some code for you.
Avatar of itamar

ASKER

Ok,

you can count on this format change !
well right now i almost have it working.. but what Format do u need DateTimeToStr to return?

anyhow.. the IniFormat is just fine,
that i only use 1 Function to read.


Avatar of itamar

ASKER

Show smth like Last BKP in DD/MM/AA HH:MM
Hehe.. You people have nothing better to do with your time?!  ;p

On that note, what is the trick to making small EXE's?  Mine are always quite large.. I mean, even for the smallest app the executable can me 200 or 300kb in size..

Is there something I can do to make it smaller?

-Pal
Dont use Forms.pas for first step :)
http://www.bw-soft.com/allweneed.txt

some of what i mixed so far..
all i use in Project Code..aka No Windows or SysUtils units..

But that DateTime is Pissing me off :(
But, if you need a form... You are sort of stuck using Forms.pas right?  Or can you cut and paste out of it the stuff you need and ditch the rest?
Well my way is Wo. IDE use.. not finnished the Library yet.. when done mabye i can make it work with RAD/IDE too.. but sorry to say we dont plan to make it free... Well mabye WO Source..

We are also writing support for more Win2k and Win98 Features that Inprise left out of d2,d3,d4,d5
Test...
Use ASPack in order to compress the executable.
Let me know if you need a link.

Alex
i just tryed UPX again..
16.896 -> 10.752
238.080 -> 105.472

http://www.nexus.hu/upx/

simonet. is ASPack good + Free etc?

tho im more looking for Delphi/Pascal code for such type of Compressor. its a interesting topic, and would never mind learning some about it..
sitting and watching...
Fresh Compile = 45 kb EXE, Compressed with UPX = 24.6 kb, using This code :

program Sample;

Uses
 Sysutils, Windows;

Function IniReadString(const FileName, Section, Ident, Default: string): string;
var
 Buffer: array[0..1023] of Char;
begin
  SetString(Result, Buffer, GetPrivateProfileString(PChar(Section),
  PChar(Ident), PChar(Default), Buffer, SizeOf(Buffer), PChar(FileName)));
end;

Var
 S   : String;
 DT  : TDateTime;
begin
 S := IniReadString(ExtractFilePath(ParamStr(0))+'app.ini','Param','parLastBKP',DateTimeToStr(now-2));

 DT := StrToDateTime(S);
  if DT+1 < now then
   MessageBox(0,PChar('LastBKP '+s),'Warning',MB_ICONWARNING or MB_OK);
end.

Well. I only have got D2. The following compiles to 14k (14,838 bytes, to be exact :-) in D2.

Since yo were willing to change the Format of the INI file, I assumed you'd be willing to write the 'raw' TDateTime in the file. (i read it as a string and then get the VALue).

(Note: procs & funcs copied from SysUtils.pas & 
declarations copied from Windows.pas)


program SmallEXE;

const
  DateDelta = 693594;
  MSecsPerDay = 24 * 60 * 60 * 1000;

type
  TSystemTime = record
    wYear: Word;
    wMonth: Word;
    wDayOfWeek: Word;
    wDay: Word;
    wHour: Word;
    wMinute: Word;
    wSecond: Word;
    wMilliseconds: Word;
  end;
  PDayTable = ^TDayTable;
  TDayTable = array[1..12] of Word;

var  c : array[0..255] of char;
     D : Double;
     code : integer;
     SysTime: TSystemTime;


procedure GetLocalTime(var lpSystemTime: TSystemTime)stdcall; external 'kernel32.dll' name 'GetLocalTime';
function  GetPrivateProfileString(lpAppName, lpKeyName, lpDefault: PAnsiChar; lpReturnedString: PAnsiChar; nSize: Integer; lpFileName: PAnsiChar):Integer; stdcall; external 'kernel32.DLL' name 'GetPrivateProfileStringA';
function  MessageBox(hWnd: integer; lpText, lpCaption: PChar; uType:integer): Integer; stdcall; external 'user32.dll' name 'MessageBoxA';


function GetDayTable(Year: Word): PDayTable;

const
  DayTable1: TDayTable = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  DayTable2: TDayTable = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  DayTables: array[Boolean] of PDayTable = (@DayTable1, @DayTable2);
begin
  Result := DayTables[((Year mod 4 = 0) and ((Year mod 100 <> 0) or (Year mod 400 = 0)))];
end;

function EncodeDate(Year, Month, Day: Word): Double;
var
  I: Integer;
  DayTable: PDayTable;
begin
  DayTable := GetDayTable(Year);
  if (Year >= 1) and (Year <= 9999) and (Month >= 1) and (Month <= 12) and
    (Day >= 1) and (Day <= DayTable^[Month]) then
  begin
    for I := 1 to Month - 1 do Inc(Day, DayTable^[I]);
    I := Year - 1;
    Result :=  I * 365 + I div 4 - I div 100 + I div 400 + Day - DateDelta;
  end;
end;


function Now: TDateTime;

begin
  GetLocalTime(SysTime);
  with SysTime do
     Result := EncodeDate(wYear, wMonth, wDay) +
     (wHour * 3600000 + wMinute * 60000 + wSecond * 1000 + wMilliSeconds) / MSecsPerDay;
end;


begin
GetPrivateProfileString('Param', 'parLastBKP', 'default', @C, 255, 'C:\MyINI.ini');
Val(string(c), D, Code);
if (Now - D) > 1 then MessageBox(0,PChar('Last BackUp '+ string(c)),'Warning',  0);
end.
Avatar of itamar

ASKER

Compiling...
Avatar of itamar

ASKER

Hi ahalya,

the problem with your code is that it shows smth. like

Last BackUp 36507,5012357639

As I said I can manipulate the INI file storage, so I can store the 2 formats (internal TDateTime as Double AND external) so I changed your code to:

>>>>

program ahalya;

const
   DateDelta   = 693594;
   MSecsPerDay = 24 * 60 * 60 * 1000;
   MB_ICONWARNING = $00000030;
   MB_OK = $00000000;
type
   TSystemTime = record
      wYear: Word;
      wMonth: Word;
      wDayOfWeek: Word;
      wDay: Word;
      wHour: Word;
      wMinute: Word;
      wSecond: Word;
      wMilliseconds: Word;
   end;
   PDayTable = ^TDayTable;
   TDayTable = array[1..12] of Word;

var
   C, CE       : array[0..255] of Char;
   D           : Double;
   code        : integer;
   SysTime     : TSystemTime;

procedure GetLocalTime(var lpSystemTime: TSystemTime)Stdcall; External 'kernel32.dll' Name
   'GetLocalTime';

function GetPrivateProfileString(lpAppName, lpKeyName, lpDefault: PAnsiChar; lpReturnedString:
   PAnsiChar; nSize: integer; lpFileName: PAnsiChar): integer; Stdcall; External 'kernel32.DLL' Name
   'GetPrivateProfileStringA';

function MessageBox(HWND: integer; lpText, lpCaption: PChar; uType: integer): integer; Stdcall;
   External 'user32.dll' Name 'MessageBoxA';

function GetDayTable(Year: Word): PDayTable;

const
   DayTable1   : TDayTable = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
   DayTable2   : TDayTable = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
   DayTables   : array[Boolean] of PDayTable = (@DayTable1, @DayTable2);
begin
   Result := DayTables[((Year mod 4 = 0) and ((Year mod 100 <> 0) or (Year mod 400 = 0)))];
end;

function EncodeDate(Year, Month, Day: Word): Double;
var
   i           : integer;
   DayTable    : PDayTable;
begin
   DayTable := GetDayTable(Year);
   if (Year >= 1) and (Year <= 9999) and (Month >= 1) and (Month <= 12) and
      (Day >= 1) and (Day <= DayTable^[Month]) then begin
      for i := 1 to Month - 1 do
         Inc(Day, DayTable^[i]);
      i := Year - 1;
      Result := i * 365 + i div 4 - i div 100 + i div 400 + Day - DateDelta;
   end;
end;

function Now: TDateTime;

begin
   GetLocalTime(SysTime);
   with SysTime do
      Result := EncodeDate(wYear, wMonth, wDay) +
         (wHour * 3600000 + wMinute * 60000 + wSecond * 1000 + wMilliSeconds) / MSecsPerDay;
end;

begin
   GetPrivateProfileString('Param', 'parLastBKPDT', 'default', @C, 255, 'C:\DTEC\Desenvolvimento\BKP123\BKP123.ini');
   Val(string(c), D, Code);
   if (Now - D) > 1 then begin
      GetPrivateProfileString('Param', 'parLastBKP', 'default', @CE, 255, 'C:\DTEC\Desenvolvimento\BKP123\BKP123.ini');
      MessageBox(0, PChar(Last BackUp ' + string(CE)), 'Warning', MB_ICONWARNING or MB_OK);
   end;
end.

And it still compiles in 17,5 Kb.

GREAT !

Your code is the winner until now !!!

Itamar
Avatar of itamar

ASKER

To ahalya,

in D4 i´m getting the following compiler message:

Return value of function 'EncodeDate' might be undefined

Could you fix it ! I know it´s just a warning, but We never know...

Thanks,
Itamar
itamar,

EncodeDate will be undefined if the input is invalid year, month or date.

//you can put a
Result := 0 ;
//to return zero for invalid dates.
//just before....

if (Year >= 1) and (Year <= 9999) and (Month >= 1) and (Month <= 12) and
    (Day >= 1) and (Day <= DayTable^[Month]) then
  begin;
Avatar of itamar

ASKER

Hi ahalya,

just have done that...

Tkxx
Avatar of itamar

ASKER

Hi all,

if no one disagrees. I will finish this challenge. I think it was very gratifying, and we had a lot of fun...

3.....
2...
1.

Hi Itamar,

It appears we can cut down on the size further ! down.

I just recall the function "GetSystemTimeAsFileTime". It would return a 64bit value in 100nanosecs.

if you save a similar value in the ini file then we can get rid of ALL our functions and directly compaere the time.  (i don't have time now to code it; but it is quite straight forward, i'd think)

btw,
GetSystemTimeAsFileTime takes in a parameter of type TFileTime

type
TFileTime = record
  dwLowDateTime:dword;
  dwHighDateTime:dword;
  end;
ASKER CERTIFIED SOLUTION
Avatar of ahalya
ahalya
Flag of Canada image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of itamar

ASKER

Hi ahalya,

I think the complexity in the code doesn´t worth. 18Kb is really good to me. I´ll try this code later just for fun. For now, I must finish this project and I´m very glad with your result.

BTW, GetSystemTimeAsFileTime is a good idea !

Txx,
Itamar
yup,

ahalya wins,
congratulation

meikl ;-)
Avatar of itamar

ASKER

Hi ahalya,

when you have some time, write the working code so we can see the really smallest exe. I´ll do the same ASAP.

[]´s
Itamar
>simonet. is ASPack good + Free etc?

Yes, excellent. It's not freeware, but the trial version works (w/o any limitations) for 30 days. Registratio in only $29 and worth every penny.

Resource Explorer, which all of you - hopefully - know, was compressed from 1.3 Mb down to 454Kb. Besides it is kinda protected since not even Resource Workshop was able to view the resources in the executable.

It's one of the few shareware programs around that I believe deserves two thumbs up.

Yours,

Alex
You may like to read the section called "...Why not use an Exe compressor?".
http://www.jordanr.dhs.org/striprlc.htm

It has a different point of view. It describes how the code is duplicated in memory if you run more than one instance of a compressed exe.

BTW, StripReloc (freeware) may make the exe even smaller by removing the relocation code. You can read about that from the same url.

Cheers,
Phil.
Simonet: well i have found millions of Decompress, rip tools for ASPack, Shrinker etc.. but almost none for UPX..

well its not up to us what to use :)
im writing own protection..
just found info on how to do it,
code tiny assembly decoder/decrypter that runs first.. kinda simple..
i faught it was alot worse..
Hi Itamar,

Here's the full code using "GetSystemTimeAsFileTime".  The code is pretty small now, but yet the size of the compiled EXE is 14,838 bytes.
(I wonder why ? may be some can shed some light}

Also please note that there is an error in Windows.pas's declaration of the proc GetSystemTimeAsFileTime (instead of passing a PFileTime, Borland has passed a TFileTime)


program sm_exe2;

const
  MB_ICONWARNING = $00000030;
  MB_OK = $00000000;
  HopeThisNoIsCorrect : double = 24.0*60*60*1000*1000*10;
//100 nano seconds in a day
//originally used "Extended", but guess "Double" is good enough

type
  PFileTime = ^TFileTime;
  TFileTime = record
    LowDateTime: Integer;
    HighDateTime: Integer;
  end;

function  GetPrivateProfileString(lpAppName, lpKeyName, lpDefault: PAnsiChar; lpReturnedString: PAnsiChar; nSize: Integer; lpFileName: PAnsiChar):Integer; stdcall; external 'kernel32.DLL' name 'GetPrivateProfileStringA';
function  MessageBox(hWnd: integer; lpText, lpCaption: PChar; uType:integer): Integer; stdcall; external 'user32.dll' name 'MessageBoxA';
procedure GetSystemTimeAsFileTime(var lpSystemTimeAsFileTime: PFileTime); stdcall; external 'kernel32.dll' name 'GetSystemTimeAsFileTime';

var  c, ce : array[0..64] of char;
     d, t : double;
     code : integer;
     ft : PFileTime;

begin;
GetPrivateProfileString('Param', 'parLastBKPDT', 'default', @C, 255, 'C:\DTEC\Desenvolvimento\BKP123\BKP123.ini');
GetPrivateProfileString('Param', 'parLastBKP', 'default', @CE, 255,'C:\DTEC\Desenvolvimento\BKP123\BKP123.ini');

Val(string(c), d, Code);
GetSystemTimeAsFileTime(ft);
t := ft^.HighDateTime + (ft^.LowDateTime/(MaxINT+1.0));
if (t-d) > HopeThisNoIsCorrect then
     MessageBox(0, PChar('Last BackUp ' + string(CE)), 'Warning', MB_ICONWARNING or MB_OK);
end.
Avatar of itamar

ASKER

Hi,
ahalya,

I have done an interesting test: I changed Now for a constant, let´s say, 39000, just to see the efect of not using DateTime stuff. For my surprise the size didn´t change so much. I think 14... Kb is some low limit...

Try it !

Itamar
Oh yes. That explains why my second code is also 14 kb.  i wonder why ?

btw, i tried a dpr file only with a

begin;
//and
end.

That turned out to be 7 kb !
Avatar of itamar

ASKER

There is smth else between earth and heaven...
Avatar of itamar

ASKER

Great ! txs. Happy 2000 !