The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My work

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,308 other followers

Delphi: ^TNodeData incompatible with PNodeData – {$T+} versus {$T-}

Posted by jpluimers on 2017/11/14

In my Turbo Pascal days, I was fan of the {$T+} directive (now a.k.a TypedAddress on) as it would make the @ operator return typed pointers and the compiler would be more strict in forcing checks on pointer types to be compatible.

Not so much in Delphi any more, see the below comment on in a G+ thread on Delphi pointer type compatibility [WayBack] by David Millington from Embarcadero.

Basically we’re stuck with {$T-} which is a double edged sword:

  • on the one hand it forces you to ensure your typed pointers are always the same actual type (so you need to define a type like PNodeData once and use it everywhere)
  • on the other hand it still allows the generic Pointer type to be compatible with typed pointers which means people use that and will not have the benefit of typed pointer checking

David Millington:

So the question is why is PNodeData different to ^TNodeData? These two pages have the answer:

  • “Two distinct types are compatible if they satisfy at least one of the following conditions:
    … (snip)
  • One type is Pointer (an untyped pointer) and the other is any pointer type.
  • Both types are (typed) pointers to the same type and the {$T+} compiler directive is in effect.”

[WayBackhttp://docwiki.embarcadero.com/RADStudio/Berlin/en/Type_Compatibility_and_Identity_(Delphi)#Type_Compatibility

That line about $T+ is key; it’s the “Typed @ operator” compiler option which is off by default. “In the {$T-} state, distinct pointer types other than Pointer are incompatible (even if they are pointers to the same type)”. So with that off, PNodeData and ^TNodeData are different.

[WayBackhttp://docwiki.embarcadero.com/RADStudio/en/Type-checked_pointers_(Delphi)

The best solution is to define PNodeData, as you’ve done, and use it everywhere.

What David fails to mention there, and I added as a commen later is this:

The problem $T is that it’s a two-edged sword as it makes code that looks valid fail to compile because the compiler in the $T+ state (a.k.a. $TypedAddress on state) never got updated to:

  • handle @ to an array create a pointer to the element of that array
  • handle @ to resourcestrings to become PResStringRec (and likely more examples)

Which means the below programs fail to compile.

The alternative (adding way more {$T+} compatible overloads to the RTL/VCL/FMX) was never done either, so now we’re stuck with {$T-}

Array of char example:

program TypedAddressDirectiveWithCharArrays;

{$APPTYPE CONSOLE}

{$TypedAddress on}

uses
  Winapi.Windows;

var
  TimeZone: string;
  TZ: TTimeZoneInformation;

begin
  TimeZone := 'Coordinated Universal Time';
  StringToWideChar(TimeZone, @(TZ.StandardName), SizeOf(TZ.StandardName) div SizeOf(WideChar));
end.

(*

[dcc32 Error] TypedAddressDirectiveWithCharArrays.dpr(16): E2010 Incompatible types: 'PWideChar' and 'Pointer'

In the System unit:

implicit types:

  PWideChar = WideChar;
  PChar = PWideChar

explicit:

function StringToWideChar(const Source: UnicodeString; Dest: PWideChar; DestSize: Integer): PWideChar;



In the Winapi.Windows unit:

type
  WCHAR = WideChar;

  PTimeZoneInformation = ^TTimeZoneInformation;
  _TIME_ZONE_INFORMATION = record
    Bias: Longint;
    StandardName: array[0..31] of WCHAR;
    StandardDate: TSystemTime;
    StandardBias: Longint;
    DaylightName: array[0..31] of WCHAR;
    DaylightDate: TSystemTime;
    DaylightBias: Longint;
  end;
  TTimeZoneInformation = _TIME_ZONE_INFORMATION;
  TIME_ZONE_INFORMATION = _TIME_ZONE_INFORMATION;

*)

ResourceString example:

program TypedAddressDirectiveWithResourceStrings;

{$APPTYPE CONSOLE}

{$TypedAddress on}

uses
  System.SysConst,
  System.SysUtils;

procedure RangeError;
begin
  raise ERangeError.CreateRes(@SRangeError);
end;

begin
end.

(*
[dcc32 Error] TypedAddressDirectiveWithResourceStrings.dpr(13): E2250 There is no overloaded version of 'CreateRes' that can be called with these arguments


System unit:

type
  PResStringRec = ^TResStringRec;
  {$IF defined(EXTERNALLINKER)}
  TResStringRec = record
    Key: MarshaledAString;
  end;
  {$ELSE}
  TResStringRec = packed record
    // 32bit = 8 bytes
    // 64bit = 16 bytes
    Module: ^HMODULE;
    Identifier: NativeUint;
  end;
  {$ENDIF}


System.SysConst unit:

resourcestring
  SRangeError = 'Range check error';


SysUtils unit:

constructor Exception.CreateRes(ResStringRec: PResStringRec);
begin
  FMessage := LoadResString(ResStringRec);
end;


*)

Source: On Friday I had a brain fade moment (which I don’t seems to have recovered…

–jeroen

2 Responses to “Delphi: ^TNodeData incompatible with PNodeData – {$T+} versus {$T-}”

  1. rvelthuis said

    Well, your first example is wrong indeed. It expects a PWideChar, so it should be StringToWideChar(TimeZone, TZ.StandardName, SizeOf(TZ.StandardName) div SizeOf(WideChar)); and that compiles fine. I am pretty sure that the other problems can be solved similarly.

    • rvelthuis said

      Or actually even something as simple as: StringToWideChar(TimeZone, TZ.StandardName, Length(TZ.StandardName));. So don’t blame it on {$T+}, just on bad assumptions.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: