Updating 32bit code to Delphi 64bit
Ever since Delphi XE2, it has been possible to generate Delphi 64bit applications from the same code base as your traditional Windows 32bit Delphi code. The business case for 64-bit for business is covered in this tech paper The Impact of 64-bit Applications to your Company’s Bottom Line.
On the whole moving to 64bit (on iOS or Windows) is beautifully simple to achieve! It can be just as simple as adding the Delphi 64bit Windows target platform in the project manager and rebuilding the project.
My experience from talking to many developers who have moved up is that normally there are a few things to check in your code but typically its not a massive task to get compiling and ready to test.
A lot has been recorded on moving from Windows 32bit to Windows 64bit Delphi and this should be a useful summary if your just planning now moving up from older versions of Delphi to Delphi 10. If you are building iOS applications, then you will need to use the 64bit build now to get into the AppStore. Thankfully, Delphi has made the task of using 64bit very simple across all platforms and protected us from the headaches non Delphi Developers have had on the whole.
Lets start with this short video from David I who covers some the foundations in 7 minutes!
Whats the same in 32bit and 64bit Delphi!
As they don’t need to sit in bigger memory spaces, Integer, LongInt, Cardinal – still 32bit on both platforms. Int64 and UInt64 are 64bit (8bytes) on both platforms.
Unicode String, AnsiString, WideString etc are all the same. Work in the same way and use the same RTL calls.
Exception handling is the same! You still set break points and they work in the same way when debugging, the exceptions that get raised are still handled in the same way.
The units you use are still the same e.g. SysUtils, Classes etc. The RTL (Run Time Library) is still the same.
Signed Types | Delphi 32bit and Delphi 64bit |
ShortInt | 1 byte |
SmallInt | 2 byte |
LongInt | 4 byte |
Integer | 4 byte |
Int64 | 8 byte |
Unsigned Types | Delphi 32bit and Delphi 64bit |
Byte | 1 byte |
Word | 2 byte |
LongWord | 4 byte |
Cardinal | 4 byte |
UInt64 | 8 byte |
Differences in 32bit and 64bit Delphi code
There are a few types that are different depending on which platform you compile for.
NativeInt and NativeUInt are 32bit or 64bit / 4 or 8 bytes, depending on the target platform.
Dynamic Arrays use 64bit Indexing on a 64bit compilation.
64bit processors don’t have the same extended type that exists on 32bit meaning that the Extended type reverts to DOUBLE on 64bit. If you need to use Extended use TExtended80Rec.
But the key point is that Pointers are all 64bit on 64bit versions. This means a few things. Firstly, all objects have a bigger pointer than an Integer. So if you have been typecasting your pointers into Integers….. be warned!!!
Pointer Types that are 4 bytes in Delphi 32bit code and 8 bytes in 64bit code include
- Pointer
- String
- Class Instance
- Class reference
- Interface
- AnsiString
- WideString
- UnicodeString
- Procedure Pointer
- Dynamic Array
- PAnsiChar
- PWideChar
- PChar
Code that uses SHL/SHR can also act differently over 32bit and 64bit values if you use NativeInt – using Integer will work the same.
Been using TAG property to store pointers?
Luckily – while its not best practice, your friendly Delphi component team has updated the Tag property on TComponent to be a NativeInt so it will be 4 / 8 bytes depending on the platform so will not break your code!
Calling Conventions
In Windows 32bit code you needed to use specific calling conventions to share and use specific API’s. In Windows 64bit, there is only one convention so the following are not required
- Register
- pascal
- cdecl
- stdcall
HOWEVER – still leave them in the code for the Win 32bit code to compile. The 64bit compiler is smart enough to just ignore them.
Safecall (used in COM development) is still “Special” however.
Using Assembler (ASM) blocks in your code on Windows?
One change in 64bit Delphi v 32bit Delphi code is that inline Assembler is not allowed. What this means is that you can’t have a method that contains both Pascal and Assembler between the same begin and end statement. This is easily fixed however and you have two options
- Create a new function / procedure to wrap up the ASM code and call that function
- Replace the ASM code with pure Pascal code that will work on 32bit and 64bit Delphi targets (not only on Windows, but beyond)
If you do extract the method into a new function (so you can keep your assembler for Win32) you will still need to add in code to allow it to work on 64bit compilations and need to tell the compiler how to differentiate between the two blocks of code… That is where Pre-defined conditions come in.
Pre-defined conditions
The Delphi 64bit compiler introduced a number of new pre-defined conditions. While you don’t need to use these regularly, they can be used if you want to keep assembler code that you have optimised for Win32 and then only use pure pascal code on other platforms.
Pre-defined conditions allow specific blocks of code to be compiled in depending on the platform e.g. The following block will show Win32 as a show message on Windows 32bit versions only.
procedure DoSomething; var S : string; begin {$IFDEF WIN32} S := 'Win32'; {$else} S := 'Not on Win32'; {$ENDIF} ShowMessage(S); end;
Here is a list of the conditions and the platforms they are defined for. You can get a full list of Delphi pre-defined conditions from DocWiki
Delphi and the Windows API
The core windows windows RT code will work correctly, if you are doing any special low level messaging, there are differences in the WPARAM and LPARAM. You can use explicit cast as appropriate: e.g if you are passing messages through SendMessage:
SendMessage(hWnd, WM_SETTEXT, 0, LPARAM(@MyCharArray));
Use LRESULT to case message results
Message.Result := LRESULT(Self);
If you are doing any TWMxxx message cracking, then you will need to re-align changes and field sizes changes to allow for the difference between Win32 and Win64.
Further reading / viewing
- Windows 64bit Delphi page on Embarcadero.com
- David I did a great webinar on moving to 64bit. Delphi 64bit – Replay on demand
- DocWiki has some useful links – specifically a page on what to look out for when making your single code base Windows 32bit and 64bit compatible (if you have worked with other tools, its a lot less to worry about that you may think!)
- [added 2019-11-26] DockWiki – Converting 32bit to 64bit
If you are doing any TWMxxx message cracking, then you will need to re-align changes and field sizes changes to allow for the difference between Win32 and Win64.
If you declare the cracking records correctly then the same code works in both compilers. The key is to specify the correct alignment and let the compiler do the work.
Thanks David, and thanks for the other comments to improve the article.
When converting to 64 bit, I came across a problem with some really old third-party code where they had used {$IFDEF WIN32} … {$ELSE} …{$ENDIF} similar to the DoSomething routine code in the article.
They had done this to support Delphi 2.0 versus Delphi 1.0, and as such the $ELSE was expected to be 16 bit code and the old units etc associated with Delphi 1.
This code had worked fine for years, but as soon as you compile as 64 bit, it breaks. The WIN32 section in this case wasn’t really meaning 32 bit, but rather “any newer Delphi version” – so I was able to take out the compiler directives and ELSE section entirely.
“Been using TAG property to store pointers?”
One point that recently bit me (Delphi 10.1 2nd Update) is that while in my 64-bit project
myControl.Tag := Integer(someObject);
runs fine at run-time, when executed in the debugger, it crashes and looking at the value of the Tag beforehand it is very negative leading me to conclude that the debugger was not treating it correctly.
Replacing these calls by
myControl.Tag := NativeInt(someObject);
gets rid of the debugger problem.
NativeInt is definitely the way to go. Integer would be a different size to tag hence the issue you saw.