-
Delphi compile timestamp
Posted on November 21st, 2015 No commentsIt could be useful to know the build date of the executable. In most situations this is the file’s timestamp.
It is quite simple to read it out in TDateTime format:ExeDate := FileDateToDateTime(FileAge(ParamStr(0)));
However, there are situations when the file’s timestamp is altered and the build date is lost.
By introduction of D2006, the build date is stored in the PE (Portable Executable format) header.
Before Delphi 2006, the build date is stored in the Resource Directory Table of PE.My first approach was to update the timestamp in the PE header for old Delphi versions. I planned to create an IDE plugin which calls the update at the AfterCompile event. The timestamp read-out would be simpler and would not consider the Delphi version. Automatic daily builds don’t use the IDE, so a separate updater tool is required. It seemed unnecessarily complex and I didn’t find good examples to alter the PE header.
On newer Delphi compilers, to get the build date for the currently running executable is easy:
function GetLinkerTimestamp: TDateTime; begin Result := PImageNtHeaders(HInstance + Cardinal(PImageDosHeader(HInstance)^._lfanew))^.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta; end;
For other executables it is still simple:
function GetLinkerTimeStamp(const Filename: AnsiString): TDateTime; var LoadedImage: PLoadedImage; begin LoadedImage := ImageLoad(PAnsiChar(Filename), nil); Result := LoadedImage.FileHeader.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta; ImageUnload(LoadedImage); end;
Please note that, this is the UTC time (or GMT). To get the local time, convert it (Delphi XE):
function ConvertToLocalTime(UTC: TDateTime): TDateTime; begin Result := TTimeZone.Local.ToLocalTime(UTC); end;
Please also note that, ImageLoad is non-Unicode!
The PE header’s timestamp is always 0x2A425E19 (1992.06.19 22:22:17) on older Delphi versions. This magic number can be used to distinguish between old and new versions.
On older Delphi compilers, to get the build date it is a bit tricky. We have to find the Directory Entry Resource table and read out its TimeDateStamp field. I found even a simple console application has a resource directory so this solution seems generally acceptable.
There’s an ImageDirectoryEntryToData function in ImageHlp.dll which helps to find the Resource Directory Entry in a Loaded Image. For the currently running executable the LoadedImage’s base address is pointed by HInstance.
The IMAGE_RESOURCE_DIRECTORY structure is not defined in the ImageHlp.pas, so let’s define a new type:
type PIMAGE_RESOURCE_DIRECTORY = ^IMAGE_RESOURCE_DIRECTORY; IMAGE_RESOURCE_DIRECTORY = packed record Characteristics: DWORD; TimeDateStamp: DWORD; MajorVersion: WORD; MinorVersion: WORD; NumberOfNamedEntries: WORD; NumberOfIdEntries: WORD; end;
function GetLinkerTimestamp: TDateTime; var ImgResDir: PIMAGE_RESOURCE_DIRECTORY; Size: Cardinal; begin Result := 0; ImgResDir := ImageDirectoryEntryToData(Pointer(HInstance), True, IMAGE_DIRECTORY_ENTRY_RESOURCE, Size); if ImgResDir <> nil then Result := FileDateToDateTime(ImgResDir.TimeDateStamp); end;
This last version reads out the resource directory’s timestamp from executables:
function GetLinkerTimestamp(const Filename: AnsiString): TDateTime; var LoadedImage: PLoadedImage; Size: Cardinal; ImgResDir: PIMAGE_RESOURCE_DIRECTORY; begin Result := 0; LoadedImage := ImageLoad(PAnsiChar(Filename), nil); if LoadedImage <> nil then begin ImgResDir := ImageDirectoryEntryToData(LoadedImage.MappedAddress, False, IMAGE_DIRECTORY_ENTRY_RESOURCE, Size); if ImgResDir <> nil then Result := FileDateToDateTime(ImgResDir.TimeDateStamp); ImageUnload(LoadedImage); end; end;
This holds the local time, so no need to convert it.
Leave a Reply