RSS icon Home icon
  • Delphi compile timestamp

    Posted on November 21st, 2015 admin No comments

    It 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

    Your email address will not be published.