Delphi Custom Container Pack updated for Delphi 10.3 Rio

 Delphi  Comments Off on Delphi Custom Container Pack updated for Delphi 10.3 Rio
Dec 292018
 

I have just updated the Delphi Custom Container Pack sources to support Delphi 10.3 Rio. This time it required than creating the packages for the new version and adding the latest version of the Delphiversions.inc file. When loading the designtime package an EAssertionFailed exception was raised. It turned out that the category sCategoryDelphiNewFiles no longer exists. I have added two fall back categories: sCategoryDelphiIndividualFiles and sCategoryDelphiNew. One of them will hopefully continue to work.

It now compiles and installs. I have not tested it extensively.

Discussion about this post in the international Delphi Praxis forum.

 Posted by on 2018-12-29 at 17:49

TDbf packages for Delphi 10.3 Rio

 Delphi  Comments Off on TDbf packages for Delphi 10.3 Rio
Dec 262018
 

TDbf is an open source library to access Dbase tables. It’s released under the LGPL license (which makes in impractical for most commercial Delphi development). It is also used in Lazaraus (I haven’t checked the license there, but apparently you can develop commercial programs with it.)

The project seems to be rather dead though. Apparently nobody noticed that it failed to compile with many Delphi versions, starting with Delphi 2009 and also including Delphi 10.2.

Today I wanted to simply add packages to support Delphi 10.3 and ran into these compile errors. I fixed them and also committed the packages. Be warned though, that I have not tested the functionality. It compiles but may still be broken.

Discussion about this post in the international Delphi Praxis forum.

 Posted by on 2018-12-26 at 14:06

GExperts 1.3.12 experimental twm 2018-12-22 released

 Delphi  Comments Off on GExperts 1.3.12 experimental twm 2018-12-22 released
Dec 222018
 

It’s time for a gift to all Delphi developers, a new Release of GExperts. Happy Holidays! (But do spend some time with your family rather than testing GExperts. 😉 )

I blogged about the new features already. There were also several bug fixes.

Please be aware that I mostly work with Delphi 2007, so this version can be regarded as tested quite well, followed by Delphi XE2. The others are only known to compile and new features are usually tested superficially with all versions. This is particularly true for Delphi 6/7 and 2005/2006.

Head over to the Experimental GExperts page to download the latest release.

Discussion about this post in the international Delphi Praxis forum.

 Posted by on 2018-12-22 at 18:04

Register and use a custom clipboard format in Delphi

 Delphi  Comments Off on Register and use a custom clipboard format in Delphi
Dec 182018
 

I just now had the need to transmit GPS (WGS 84) coordinates from one program to another. First, I simply copied longitude and latitude separately using the clipboard, which works fine but is really time consuming when you have to do that very often.

I could have just copied both values as text to clipboard using e.g. <tab> as the separator, but that would have required some code on both sides anyway, so I thought, why not use a custom clipboard format? I had never done that before and I wanted to learn how to do it.

Windows allows all programs to register custom clipboard formats by calling the RegisterClipboardFormat Windows API function. It simply takes a PChar (PAnsiChar / PWideChar, depending on which variant you call) and returns a handle for the new format. This handle can then be used to read and write entries of that format to and from the clipboard.

  MyClipFormat := RegisterClipboardFormat(PChar('MyClipFormat'));

The name of the clipboard format must be unique for that format, so e.g. you could prefix it with your company’s domain name to be sure. Multiple programs can register the same format if they know how to read and write it and can then exchange data this way.

Once such a format has been registered, it can be used with the standard Clipboard.HasFormat method.

  if Clipboard.HasFormat(MyClipFormat) then begin
    // Yes! There is something for us on the clipboard.
  end;

Actually writing and reading a custom format involves calls to the Windows API function GlobalAlloc, GlobalLock and GlobalUnlock as well as using the Clipboard.SetAsHandle and Clipboard.GetAsHandle methods.

But first, we need to define that format. In my case I decided to transfer the GPS coordinates as an AnsiString in the following format:

'WGS84'#9 + LongitudeString + #9 + LatitudeString
So, I use a fixed prefix followed by a tab character and then the longitude and latitude as strings, again separated by a tab character. I could simply have transferred the binary representation of e.g. two doubles but I wanted to use a format that was easy to debug. Since this is the clipboard of a single computer, I don’t care about the decimal separator. It should be the same on both sides, the writer and the reader.

Writing is the more complicated part, so let’s implement that first.

procedure WriteMyClipFormat(_Longitude, _Latitude: double);
var
  s: AnsiString;
  MemHandle: HGLOBAL;
  MemPtr: Pointer;
begin
  s := AnsiString(Format('WGS84'#9'%.5f'#9'%.5f', [_Longitude, _Latitude]));
  // Get a moveable memmory handle.
  // (according to the Windows API documentation of
  // SetClipboardData it must be moveable.)
  MemHandle := GlobalAlloc(GMEM_MOVEABLE, Length(s) + 1);
  // To be able to write to such a handle, we must convert it to a pointer.
  MemPtr := GlobalLock(MemHandle);
  try
    StrCopy(MemPtr, PAnsiChar(s));
  finally
    GlobalUnlock(MemHandle);
  end;
  // Now we call SetAsHandle to write it to the clipboard.
  Clipboard.SetAsHandle(MyClipFormat, MemHandle);
  // Since the memory is now stored in the clipboard,
  // we can not free it. So don't call GlobalFree here!
end;

That takes care of the writing part. Now for reading.

function TryReadMyClipFormat(out _Longitude, _Latitude: double): boolean;
var
  MemHandle: THandle;
  MemPtr: Pointer;
  WgsCoords: string;
  s: string;
begin
  Result := Clipboard.HasFormat(MyClipFormat);
  if not Result then begin
    // There is nothing in that format on the clipboard.
    Exit; //==&amp;gt;
  end;

  Clipboard.Open;
  try
    // Get the content.
    MemHandle := Clipboard.GetAsHandle(MyClipFormat);
    // Convert the memory handle to a pointer so we can access it.
    MemPtr := GlobalLock(MemHandle);
    // It's a PAnsiChar, so we can simply assign it to a string,
    // converting it from AnsiString to String on the way
    WgsCoords := string(PAnsiChar(MemPtr));
    GlobalUnlock(MemHandle);
  finally
    Clipboard.Close;
  end;
  // WgsCoords now contains the coordinates encoded in the
  // way described above.
  // We now use ExtractStr (from my u_dzStringUtils unit)
  // to get the three parts and convert the numbers to float.
  s := ExtractStr(WgsCoords, #9);
  Result := (s = 'WGS84');
  if not Result then begin
    // Something went wrong. It is supposed to have this prefix.
    Exit; //==&amp;gt;
  end;

  s := ExtractStr(WgsCoords, #9);
  Result := TryStrToFloat(s, _Longitude);
  if not Result then begin
    // The longitude could not be converted to a float.
    Exit; //==&amp;gt;
  end;

  s := ExtractStr(WgsCoords, #9);
  Result := TryStrToFloat(s, _Latitude);
end;

That takes care of the reading part.

As mentioned above, we could have simply transfered the a memory block containing the binary representation of two doubles instead. I just like my code complex and unreadable. 😉

Also, it would easily be possible to wrap all this into a class which registers the custom clipboard format in its constructor and has a Write and TryRead Method. We could even have multiple classes for different clipboard data types.

Discussion about this in the international Delphi Praxis forum.

 Posted by on 2018-12-18 at 18:13

New features in GExperts

 Delphi, GExperts  Comments Off on New features in GExperts
Dec 152018
 

GExperts has recently gained a few new features:

  • Two new experts to start/stop recording and to replay a keyboard macro. These are minimal experts which allow you to add additional keyboard shortcuts to the existing IDE functionality. The idea and the code were contributed by Dejan M.
  • Goto Previous / Next modification Editor Experts. These again are minimal experts that create menu items for existing functionality of the IDE. They are only available in Delphi XE5 and later. The idea comes from Kryvich who has also written an IDE plugin for that functionality.
  • The Identfier tab I had already introduced into the Uses Clause Manager is now much more useful, because it fills much faster and because of the speed improvement now by default parses all units in the search path, not just the favorites.
  • The Set Tab Order expert now has two buttons to set the tab order by position. It also has buttons to move a control up or down in the sort order like in the Edit Tab Order dialog of the IDE, but these button have keyboard shortcuts.
  • The Remove Matching Lines editor expert in its default configuration removes the useless and annoying { Private declarations } etc. comments that are automatically added by the IDE for a new form. It can be configured to delete lines that match other text.
  • The Favorite Files expert can now optionally add a new menu entry to the Files menu showing the configured files.
  • And of course there is support for Delphi 10.3 Rio, currently as Beta 3 but you can expect a normal release shortly (maybe as early as later today).

If you want to comment on this post, you can do so in the international Delphi Praxis forum.

 Posted by on 2018-12-15 at 15:26

Pointers are dangerous

 Delphi  Comments Off on Pointers are dangerous
Dec 112018
 

Pointers can be really helpful, especially they can improve performance and readability, but they are also dangerous. I spent nearly a day tracking down the reason why the code a former colleague wrote about 5 years ago all of a sudden led to access violations. The code used to work fine, the problem only surfaced when I changed the size of a record. Consider this code:

var
  arr: array of TSomeRec;
  ptr1: ^SomeRec;
  Ptr2: ^SomeRec;
begin
  SetLength(arr, 0);
  // [...]
  SetLength(arr, Length(arr) + 1);
  ptr1 := @arr[High(arr)];
  ptr1.SomeField := SomeValue;
  // [...]
  SetLength(arr, Length(arr) + 1);
  ptr2 := @arr[High(arr)];
  ptr2.SomeField := SomeValue;
  // [...]
  SomeVariable := ptr1.SomeField); // boom

(Of course this is a very simplified example. There is a lot of code where I put the […] comments.)

So why is accessing ptr1 a problem? It was assigned and pointed to the right kind of variable. And it still points to that memory.

It’s not the pointer that was changed, it’s the array. arr is a dynamically allocate array. Each call to SetLength changes the length of the array and, if the previously allocated memory block is too small to fit the new length, a new memory block will be allocated, the contet of the array will be copied to the new memory block and the old memory block will be marked as unused.

And this is where ptr1 becomes invalid. It still points to the old memory block which now is marked as unused and will be used to store other data as soon as necessary.

So: Boom 💣

This is called a “stale pointer” and is the cause of a lot of debugging headaches.

So, why did this work before I changed the record size?

It’s due to the allocation strategy of the memory manager. When it gets a memory request, it checks for free blocks and assigns the block that fits best. Depending on how large the requested memory block is, it takes that memory from one of several pools. Each pool contains blocks of available memory of a fixed size, e.g. pool1 contains blocks of 16 bytes, pool2 contains blocks of 64 bytes etc. (these are just examples the real sizes depend on which memory manager is used and how it has been configured).

When I changed the record size, the record became smaller so the first array entry was allocated from a different pool than before, one that contained smaller blocks. Where previously the memory allocated to the array was large enough to fit more than one record, it now was so small that it fit only one record. Increasing the length of the array therefore required requesting a larger block of memory and copying the array contents. So the pointer became stale.

Discuss this in Delphi Praxis.

 Posted by on 2018-12-11 at 10:49

Remembering an Application’s Size and Position on Multiple Screens

 Delphi  Comments Off on Remembering an Application’s Size and Position on Multiple Screens
Dec 102018
 

David Hoyle just blogged about this topic (go ahead, read it first but remember to come back. 😉 )

Welcome back.

I think he is making two mistakes here (for suitable definitions of “mistake”):

  1. Don’t store the form positions in an ini file, use the registry. These positions make sense only on the same computer. They don’t need to be portable and if they get lost, it’s no big deal.
  2. And if these settings are stored in the registry, all of a sudden, it doesn’t matter whether it is a single or multi monitor setup. Just store the absolute position and be done.

You still need to handle the case that the stored position is outside the visible area, e.g. the monitor is no longer available, the monitor position has been changed, or the monitor settings have been changed.

All the above does not apply to other settings of a program, you might still want to store these in INI files to make them portable.

Of course, this is just my opinion, you have the right to disagree. 😉

This post caused has some comments in the English Delphi Praxis forum.

 Posted by on 2018-12-10 at 11:15

Delphi IDE Explorer is broken in Delphi 10.3 Rio

 Delphi  Comments Off on Delphi IDE Explorer is broken in Delphi 10.3 Rio
Dec 092018
 

I just found out what is wrong with my Delphi IDE Explorer in Delphi 10.3 Rio. The symptom is that the Follow Focus option and the Select Active button no longer work in some forms, in particular in the options dialog (Tools -> Options / Run -> Parameters / Project -> Options). The reason is that this dialog now uses the Screen.OnActiveControlChange event itself and does not bother to call the original event. In previous Delphi versions it did not use this event. Delphi IDE Explorer hooks it to track the control that has the focus and provide the functionality mentioned above. It does call the original event though.

Interestingly the IDE restores the previous event when closing the options dialog so Delphi IDE Explorer gets back its functionality for other dialogs. Unfortunately that does not apply to the dialogs called from within the Options dialog.

Nobody at Embarcadero seems to have read my proposal about Safe event hooking for Delphi IDE plugins or maybe they chose to ignore it. (I guess the number of people who have read it can be counted on the fingers of one hand anyway 😉 )

I have to investigate whether there is any safe method to get my plugin work again. Until then, you will have to live without the Follow Focus feature.

 Posted by on 2018-12-09 at 16:00