The Mysterious Case Of The Lost Inherited Call

Soon after I released the free version of MMX Code Explorer, I got complaints from people having problems after they tried it in Delphi 10.2 Tokyo initial release and with Update 1 installed. The version I released was compiled with Delphi 10.2 Update 3 and when loaded in a Delphi 10.2 with no update or only Update 1 it popped up with an error message about a missing procedure entry point “@Dockform@TDockableForm@DoEndDock”.

As a quick fix to this problem I released another version compiled with Delphi 10.2 Tokyo (no updates), which solved it for those versions as well as apparently made no harm to installations with Update 2 and 3  – apparently!

After I got the complaining users satisfied for the moment I investigated a bit more closely what the real cause of the problem actually was. Despite seeing no direct problems with the workaround above, I somehow felt a bit uncomfortable. Perhaps there was some little beast lurking in the dark, which may raise its ugly head some day when no one expects it. In addition, having to compile with a not up-to-date version doesn’t make life easier on the long run, too.

A straight forward search revealed one class in the code inherited from TDockableForm overriding DoEndDock. As expected there was a call to inherited DoEndDock in the implementation. All looked quite reasonable to me.

Then I moved the mouse over the inherited DoEndDock and waited for the Help Insight window to popup. This is what it looked with the initial Tokyo release:

We can see that the inherited call goes to TControl.DoEndDock. Still nothing to worry about here.

Now lets see what the same code reveals in Update 3:

Hey, that looks indeed a bit suspicious. In Update 3 the inherited call will go to TDockableForm.DoEndDock instead of TControl.DoEndDock.

Now comes the tricky part: Virtual method calling is a one way process!

When a virtual method is called, the most significant override of this method is called through the Virtual Method Table (VMT) – we know that, don’t we? Thus we don’t have to know the concrete class when we make that call. The VMT takes care of the correct method to be called.

Virtual method calling is a one way process!

On the other hand, when we call inherited in this overridden method, the compiler knows, which inherited method of which class is going to be called and resolves that call to the correct address. Seems legit – well, somehow, but yes.

Everything works as expected when we compile a single executable where all methods are linked in and entry points are known. Things look different when packages come into play.

When compiling with packages the compiler uses the DCP file to learn which method has to be called with inherited. At runtime the corresponding BPL will then provide  the proper procedure entry point. Unfortunately this mechanism fails, when the runtime package version is different. In that case the procedure entry points don’t match or are simply non-existent.

In this case Update 2 introduces an override of TDockableForm.DoEndDock adding a new procedure entry point. A compiler seeing this override will rightly call this method instead of TControl.DoEndDock. Now when a package before Update 2 is used during runtime, we cannot find that entry point any more. That is exactly the error we got in the beginning.

So, does compiling the expert against the older DCP really solve the problem? At least it makes the error message go away when run in the older environment. But what about running under Update 2 or 3? The method called for inherited is still TControl.DoEndDock and that exists and the call succeeds. The bad thing about this is that we miss to call TDockableForm.DoEndDock in this case. I have no idea what this method does, but with the current version of the expert it is simply never called for all dockforms created by the expert. I could feel a deep grumbling in my stomach when I realized that.

The conclusion to be drawn from this is: When it comes to packages and you want to be binary compatible between your package versions, you better not touch the interface sections of your units! Introducing a new inheritance level for a virtual method – or worse, removing one – will break binary compatibility. That is a direct consequence of how the compiler resolves inherited calls.

For Delphi this limits the possibilities of what can be implemented in a so called point release, where binary compatibility is key.

How can we overcome this limitation? It depends. In this case I removed the overridden method and wired an event handler for OnEndDock, hoping that nowhere in the IDE some other event handler will steel this link. I know that this will not always be an option.

The next release will incorporate this fix, which also allows me to compile with Delphi 10.2 Update 3 again. Simplifying the build process is currently my main task regarding MMX Code Explorer. The need to use a specific Tokyo version for compiling the expert threw a massive burden on my build system. Having things back to normal is a big relief.

 

 

The only new feature in MMX

The move of MMX turned out to be very welcomed in the Delphi community. Over 3000 downloads during the first 48 hours are just overwhelming and unfortunately led to some interim drop outs of the web server. Apologies for that. I will talk to the web hoster about this and see if we can get this a bit more stable in the future.

The original plan was to release with the exact same feature set as the then current version of MMX with just the licensing removed and all the references to ModelMaker Tools BV rewired to the new home. Well, that didn’t work out exactly like that. There is one new little feature in V13 that somehow slipped through. I doubt that many of you long time users found it unless you actually scrutinized the option dialog, so let me explain what it is and what it does.

This is a screenshot of the Pascal Sorting options dialog with the checkbox Group and sort uses highlighted. With this option checked any Format Uses Clause action (default Ctrl-Alt-U) will resolve unit aliases and expand unit scope names. Then units are grouped by scope names and sorted by group in the order given by the projects options scope names entry. For example:

will be changed to

As with the original uses clause formatting this works only when there are no conditional defines inside the uses clause.

I implemented this feature more as a exercise to become familiar with the code and somehow forgot to exclude it from the release. It also is not fully complete, because the only way to influence the grouping and order is to adjust the unit scope names of the project, where a project specific settings file (or a new entry in the dproj file) would be more appropriate. So I guess this will be completed in one of the next releases.

Now as it is public I hope it might be useful for some of you.