mitzi 43 Posted September 14, 2021 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. 6 5 Share this post Link to post
Vandrovnik 214 Posted September 14, 2021 Thank you! Emba could do the same... Share this post Link to post
Dmitry Arefiev 101 Posted September 14, 2021 Please report this at quality.embarcadero.com Share this post Link to post
David Heffernan 2345 Posted September 14, 2021 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. 1 1 Share this post Link to post
Fr0sT.Brutal 900 Posted September 14, 2021 They made GetTickCount64 a method of TThread *facepalm* Share this post Link to post
Der schöne Günther 316 Posted September 14, 2021 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. 3 Share this post Link to post
Vandrovnik 214 Posted September 14, 2021 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
Anders Melander 1782 Posted September 14, 2021 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. 2 Share this post Link to post
Rollo62 536 Posted September 14, 2021 @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 2 Share this post Link to post
Dmitry Arefiev 101 Posted September 14, 2021 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
aehimself 396 Posted September 14, 2021 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 🙂 1 Share this post Link to post
Alexander Elagin 143 Posted September 14, 2021 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. 2 Share this post Link to post
David Heffernan 2345 Posted September 14, 2021 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. 2 Share this post Link to post
Lars Fosdal 1791 Posted September 15, 2021 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. 1 Share this post Link to post
Fr0sT.Brutal 900 Posted September 15, 2021 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
Alexander Elagin 143 Posted September 15, 2021 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
Anders Melander 1782 Posted September 15, 2021 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 Share this post Link to post
zed 14 Posted September 15, 2021 A change in TThread breaks Windows XP compatibility 1 Share this post Link to post
Fr0sT.Brutal 900 Posted September 15, 2021 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. 1 Share this post Link to post
Marco Cantu 78 Posted September 15, 2021 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
Remy Lebeau 1392 Posted September 15, 2021 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 Posted September 15, 2021 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
Remy Lebeau 1392 Posted September 15, 2021 (edited) 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 September 16, 2021 by Remy Lebeau Share this post Link to post
Guest Posted September 16, 2021 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