Jump to content
mitzi

Delphi 11 Windows XP compatibility tweak

Recommended Posts

As you likely noticed, Delphi 11 officially does not support Windows XP anymore. You can make your application compatible with XP again by simple set.
In Project Options|Building|Delphi Compiler|Linking set "Set OS Version fields in PE Headers" and "Set SubSystem Version fields in PE Headers" to "5.1".


Unless your application uses System.Threading.pas (TTask etc) you should run it under Windows XP with no problems. But if so, then you have to tweak this unit.
Threading objects actually use in their internals new TThread.GetTickCount64 method, which is hardwired to Windows API GetTickCount64 which is not available in Windows XP API. 
Take this unit from "source\rtl\common" folder in Delphi 11 installation. Declare new local function in the beginning of implementation section of this unit like this:

 

function _GetTickCount64: UInt64;
begin
  if TOSVersion.Major<6 then
    Result :=  TThread.GetTickCount
  else
    Result :=  TThread.GetTickCount64;
end;

and replace all occurrences of TThread.GetTickCount64 calls with _GetTickCount64.

For Win32 applications then copy this modified unit to \lib\win32\debug and \lib\win32\release folders in Delphi 11 installation and rename original System.Threading.dcu to e.g. _System.Threading.dcu.


Then build your project which uses System.Threading with Debug and Release configuration. New System.Threading.dcu should be created in mentioned folders. After this you should remove modified System.Threading.pas from these folders to prevent its recurrent compilation.

 

Now your Delphi 11 Win32 applications should run under Windows XP with no External Exception crash.
 
 

Screenshot_002.png

  • Like 5
  • Thanks 4

Share this post


Link to post
24 minutes ago, Vandrovnik said:

Thank you! Emba could do the same...

They explicitly don't want to keep on supporting these old versions.

 

8 minutes ago, Dmitry Arefiev said:

Please report this at quality.embarcadero.com

Why? Emba have done all this intentionally.

  • Thanks 1

Share this post


Link to post

There's too many "should run" in there.

 

Neither the IDE, nor the produced Win32 binaries are intended to be run on Windows XP, MS-DOS or Enigma. Is there a guarantee it will run on those platforms, just by not importing GetTickCount64? That just doesn't sound safe enough to promise my customers the same.

  • Like 2

Share this post


Link to post
28 minutes ago, David Heffernan said:

They explicitly don't want to keep on supporting these old versions.

 

Why? Emba have done all this intentionally.

From my point of view, compatibility with old versions of Windows is one of important benefits of applications written in Delphi. If they broke this compatibility just because of GetTickCount64, they are throwing this benefit away needlessly.

Share this post


Link to post

Support for older versions of Windows doesn't come for free. Official support means that they need to test against those versions and that they can't use the APIs of newer versions. Given the minimal market share of XP it hardly seems worth using resources to support it.

If support for XP is required then use a version of Delphi that supports XP instead.

 

I myself am on Windows 7 and if (when) that breaks Delphi then I will only blame myself.

  • Like 2

Share this post


Link to post

@mitzi

Thanks a lot.

This could make some backed up WinXP VMs with old XP projects obsolete, if we can compile them in current Rx11 too.

Lets utilize the free'd disk-space with some new, astonishing and modern FMX project's :classic_biggrin:

  • Like 2

Share this post


Link to post
2 hours ago, David Heffernan said:

They explicitly don't want to keep on supporting these old versions.

 

Why? Emba have done all this intentionally.

Not at all.

Share this post


Link to post
2 hours ago, Anders Melander said:

Given the minimal market share of XP

What about Server 2003? Also, most built-in machines are still on XP (older ones can be even on 2k).

 

What I loved about Delphi until now is that if you write the code thoughtfully it runs on Win2k up. And while I can understand the business decision behind this it still hurts a little 🙂

  • Like 1

Share this post


Link to post

But using GetTickCount64 in fact is a must for their code in the Threading unit which internally uses the milliseconds counter. As you know, the counter overflows every 49 or so days and some threads in your server application just hang waiting until the condition TThread.GetTickCount > FLastSuspendTick.Value + TThreadPool.SuspendInterval is fulfilled... I got hit by this myself. Of course, it mostly matters for the applications that run 24/7, but anyway using the 64 bit counter is a wise decision.

  • Like 2

Share this post


Link to post
4 hours ago, Dmitry Arefiev said:

Not at all.

I watched the sneak peak webinar and Marco mentioned that apps would no longer run on XP and also described the workaround in the original post here. 

 

So yes, this is intentional, and submitting a QP report would be kind of pointless. However, if you want there to be one you could do it yourself, rather than ask somebody else to do it.

  • Like 1

Share this post


Link to post

I am with @Anders Melander - If you need XP compatible software, you need to use an older Delphi.

Cutting technical debt is vital to simplify any system.

  • Like 1

Share this post


Link to post
12 hours ago, Alexander Elagin said:

But using GetTickCount64 in fact is a must for their code in the Threading unit which internally uses the milliseconds counter. As you know, the counter overflows every 49 or so days and some threads in your server application just hang waiting until the condition TThread.GetTickCount > FLastSuspendTick.Value + TThreadPool.SuspendInterval is fulfilled... I got hit by this myself. Of course, it mostly matters for the applications that run 24/7, but anyway using the 64 bit counter is a wise decision.

function TickDiff(StartTick, EndTick: Cardinal): Cardinal;
begin
  if EndTick >= StartTick
    then Result := EndTick - StartTick
    else Result := High(Cardinal) - StartTick + EndTick;
end;

 

Share this post


Link to post
2 hours ago, Fr0sT.Brutal said:

function TickDiff(StartTick, EndTick: Cardinal): Cardinal;
begin
  if EndTick >= StartTick
    then Result := EndTick - StartTick
    else Result := High(Cardinal) - StartTick + EndTick;
end;

 

I was going to suggest this solution myself but then I thought it was too obvious 😉 and the code in System.Threading (which I quoted) was written by somebody more clever than me...

Share this post


Link to post
24 minutes ago, Alexander Elagin said:

the code in System.Threading (which I quoted) was written by somebody more clever than me...

Yes, they probably realized that there's no reason to resort to hacks when there's better solution :classic_dry:

Share this post


Link to post
18 hours ago, Alexander Elagin said:

Of course, it mostly matters for the applications that run 24/7

Alas, no. The tick rollover could happen at any time, so even short-running apps are not protected. Moreover, the issue isn't exclusive for 24/7 servers only because of hibernation. I personally prefer to hibernate my PCs (both work and personal) when I leave so I have uptimes counting weeks and months.

  • Like 1

Share this post


Link to post

Let me clarify:

 

Changing the PE format to target newer versions was a design decision. It's the same Visual Studio does. And it does make a difference in the results when invoking some HighDPI related Windows APIs. They FAIL to return the right value if the app is for XP, so if you change the PE setting (doable) you'll ahve some trouble on the latest systems

 

The introduction of an XP braking issue with GetTickCount64 was not intentional and discussed (while of course GetTickCount64 was a requested feature, and it's called by other libraries) . We don't test on XP by design, no one in the beta did, most likley. While we don't officially support XP, such a simple change is doable -- as long there is zero impact on newer systems and it costs a limited time. I doubt we'll release a patch for it, though... 

Share this post


Link to post
2 hours ago, Marco Cantu said:

While we don't officially support XP, such a simple change is doable -- as long there is zero impact on newer systems and it costs a limited time. I doubt we'll release a patch for it, though... 

Here is an implementation of GetTickCount64() for Win2K and XP, it would be trivial to use the native GetTickCount64() on Vista+ and fallback to this version on pre-Vista systems:

 

https://titanwolf.org/Network/Articles/Article?AID=7398e4d3-ddc2-42b0-ae21-d52654fe9287#gsc.tab=0

Share this post


Link to post
Guest
47 minutes ago, Remy Lebeau said:

Here is an implementation of GetTickCount64() for Win2K and XP, it would be trivial to use the native GetTickCount64() on Vista+ and fallback to this version on pre-Vista systems:

 

https://titanwolf.org/Network/Articles/Article?AID=7398e4d3-ddc2-42b0-ae21-d52654fe9287#gsc.tab=0

Will work, but i think it will be an overkill, also the overhead using system call (NtQuerySystemInformation) instead a simple OS function call will prevent it from being useful for timing.

 

I suggest a simple emulation like this

var
  TickCount64: UInt64;

function GetTickCount64Emu: UInt64;
var
  Current: UInt32;
begin
  Current := GetTickCount;
  if Current < PUint32(@TickCount64)^ then
    Inc(TickCount64, $100000000);
  PUint32(@TickCount64)^ := Current;
  Result := TickCount64;
end;

This will emulate 64bit version fine, except in stressed system with multithreading, here instead of implementing locking mechanism, it is better to avoid using global, on other hand using that 64 bit counter as field in TThread will fix it, considering TThread is implementing GetTickCountxx as methods, one thing though, this emulated version can't be a class function.

Share this post


Link to post
Quote

I suggest a simple emulation like this

...

This will emulate 64bit version fine, except in stressed system with multithreading,

What about (in a loop) using TInterlocked to Read() the counter into a local variable, update the two halves of that variable as needed, and then CompareExchange() the result back into the counter until it takes effect for multi-threaded access?  Looking at some other apps (Mozilla, etc), that is how they solved this problem years ago.

Quote

here instead of implementing locking mechanism, it is better to avoid using global

How? Without using thread-local variables?

Quote

on other hand using that 64 bit counter as field in TThread will fix it, considering TThread is implementing GetTickCountxx as methods

Would still need a locking mechanism to avoid races across threads.

Quote

one thing though, this emulated version can't be a class function.

Why?

Edited by Remy Lebeau

Share this post


Link to post
Guest
5 hours ago, Remy Lebeau said:

What about (in a loop) using TInterlocked to Read() the counter into a local variable, update the two halves of that variable as needed, and then CompareExchange() the result back into the counter until it takes effect for multi-threaded access?  Looking at some other apps (Mozilla, etc), that is how they solved this problem years ago.

That works and a good solution too, but my idea is to think something locking-free.

5 hours ago, Remy Lebeau said:

How? Without using thread-local variables?

It will be just more complicated, to make sure we increase higher 32bit only once with in a specific time frame (lets say 60000ms) so it will require a var to store when it has being increased, in other words TickCount64 will be declared as UInt32 and will only represent the higher part, also will be increase by one thread in atomic way.

But this introduce another problem, what if a thread created after 49 days from continuous running, then it will have different time by 2^32 ms, the solution is make the higher part of that ticker a global var and the lower part a local class field, here it will increase when the increment happen within a specific time and increase from the old value (that will be changed after that specific long time) and will not issue an "Inc" on the global 32bit, but will increase the local then overwrite the global, this will protect the higher part . 

 

5 hours ago, Remy Lebeau said:
Quote

one thing though, this emulated version can't be a class function.

Why?

Well, thinking again about it, it can be solved by simply be declare the counter as treadvar

threadvar
  TickCount64: UInt64;

function GetTickCount64Emu: UInt64;
var
  Current: UInt32;
begin
  Current := GetTickCount;
  if Current < UInt32(@TickCount64) then
    Inc(TickCount64, $100000000);
  PCardinal(@TickCount64)^ := Current;
  Result := TickCount64;
end;

Now it can be used as class function, also i expect GetTickCount64 to be loaded dynamically, so if it does not exist then use the emulated one, using threadvar is slower than local but faster than system calls.

Also it should implement, the method mentioned above to make the higher part globally accessible to threads then overwrite the higher part, this will prevent a newly created thread after 49 days of running the application from being off by 49 days.

 

 

One thing though, Mitzi pasted a solution and i really can't make sense of it, i mean i only can make sense of that code if GetTickCount64 is declared as delayed call to make such code right, but on other hand declaring kernel function as delayed makes no sense at all as kernel is loaded always and delaying an import for it is simply wrong, 

Found this from https://docs.microsoft.com/en-us/cpp/build/reference/linker-support-for-delay-loaded-dlls?view=msvc-160#constraints-on-delay-load-dlls

Quote

Delay loading Kernel32.dll isn't supported. This DLL must be loaded for the delay-load helper routines to work.

I don't see GetTickCount64 in Seattle, so if someone with higher version can shed some light on how it is declared, if it is delayed then it should be fixed, and replaced with dynamic address resolving, later to use it in TThread it could depend on its pointer value instead of TOSVersion.

 

 

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×