So I have spent about a week now with XE2 and FireMonkey and thought I would share some of the experience so far.
After an initial peek and poke around, the first order of business for me was to migrate some of my existing code to the new RTL. First on the list was my own testing framework which I have been using for a few years now. Something which was on the verge of being ready to expose to the harsh light of day but which I had decided to wait until I had an XE2 (and dare I hope… a cross platform) version before releasing.
So this will be the first in a number of posts dealing with specific things that I have run across.
First up: Win32/Win64 cross-platform.
First of all, don’t be too concerned. The notion that Win64 “just works” in Delphi is still valid – for the most part. But there are some edge cases that can catch you out, and what I am about to describe is one of those that you may run into, and emphasises some differences that are also useful to be aware of.
In introducing support for Win64, the RTL now declares some Windows API functions differently (more correctly in fact) which serve both to highlight some important aspects of coding for Win32/Win64 portability but also create some problems if you are writing code intended for use in versions of Delphi earlier than XE2 as well as for XE2 and later.
For example, consider GetProcessAffinityMask(). This used to be declared as follows:
function GetProcessAffinityMask(hProcess: THandle; var lpProcessAffinityMask, lpSystemAffinityMask: DWORD): BOOL; stdcall;
But is now declared thus:
function GetProcessAffinityMask(hProcess: THandle; var lpProcessAffinityMask, lpSystemAffinityMask: DWORD_PTR): BOOL; stdcall;
You will notice that the two mask parameters are now declared as DWORD_PTR rather than plain DWORD.
This DWORD_PTR type is potentially confusing – surely a var parameter is already (under the hood) a pointer, so this declaration is utterly wrong, isn’t it?
No, it’s perfectly correct, just potentially a bit misleading.
What’s in a (D)WORD ?
DWORD_PTR is an unsigned integer with “pointer precision”. Or, put another way: a value declared and treated as an unsigned integer which is big enough to safely store a pointer value. This means that the absolute size (in storage bytes) of a value of this type is dependent upon the platform for which the code is being compiled.
The _PTR suffix in this type does not indicate that the value is a pointer, only that it is big enough to hold a pointer, should you wish to type-cast and use it for that (it’s still an integer at the end of the day, so it is not assignment compatible with a pointer, just “size compatible”. You will note that in this case, the value being referenced is NOT a pointer but a simple bit mask where each bit corresponds to a CPU.
- Win32: DWORD_PTR == 32-bit unsigned integer
- Win64: DWORD_PTR == 64-bit unsigned integer
To make things just a bit more confusing, although DWORD_PTR changes size according to “platform”, a DWORD itself is always 32-bit (in Windows at least – Win32 or Win64, it doesn’t matter) and still corresponds to the Cardinal type.
A 64-bit unsigned integer is, in Windows API terms, a QWORD and in Delphi terms a UInt64.
In XE2 DWORD_PTR is synonymous with the new Delphi type NativeUInt.
This isn’t a problem if you are only writing new code using these Windows API routines for XE2 and later, you just need to make sure to use the NativeUInt type in such cases. But if you are writing code to be portable to older versions of Delphi (Win32 only) and/or are migrating code from those older versions, then this causes a small problem.
Lead Us Not Into Temptation…
Invoke Code Completion on the parameter list for GetProcessAffinityMask() and you will be presented not with the verbatim declaration of the parameter types, but what those parameter types mean at the most basic level. So for example in Delphi 2010 you will be told that the two mask parameters to this function are of type Cardinal whilst in XE2 you will be informed that they are NativeUInt.
So assuming you wrote this code some time ago using Delphi 2010, you most likely took the hint from the Code Completion and declared some Cardinal variables to allow you to use this function:
var processMask, systemMask: Cardinal; begin GetProcessAffinityMask( GetCurrentProcess(), processMask, systemMask ); end;
And you were set. Everything was right with the world.
Then you get your shiny new XE2 license and find that this code no longer compiles, complaining that …
[DCC Error] E2033 Types of actual and formal var parameters must be identical
So you invoke Code Completion to see what parameter types XE2 expects and discover the new NativeUInt parameter type. Accordingly you change your variable declaration, perhaps with a niggly feeling that this might be going to cause you some problems later…
var processMask, systemMask: NativeUInt; begin GetProcessAffinityMask( GetCurrentProcess(), processMask, systemMask ); end;
And lo and behold, everything is once more right with the world so you commit your change and get on with your day.
Then the phone rings.
It’s your buddy down the hall still using Delphi 2010 and your commit just broke his compiler because Delphi 2010 knows nothing about this “NativeUInt” type of which you spoke in XE2.
What to do ?
Salvation in Type
Fortunately this case is easily resolved. Since your buddy is using Delphi 2010 you can – armed with this knowledge or perhaps after digging through the source code of the declarations for GetProcessAffinityMask() and coming to the same realisations and conclusions as I – simply change your variable declarations to DWORD_PTR.
Even though the Delphi 2010 declaration for this GetProcessAffinityMask() uses DWORD, the RTL does also declare DWORD_PTR, and in Delphi 2010/Win32 these are fundamentally one and the same thing: Cardinal.
var processMask, systemMask: DWORD_PTR; begin GetProcessAffinityMask( GetCurrentProcess(), processMask, systemMask ); end;
After this change the Delphi 2010 compiler is happy, so too is Delphi XE2 (and so is your buddy down the hall). 🙂
The Sting in the Tail
However, there remains a problem if your code is, or may be, used by someone using an even older version of Delphi. As of Delphi 2006 (and possibly 2007/2009 also but I haven’t checked) the DWORD_PTR type does not exist. So if that is the case then you will need a more sophisticated (read: complex/messy) approach.
That is in fact the situation in my particular case, and I shall demonstrate how I solved it in a future post since it is part of a broader solution to a number of such issues I am running in to.