The new version is available from SourceForge. Note that, for whatever reason, SoureForge still claims that the latest download is version 1.3.0. You’ll have to select version 1.3.2 yourself. I hope that it is just a matter of time for SourceForge to update that link.
But you should never make instance methods on value types that mutate the value.
Otherwise you can call such a method on an instance passed to a function as a const parameter.
Where “value types” in this case is meant to be an advanced record in Delphi.
I must admit that I am guilty of doing this and have of course lived to regret it. Here is an example:
type TDistance = record private FMillimetres: Int64; public function InMillimetres: Int64; function InMetres: Double; function InKm: Double; procedure AssignMetres(_Value: Double); // don't do this! end;
In case it isn’t obvious: This is an advanced record for storing a distance with a fixed resolution on 1 mm. The idea is to prevent cases where you have got a variable containing a distance but it isn’t immediately clear what unit is it using.
There are functions that return the value converted to metres and kilometres. And then there is a procedure AssignMetres which violates the rule that David stated:
procedure TDistance.AssignMetres(_Value: double); begin FMillimetres := Round(_Value * 1000); end;
On first glance, there is nothing wrong with this method. It simply assigns a new value to the record.
But now, consider this procedure that takes a const TDistance parameter:
procedure doSomething(const _Dist: TDistance); begin // ... Dist.AssignMetres(5); // ... end; // ... var SomeDist: TDistance; begin SomeDist.AssignMetres(3); doSomething(SomeDist); // ...
The compiler won’t complain because calling methods of value types passed as const is allowed. But since the method has a side effect, setting the value of _Dist, it will not just change the value of _Dist inside the procedure but also the value of the variable passed to doSomething. The caller of course relies on that value to remain unchanged, because this is a const parameter after all. But, after the call to doSomething, the value of SomeDist now is 5m rather than 3m as originally assigned.
That’s what David meant with this comment.
This was bad enough, but there is another trap which I fell for. Consider this class that has a property Length of the type TDistance:
type TCar = class private FHasChanged: boolean; FLength: TDistance; procedure SetLength; public property Length: TDistance read GetLength write SetLength; property HasChanged: read FHasChanged; end; // ... function TCar.GetLength: TDistance; begin Result := FLength; end; procedure TCar.SetLength(const _Value: TDistance); begin FHasChanged := True; FLength := _Value; end;
Again, nothing seems to be wrong here. The SetLength property setter, in additon to setting the FLength field also sets the FHasChanged field to True which supposedly is read later on to determine whether any changes have to be saved somewhere. You could probably argue that the property getter for Length is not necessary, but who knows, it might become necessary later on.
It works fine too:
var MyCar: TCar; Dist: TDistance; begin Dist.AssignMetres(4.5); MyCar := TCar.Create; try MyCar.Length := Dist; if MyCar.HasChanged then MyCar.SaveChanges; finally FreeAndNil(MyCar) end;
And then, probably years later, somebody thinks that the code above is unnecessarily complex and optimizes it like this:
var MyCar: TCar; begin MyCar := TCar.Create; try MyCar.Length.AssignMetres(4.5); if MyCar.HasChanged then MyCar.SaveChanges; finally FreeAndNil(MyCar) end;
That’s a reasonable optimization. It looks much cleaner and gets rid of an unnecessary variable declaration. But it does not work!
Why? I’m glad you asked. Let’s have a look at what happens in this line:
It first calls the getter for the Length property, returning a copy of the FLenght field. Then it calls the AssignMetres method of that record, which changes the FMillimetres field of the record. Now, the question: What will be the value of the field MyCar.FLength? It will still be 0 (assuming the constructor of TMyCar does not initialize it otherwise), because we changed the value of the copy, not the value of the field. Also, MyCar.HasChanged will still be False, because the setter for Length has never been called.
So, advanced records should not have methods that change the record’s values. In the example, the solution would be something like this:
type TDistance = record private FMillimetres: Int64; public class function FromMetres(_Value: Double): TDistance; static; function InMillimetres: Int64; function InMetres: Double; function InKm: Double; end; /// ... class function TDistance.FromMetres(_Value: double): TDistance; begin Result.FMillimetres := Round(_Value * 1000); end;
So, a class function FromMetres would be used to return a new TDistance variable initialized to the given metres value.
And it would be used like this:
var MyCar: TCar; begin MyCar := TCar.Create; try MyCar.Length := TDistance.FromMetres(4.5); if MyCar.HasChanged then MyCar.SaveChanges; finally FreeAndNil(MyCar) end;
The setter method for Length gets called does its thing. Everybody is happy. Until, of course, some smart ass like me thinks that an TDistance.AssignMetres would be a great idea …
I have started to write some documentation on the internal workings of GExperts. For now, it covers only a very small part of the IDE form enhancements that GExperts provides. It is meant mostly for myself to get an overview of that part of the tool which has grown too large to understand at a single glance. But it might also help if somebody wants to contribute to that part.
I just moved a Windows 8.1 installation in Virtual Box from one computer to another. When booting up, Windows told me:
This 64-bit application couldn’t load because your PC doesn’t have a 64-bit processor
The host computer is an Intel Xeon CPU which definitely is a 64 bit CPU (the previous computer was an older AMD 6 core CPU which was also 64 bit).
Oddly enough I could not find any solution on the interweb tubes (my Google fu seems to have weakened or maybe Google search isn’t as helpful as it used to be because it tries to guess what the user is searching for rather than simply searching for what he has typed).
It took me a while to figure out what the problem was: For some reason the virtual machine’s configuration had changed on the “General” -> “Basic” page from Version = “Windows 8.1 (64-bit)” to “Windows 7 (32-bit)”. Which apparently means that the CPU reported to the OS is a 32 bit CPU. Changing this back to the original value solved the problem.
Donations for GExperts keep coming in. It’s more a trickle than a flood but hey, I’m not in it for the money. And please remember that I prefer other kinds of contributions over money.
I have used that money in turn for donations to
- Pi-hole®: A black hole for Internet advertisements
- Rapid Environment Editor
- PDF Creator
(Yes, there is a donate link on the FileZilla SourceForge page but it doesn’t work.)
The method of changing the order of the TabSheets in a PageControl in Delphi is not obvious. Apparently there is no drag and drop support (at least not in Delphi 2007). You have to change the PageIndex property. So, if you want to insert a new page, add it and then change its PageIndex to the insert position.
I smell an opportunity for a new GExperts Expert. Any takers?
The issue was that a Macro Template
used on this code
// öäüß procedure TForm1.FormCreate(Sender: TObject); begin ShowMessage('TEST'); end;
while TEST was selected
generated code like this
// öäüß procedure TForm1.FormCreate(Sender: TObject); begin ShowMessage(''); (TEST) end;
The reason again was improper use of positions and offsets into the editor buffer in conjunction with some Unicode characters above the code. So the insert position was calculated by 4 bytes too high, hence the macro code was inserted 4 characters behind the start of the original selection.
Note to self: Do not use a watch entry like this while debugging GExperts:
While this might seem very convenient it will sooner or later corrupt the current edit buffer or do something even worse, because, as a comment in ToolsApi states:
A IOTAEditReader should never be active at the same time as an IOTAEditWriter.
But having it as a disabled entry in the watch window, enable it to inspect the current content of the edit buffer, before disabling it again can be very useful.
dzFeedReader started as a proof of concept, but became actually a usable tool. So, why not make a release available to others?
There you go:
dzFeedReader 1.0.0 is available from SourceForge.
dzFeedReader is a program that can display rss feeds from websites in RSS 1.0, RSS 2.0 and Atom 1.0 format. Its UI is similar to that of Netvibes. It started as a Delphi program but I recently switched it to Lazarus just to see whether that development environment has become any better. Turned out Lazarus is quite usable nowadays.
Since it became rather cumbersome to find the information on my Delphi IDE Explorer expert, which was scattered through multiple blog posts, I have now added a static page for it to my blog which I will keep up to date whenever I change anything in the expert. The sourceforge page now also points to this new page.