Skipping the UTF-8 BOM with TMemIniFile in Delphi 2007

 Delphi  Comments Off on Skipping the UTF-8 BOM with TMemIniFile in Delphi 2007
Mar 072020
 

Recently I came across a problem with INI files: Some editors (including recent versions of Windows Notepad) add a byte order mark (BOM) to the files they save. In particular the BOM for UTF-8 kept appearing in INI files which then were read incorrectly by the Delphi 2007 implementation of TMemIniFile (I guess the same applies to all pre Unicode versions of Delphi). In particular this was a problem with programs that used TJvAppIniStorage for streaming application settings to disk. (TJvAppIniStorage internally uses TMemIniFile.) So I tried to fix this, first by adding code that reads that file, removes the BOM and writes it back, before actually using it. This had some unpleasant side effects because some programs that usually start at the same time tried to access the file in parallel and failed. (No problem when only reading, but a big problem when writing.)

So I dug deeper and found that modifying TMemIniFile.LoadValues like this fixed the problem:

procedure TMemIniFile.LoadValues;
const
  BOM_LENGTH = 3;
var
  List: TStringList;
  st: TMemoryStream;
  Buffer: array[0..BOM_LENGTH-1] of Byte;
begin
  if (FileName <> '') and FileExists(FileName) then
  begin
    List := nil;
{$MESSAGE hint 'UTF-8 fix for TMemIniFile.LoadValues is active'}
    st := TMemoryStream.Create;
    try
      st.LoadFromFile(FileName);
      st.Position := 0;
      if BOM_LENGTH = st.Read(Buffer, BOM_LENGTH) then begin
        // the file contains at least BOM_LENGTH bytes
        if (Buffer[0] = $EF) and (Buffer[1] = $BB) and (Buffer[2] = $BF) then begin
          // we have a BOM -> Just leave the stream position as it is
        end else begin
          // no BOM -> reset stream position
          st.Position := 0;
        end;
      end;

      List := TStringList.Create;
      List.LoadFromStream(st);
      SetStrings(List);
    finally
      List.Free;
      st.Free;
    end;
  end
  else
    Clear;
end;

Note that this will only skip the BOM for UTF-8, but that is the only case I have ever encountered, because UTF-8 is an encoding that is mostly compatible with ANSI encoding. Other encodings will break TMemIniFile completely. But even with UTF-8 you will still encounter problems with characters that are encoded with more than one byte. So this is more of a simple workaround than a bugfix. For a bugfix, you will have to properly decode the whole file. (Or use a Unicode aware version of Delphi where this problem doesn’t exist.)

Of course TMemIniFile is declared in the RTL unit IniFiles so modifying it is not something to do on a whim. It turned out that at least in my case there was no problem as apparently there are no other RTL units that needed to be recompiled to include the changed IniFiles unit. So the easiest way was to copy IniFiles.pas to my program’s source directory, add it to the project (I prefer doing that so it’s easier to spot such a modified unit.) and recompile.

 Posted by on 2020-03-07 at 10:45

Delphi’s TStream.Read returns the number of bytes read

 Delphi  Comments Off on Delphi’s TStream.Read returns the number of bytes read
Mar 022020
 

Note to self: TStream.Read in the Delphi RTL returns the number of bytes read. It does not check whether the intended number of bytes could actually be read. So if you do not check it yourself, call TStream.ReadBuffer instead.

So, it’s either:

var
  st: TFileStream;
  Buffer: array[0..7] of Byte;
  BytesRead: Integer;
begin
  st := TFileStream.Create(fn, fmOpenRead);
  try
    BytesRead := st.Read(Buffer, SizeOf(Buffer));
    if BytesRead <> SizeOf(Buffer) then
      raise Exception.CreateFmt('BytesRead (%d) <> SizeOf(Buffer) (%d)',
        [BytesRead, SizeOf(Buffer)]);
    // do something with the content of buffer
  finally
    FreeAndNil(st);
  end;
end;

or

var
  st: TFileStream;
  Buffer: array[0..7] of Byte;
begin
  st := TFileStream.Create(fn, fmOpenRead);
  try
    st.ReadBuffer(Buffer, SizeOf(Buffer));
    // do something with the content of buffer
  finally
    FreeAndNil(st);
  end;
end;

The same logic applies to TStream.Write and TStream.WriteBuffer.

I have just grep-ed my sources and found way to many places where I used Read instead of ReadBuffer and Write instead of WriteBuffer.

Unfortunately that’s an easy mistake to make, so I guessed that I am not the only one who made it. And lo and behold, I found lots of places in the Delphi 2007 RTL and VCL (so they might have been fixed in the mean time, I didn’t check though) and several 3rd party libraries (including the current JCL and JVCL) where this mistake was made. So it’s probably a good idea if you do that check on your own code.

The regular expressions I used for this were:

^\s*\w*\.read\(
^\s*\w*\.write\(

If you use GExperts Grep, don’t forget to enable the “Regular Expression” option!

 Posted by on 2020-03-02 at 16:03

dzBdsLauncher 1.0.3 released

 Delphi, dzBdsLauncher  Comments Off on dzBdsLauncher 1.0.3 released
Mar 012020
 

The latest version of dzBdsLauncher can now also handle some .dproj files with invalid ProjectVersion entries (e.g. those generated by project JEDI which apparently uses ProjectVersion 17.3 for all Delphi versions >XE8). It does this by evaluating the DllSuffix entry, if one exists. This can also help solving conflicts if the ProjectVersion is not unique.

In addition the tool can now also open .grouproj files. It handles them by inspecting the first project listed in the file.

Another small improvement is colored diagnostic output to help troubleshooting if something goes wrong.

See the main dzBdsLauncher page for download links.

 Posted by on 2020-03-01 at 19:24

dzPackageInst 1.0.2 for Delphi 6 to 10.3 released

 Delphi  Comments Off on dzPackageInst 1.0.2 for Delphi 6 to 10.3 released
Feb 282020
 

Today I released version 1.0.2 of dzPackageInst. Its a command line tool that allows installing and uninstalling design time packages into the Delphi 6 to 1.3 IDEs.

Source code and download are available from the project page on SourceForge.

It’s also part of my buildtools which I use for nearly all my projects.

 Posted by on 2020-02-28 at 09:37

TStringList vs. THashedStringList vs. TDictionary

 Delphi  Comments Off on TStringList vs. THashedStringList vs. TDictionary
Feb 162020
 

Prompted by the topic Dictionaries, Hashing and Performance in the international Delphi Praxis forum I did some timing to compare the performance of data structures in the Delphi runtime library that can be used to store data indexed by strings:

  • a sorted, case sensitive TStringList (available since Delphi 6)
  • a sorted, case sensitive THashedStringList (available since Delphi 6)
  • a TDictionary<string,Integer> (available since a Delphi 2009)

Just in case you did not know about THashedStringList: It is a TStringList descendant declared in System.IniFiles. It’s used to speed up access to TMemIniFile. (EDIT: As Uwe Raabe pointet out, that’s no longer true. As of Delphi 10.3 (and possibly earlier, I haven’t checked) TMemIniFile also uses TDictionary<string,Integer>.)

The test adds 676 strings (‘AA’ .. ‘ZZ’) to each structure and does that 10000 times (which means that there are quite a few checks for duplicates to be done on adding). Then – again 10000 times – it does a lookup for each of these strings.

Of course that is just a simple test and it is neither a large number of entries nor long strings. I just wanted to get a feel for the performance of these structures.

Here is the main code for TStringList and THashedStringList:

procedure Tf_HashedStringListTest.DoTiming(sl: TStringList);
const
  CYCLES = 10000;
var
  k: integer;
  i: integer;
  j: integer;
  sw: TStopwatch;
  s: string;
  Idx: Integer;
begin
  sl.Sorted := True;
  sl.CaseSensitive := True;
  sl.Duplicates := dupError;
  sw := TStopwatch.StartNew;
  sl.BeginUpdate;
  for k := 1 to CYCLES do begin
    for i := Ord('A') to Ord('Z') do begin
      for j := Ord('A') to Ord('Z') do begin
        s := chr(i) + chr(j);
        if not sl.Find(s, Idx) then
          sl.AddObject(s, Pointer(i * 100 + j));
      end;
    end;
  end;
  sl.EndUpdate;
  sw.Stop;
  m_Output.Lines.Add(sl.Count.ToString + ': Add: ' + sw.Elapsed.ToString);

  sw.Reset;
  sw.Start;
  for k := 1 to CYCLES do begin
    for i := Ord('A') to Ord('Z') do begin
      for j := Ord('A') to Ord('Z') do begin
        s := chr(i) + chr(j);
        sl.IndexOf(s);
      end;
    end;
  end;
  m_Output.Lines.Add(sl.Count.ToString + ': IndexOf: ' + sw.Elapsed.ToString);
end;

And very similar for TDictionary:

procedure Tf_HashedStringListTest.DoTiming(sl: TDictionary<string, integer>);
const
  CYCLES = 10000;
var
  k: integer;
  i: integer;
  j: integer;
  sw: TStopwatch;
  s: string;
  v: integer;
begin
  sw := TStopwatch.StartNew;
  for k := 1 to CYCLES do begin
    for i := Ord('A') to Ord('Z') do begin
      for j := Ord('A') to Ord('Z') do begin
        s := chr(i) + chr(j);
        if not sl.TryGetValue(s, v) then
          sl.Add(s, i * 100 + j);
      end;
    end;
  end;
  sw.Stop;
  m_Output.Lines.Add(sl.Count.ToString + ': Add: ' + sw.Elapsed.ToString);

  sw.Reset;
  sw.Start;
  for k := 1 to CYCLES do begin
    for i := Ord('A') to Ord('Z') do begin
      for j := Ord('A') to Ord('Z') do begin
        s := chr(i) + chr(j);
        sl.Items[s];
      end;
    end;
  end;
  m_Output.Lines.Add(sl.Count.ToString + ': IndexOf: ' + sw.Elapsed.ToString);
end;

The result is not really surprising:

TDictionary is the winner by a large margin, followed by THashedStringList and then TStringList. The two string lists only differ in the IndexOf times, the adding times are very similar.

On my computer, with an AMD Phenom II XE 1090T processor, and compiled with Delphi 10.3 I get the following times:

Structure Time for Add [sec] Time for IndexOf [sec]
TStringList 7.43 7.48
THashedStringList 7.45 4.40
TDictionary 1.05 1.04

EDIT: I just found that changing the code for the THashedStringlist from using Find to using IndexOf reduced the time for adding entries to about the same time as for IndexOf. So both are about 4 seconds. This makes me wonder whether there is a bug in THashedStringList because it does not override AddObject. It simply inherits it from TStringList which for sorted lists calls Find to see if the string is already in the list. In contrast to IndexOf the Find method does not use the hash so it’s as slow as in TStringList. But maybe that is on purpose because the hashes get calculated rather frequently for all entries. I get the impression that THashedStringList is not really well implemented and nobody noticed because it was just good enough.

EDIT2: As it was only used in TMemIniFile to get fast access to the entries without needing them to be sorted, the implementation probably was good enough. My test above doesn’t check the performance of Add for an unsorted THashedStringList which is what TMemIniFile used.

If you like, you can download the full source code of my test program.

 Posted by on 2020-02-16 at 16:05

Delphi is 25 years old

 Delphi  Comments Off on Delphi is 25 years old
Feb 152020
 

Everybody seems to be blogging about Delphi having been around for 25 years, so I won’t stay back and tell some of my story.

When I finished university and started a job, Delphi was just about being “born” and I was working with Turbo Pascal and later Visual Basic. VB was great in some aspects because it allowed to easily design user interfaces and write code only where you needed it. It wasn’t after several years later that I was introduced to Delphi when I took a job at fPrint UK Ltd. (Yes, that’s what web pages looked in 1997) and moved from Germany to the UK. The time I worked there was among the best of my life. I had some great coworkers there who were expert software developers (Hello Mamta, Allan, Vitaly and Linden, if you read this. And RIP to you, Andrew). We were already using Delphi 3 by that time and it delivered everything that Visual Basic had only been promising. I was hooked for life. We also worked on Virtual Pascal, a Pascal compiler compatible to Borland Pascal and partly Delphi which had originally been Vitaly’s project. Working for fPrint later made me move to Paris (France) for a while. Back then I also made first contact with GExperts.

Fast forward to 2020. I had changed jobs frequently until 2007 due to companies I worked for being bought by others and working conditions deteriorating afterwards. I made my fist million D-Mark (and lost most of it shortly afterwards, never gaining it back). I even had to go back to programming in Visual Basic 6 for a while (and I hated it).

Today I work at TÜV Rheinland Schniering GmbH (formerly Schniering Ingenieurgesellschaft) and develop Software in Delphi for road condition surveys. It is running on our measurement vehicles and also used in the office and at customer’s sites. As software development jobs go this is way cool, and again I have some great coworkers, this time not only in software development, because we also build our own measurement hardware and even developed the elevator examination system Liftis© (software by me, hardware by my coworkers) for our parent company TÜV Rheinland.

I really wonder how my career and my life would have turned out if Delphi hadn’t been around at the time I started out. Maybe I would have ended up as a COBOL programmer for life at Debeka (which was my first employer). Or I would have written embedded software in C for some company I didn’t even get to know. At some time I even interviewed for a job at a company (I forgot the name, but it was located in Dreieich near Frankfurt, Germany) that was developing a search engine written in Delphi (Edit: I remembered: They called themselves “Twirlix” and apparently folded in 2001, shortly after my interview)

Thinking back, this has been some exciting time to be alive and for me Delphi played a significant part of it.

 Posted by on 2020-02-15 at 12:22

How the handle declarations changed in Delphi

 Delphi  Comments Off on How the handle declarations changed in Delphi
Dec 312019
 

Delphi has had a THandle type for a long time (at least since Delphi 6) but didn’t use it consistently. I just had to check those declarations for various Delphi versions in order to get rid of compile errors or warnings in GExperts. Here is what I found:

  • THandle is a type declared in the System unit.
  • INVALID_HANDLE_VALUE is a constant declared in the Windows unit.
  • THandleStream, declared in the Classes unit, has got a private field called FHandle
  • THandleStream.Create has got an AHandle parameter

You would have thought that in all those places THandle is used, but it isn’t. Sometimes it’s DWord, sometimes it’s integer, and sometimes it’s THandle. Also, the declaration of the THandle type changed from LongWord to NativeUInt at some time. Only in Delphi XE2 and later it is consistent (but hey: Everybody keeps telling me to drop GExperts support for versions older than that, so there is an easy solution 😉 ).

Delphi Version THandle INVALID_HANDLE_VALUE FHandle AHandle
6 – 2007 LongWord DWord Integer Integer
2009 – XE LongWord DWord THandle Integer
XE2 and later NativeUInt THandle THandle THandle

 
So, in order to not get any compile errors or warnings I declared two different types:

type
{$IFDEF THANDLESTREAM_CREATE_HANDLE_IS_THANDLE}
  THandleStreamCreateHandleCast = THandle;
{$ELSE}
  THandleStreamCreateHandleCast = Integer;
{$ENDIF}
{$IFDEF THANDLESTREAM_HANDLE_IS_THANDLE}
  THandleCast = THandle;
{$ELSE}
  THandleCast = Integer;
{$ENDIF}

Where the conditional defines are defined as follows:

{$INCLUDE 'jedi.inc'}

// The following cond. defines address errors in various Delphi versions regarding the declaration
// of the FHandle field of THandleStream and the corresponding Create constructor parameter:
{$IFDEF DELPHI2009_UP}
// THandleStream.FHandle is declared as THandle (before that it's an Integer)
{$DEFINE THANDLESTREAM_HANDLE_IS_THANDLE}
{$ENDIF}

{$IFDEF DELPHIXE2_UP}
// AHandle is declared as THandle (otherwise it's an Integer)
{$DEFINE THANDLESTREAM_CREATE_HANDLE_IS_THANDLE}
{$ENDIF}

Really annoying but at least that takes care of these errors and warnings.

 Posted by on 2019-12-31 at 19:50

Building a project in Delphi 10.3 fails if the build script output contains “error:”

 Delphi  Comments Off on Building a project in Delphi 10.3 fails if the build script output contains “error:”
Dec 312019
 

I just had a nasty surprise with Delphi 10.3 when trying to build a project that worked fine with previous Delphi versions. The problem turned out the text one of my pre build events wrote to the output. It contained the string “error :”. Apparently Delphi 10.3 parses the output of the build events and tries to interpret it.

Try for yourself:

  • Create a batch file test.cmd with the following content:
    @echo error: bla
    
  • Add it as a pre build event to a Delphi project:
    call path\to\test.cmd
    
  • Try to compile.

If I’m right, you will get an error like:

And the Messages window will contain the following error:

[Exec Error] EXEC(1): bla

Very annoying. If this is documented, I can’t find it. I only see:

Cancel on error

Cancels the project build if a command returns a nonzero error code.

on http://docwiki.embarcadero.com/RADStudio/Rio/en/Build_Events and http://docwiki.embarcadero.com/RADStudio/Rio/en/Creating_Build_Events

 Posted by on 2019-12-31 at 13:40

DUnit Folder Iterator Extension

 Delphi, GExperts  Comments Off on DUnit Folder Iterator Extension
Dec 282019
 

In 2012 Uwe Raabe blogged about an extension to the DUnit framework he had written. He mentioned it today in the German Delphi Praxis forum.

Guess what? It’s brilliant. It does exactly what I always wanted to write (and never came around doing) for the Unit Tests of the GExperts Code Formatter.

Those tests basically consist of a set of input files in one directory and for each of the tested configurations another set of files with the expected output. It always irked me that I actually had to write some code every time I added a new test instead of simply adding another bunch of files.

Today I changed those tests to use Uwe’s unit (with a few modifications to make it compatible to Delphi 2007). And I found that there were quite a few files which didn’t even get tested at all, because I forgot to add the code.

I’m happy to report that even with these additional tests, the number of failed tests did not increase.

 Posted by on 2019-12-28 at 19:18