Wednesday, 13 April 2011

Delphi 64-bit On The Horizon

When you want to build native Windows applications, Delphi is still a major tour de force in the world of development tools. But currently it can only generate 32-bit executables and so cannot take advantage of all the memory space available when running 64-bit versions of Windows. That will be changing very soon.

Earlier this month David Intersimone posted a 13.5 minute video showing how the pre-release 64-bit Delphi compiler is coming along - you can see it here.

Summary from the video is that most regular Delphi code will simply compile and run. The Delphi RTL (run-time library) and VCL (Visual Component Library) have been updated and reworked internally where appropriate to just work in 64-bit Windows without undue changes to their interface, in much the same way as occurred with Delphi 2 with the move from 16-bit to 32-bit.

Of course things aren’t as simple as that, and there are some key areas where attention must be paid to have smooth transition and have code work in 64-bit but most regular Delphi code will pass through untouched.

In order to get prepared for 64-bit Delphi (or Delphi/64) and to assess your code for areas where attention needs to be paid the slides in the video point out some key gotchas, which I’m listing here to make them easily locatable (much easier to find text on a web page than in a video):

Things that stay the same:

  • Shortint and Byte are still 8 bits.
  • Smallint and Word are still 16 bits.
  • Integer, Longint, Cardinal and LongWord are still 32 bits.
  • Int64 and UInt64 are still 64 bits.
  • UnicodeString, AnsiString, WideString.
  • The Delphi exception system.
  • The RTL (Run-Time Library), e.g. SysUtils, Classes, generics.Collections etc.
  • The VCL (Visual Component Library), e.g. Forms, Graphics, Controls, menus, etc.
  • Windows API

Things that are different:

  • NativeInt and NativeUint are now 64 bits, not 32.
  • Pointer and all pointer types are now 64 bits, not 32. That covers things such as String, AnsiString, WideString, UnicodeString, class instance, class reference, interface reference, procedure pointer, dynamic array, PAnsiChar, PWideChar, PChar. Remember that string variables are really pointers to reference-counted string data.
  • Dynamic arrays now use 64-bit indexing, not 32-bit.
  • Floating point math is done with type Double (the 64-bit float type) – 32-bit floating point math not supported (Single will map to Double).

Things to bear in mind:

  • The Tag property will be NativeInt, meaning you can still cast object instances and other pointers and store them in Tag at runtime. Note that Tag was originally added to allow unique identification of components which share events. Think: case (Sender as TComponent).Tag of ...
  • SizeOf(Pointer) <> SizeOf(Integer)
    • IntegerPointer casts will break in 64-bit
    • SizeOf(THandle) = SizeOf(Pointer)
    • All handles (HWND, HDC, etc.) are pointer-sized, i.e. they will be 64-bit in the 64-bit compiler

  • All code in the process must be 64-bit, including all dependent DLLs
  • There is only one calling convention (register, pascal, cdecl, stdcall will be ignored), although safecall is still handled as a special case
  • Old “pointer math” may break
    • This will work in 32-bit and 64-bit:
      MyPtr := PByte(P) + 10
  • Inline assembly
    • Only procedural level asm blocks are supported – asm blocks cannot be mixed with Pascal code
    • Stack must be 16-byte aligned at each call instruction
    • Define locals for temporary storage
    • Do not modify the RSP stack pointer
    • In the new unified calling convention the first 4 parameters are passed in registers: RCX, RDX, R8 & R9 (or XMM0-XMM3)

  • Exception unwinding
    • Pure Delphi code works as before
    • Inline assembly can cause exception unwinding to fail if not properly written

  • Windows API gotchas
    • SetWindowLong/GetWindowLong should be replaced with SetWindowLongPtr/GetWindowLongPtr for GWLP_HINSTANCE, GWLP_WNDPROC etc, because they return pointers and handles
      • Pointers passed to SetWindowLongPtr should cast to LONG_PTR rather than to Integer/Longint
    • SetWindowLong is mapped to SetWindowLongPtr in Windows.pas
      • calls to this mapped version are safe so long as they are cast correctly
    • Use explicit casts to WPARAM/LPARAM where appropriate
      • E.g. passing pointers through SendMessage:
        SendMessage(hWnd, WM_SETTEXT, 0, LPARAM(@MyCharArray));
    • Use LRESULT to cast message results
      • E.g. Message.Result := LRESULT(Self)
    • Message cracker record definitions (TWMxxx) have changed to accommodate altered alignment and field sizes

Things to do to your code today:

  • Find all IntegerPointer casts (including Integer ↔ instance casts)
  • Check for Pointer size assumptions
  • Ensure external dependencies are available in 64-bit
    • Image/bitmap libraries
    • Hardware interface libraries
    • ActiveX controls

  • Consider rewriting assembler code in pure Pascal
    • This offers better portability (think future ARM CPU support)
    • Rely more on algorithmic performance rather than raw assembly performance

To get a heads-up on some of the issues surrounding 64-bit programming on Windows you read:

Finally, if you are a Delphi XE user you get priority access to the Delphi 64-bit beta. Non-XE users can also get on the beta, but there will be limited places, and XE users will be given priority. More information about the beta program is available here.