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
LVL 4
itamarAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

itamarAuthor Commented:
Edited text of question.
0
itamarAuthor Commented:
Edited text of question.
0
kretzschmarCommented:
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
0
Cloud Class® Course: Python 3 Fundamentals

This course will teach participants about installing and configuring Python, syntax, importing, statements, types, strings, booleans, files, lists, tuples, comprehensions, functions, and classes.

brainwareCommented:
hehe.. just my kinda challenge, ill go code right away :)
0
itamarAuthor Commented:
Edited text of question.
0
brainwareCommented:
Im almost done.. exe size = 15.3 kb so far.. :)
0
itamarAuthor Commented:
Amazing !!!
0
kretzschmarCommented:
well,
then i'm out of the challenge (d3 or d5)
brainware, let see
meikl
0
brainwareCommented:
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..
0
brainwareCommented:
kretzschmar .. dont give up..
i do not yet know how big Final will be :) SysUtils is NASTY..
0
philipleighsCommented:
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.
0
brainwareCommented:
hehe.. i already did that Profile Stuff :)) that was the first i did :)
0
brainwareCommented:
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..
0
kretzschmarCommented:
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
0
brainwareCommented:
yeah.. else it should be written as a single Assembler rutine :) tho im not much good at assembler anymore..
0
itamarAuthor Commented:
testing comment...
0
brainwareCommented:
hmm dont give up.. im gonna grab a smoke while thinking.. i never failed to get below 20 kb in any case :)
0
philipleighsCommented:
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.
0
itamarAuthor Commented:
Ok,

you can count on this format change !
0
brainwareCommented:
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.


0
itamarAuthor Commented:
Show smth like Last BKP in DD/MM/AA HH:MM
0
PalamedesCommented:
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
0
brainwareCommented:
Dont use Forms.pas for first step :)
0
brainwareCommented:
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 :(
0
PalamedesCommented:
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?
0
brainwareCommented:
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
0
IndefreiCommented:
Test...
0
simonetCommented:
Use ASPack in order to compress the executable.
Let me know if you need a link.

Alex
0
brainwareCommented:
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..
0
rarigoCommented:
sitting and watching...
0
brainwareCommented:
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.

0
ahalyaCommented:
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.
0
itamarAuthor Commented:
Compiling...
0
itamarAuthor Commented:
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
0
itamarAuthor Commented:
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
0
ahalyaCommented:
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;
0
itamarAuthor Commented:
Hi ahalya,

just have done that...

Tkxx
0
itamarAuthor Commented:
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.

0
ahalyaCommented:
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;
0
ahalyaCommented:
Here is some code.  (Please note my logic to convert FILETIME into an extended number for comparion with the value in inifile).

Hope this works.


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

procedure GetSystemTimeAsFileTime(var lpSystemTimeAsFileTime: PFileTime); stdcall; external 'kernel32.dll' name 'GetSystemTimeAsFileTime';


var  c    : array[0..255] of char;
     d, t : extended;
     code : integer;
     ft : PFileTime;

Const
     HopeThisNoIsCorrect : extended = 24.0*60*60*1000*1000*10;


begin;
Val(string(c), D, Code);
GetSystemTimeAsFileTime(ft);
t := ft^.HighDateTime + (ft^.LowDateTime/(MaxINT+1.0));
if (t-d) > HopeThisNoIsCorrect then
//   MessageBox
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
itamarAuthor Commented:
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
0
kretzschmarCommented:
yup,

ahalya wins,
congratulation

meikl ;-)
0
itamarAuthor Commented:
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
0
simonetCommented:
>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
0
philipleighsCommented:
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.
0
brainwareCommented:
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..
0
ahalyaCommented:
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.
0
itamarAuthor Commented:
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
0
ahalyaCommented:
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 !
0
itamarAuthor Commented:
There is smth else between earth and heaven...
0
itamarAuthor Commented:
Great ! txs. Happy 2000 !
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.