The Wiert Corner – irregular stream of stuff

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

  • 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,913 other followers

Porting Delphi 2006 to XE2, fields versus properties: tightened compiler error E2197

Posted by jpluimers on 2013/03/19

When porting some communications code that used records as properties from Delphi 2006 to Delphi XE2, I came across a tightened compiler error “E2197 Constant object cannot be passed as var parameter“.

Let me first explain the good old occurrence of E2197 with some code that uses my last Variant Records example:

Just look at TPacket.InitializePacket and TPacketBase.InitializeFPacket: Basically even though the Packet property has storage specifiers indicating it directly reads from a field and directly writes to a field, you cannot pass it as a var parameter in the FillChar method.

Of course you can with a field, you can pass it to FillChar without trouble as TPacketBase.InitializeFPacket shows.

unit PacketWrapperUnit;

interface

uses
  VariantRecordUnit;

type
  TPacketBase = class(TObject)
  strict private
    FPacket: TPacket;
  strict protected
    procedure InitializeFPacket; virtual;
  public
    property Packet: TPacket read FPacket write FPacket;
  end;

  TPacketWrapper = class(TPacketBase)
  strict private
    procedure InitializePacket;
  end;

implementation

procedure TPacketBase.InitializeFPacket;
begin
  FillChar(FPacket, SizeOf(FPacket), MarkerChar);
end;

constructor TPacketWrapper.Create;
begin
  inherited;
  InitializeFPacket();
end;

procedure TPacketWrapper.InitializePacket;
begin
//[Pascal Error] PacketWrapperUnit.pas(74): E2197 Constant object cannot be passed as var parameter
//  FillChar(Packet, $AA, SizeOf(Packet));
  InitializeFPacket(); // alternative: use the field
end;

end.

Since TPacket is a record, and often you only want to initialize some fields, I have added the methods InitializeFPacketData1..3 to TPacketBase and InitializePacketData1..3 to TPacket with various gradations of with depth.

Since the methods in TPackketBase methods all refer the FPacket field, they all compile.

The methods in TPackket methods all refer the Packet property, you might suspect they won’t compile, but they all do except for InitializePacketData3.

unit PacketWrapperUnit;

interface

uses
  VariantRecordUnit;

type
  TPacketBase = class(TObject)
  strict private
    FPacket: TPacket;
  strict protected
    procedure InitializeFPacket; virtual;
  public
    procedure InitializeFPacketData1; virtual;
    procedure InitializeFPacketData2; virtual;
    procedure InitializeFPacketData3; virtual;
    property Packet: TPacket read FPacket write FPacket;
  end;

  TPacketWrapper = class(TPacketBase)
  strict private
    procedure InitializePacket;
  public
    constructor Create;
    procedure InitializePacketData1; virtual;
    procedure InitializePacketData2; virtual;
    procedure InitializePacketData3; virtual;
  end;

implementation

procedure TPacketBase.InitializeFPacket;
begin
  FillChar(FPacket, SizeOf(FPacket), MarkerChar);
end;

procedure TPacketBase.InitializeFPacketData1;
begin
  InitializeFPacket();
  with FPacket do
    FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar);
  with FPacket do
    FillChar(Data.Zero, SizeOf(Data.Zero), NullChar);
end;

procedure TPacketBase.InitializeFPacketData2;
begin
  InitializeFPacket();
  with FPacket.Data do
    FillChar(Contents, SizeOf(Contents), SpaceChar);
  with FPacket.Data do
    FillChar(Zero, SizeOf(Zero), NullChar);
end;

procedure TPacketBase.InitializeFPacketData3;
begin
  InitializeFPacket();
  FillChar(FPacket.Data.Contents, SizeOf(FPacket.Data.Contents), SpaceChar);
  FillChar(FPacket.Data.Zero, SizeOf(FPacket.Data.Zero), NullChar);
end;

constructor TPacketWrapper.Create;
begin
  inherited;
  InitializeFPacket();
end;

procedure TPacketWrapper.InitializePacket;
begin
//[Pascal Error] PacketWrapperUnit.pas(74): E2197 Constant object cannot be passed as var parameter
//  FillChar(Packet, $AA, SizeOf(Packet));
  InitializeFPacket();
end;

procedure TPacketWrapper.InitializePacketData1;
begin
  InitializePacket();
  with Packet do
    FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar);
  with Packet do
    FillChar(Data.Zero, SizeOf(Data.Zero), NullChar);
end;

procedure TPacketWrapper.InitializePacketData2;
begin
  InitializePacket();
  with Packet.Data do
    FillChar(Contents, SizeOf(Contents), SpaceChar);
  with Packet.Data do
    FillChar(Zero, SizeOf(Zero), NullChar);
end;

procedure TPacketWrapper.InitializePacketData3;
begin
  InitializePacket();
//[Pascal Error] PacketWrapperUnit.pas(101): E2197 Constant object cannot be passed as var parameter
//[Pascal Error] PacketWrapperUnit.pas(102): E2197 Constant object cannot be passed as var parameter
//  FillChar(Packet.Data.Contents, SizeOf(Packet.Data.Contents), SpaceChar);
//  FillChar(Packet.Data.Zero, SizeOf(Packet.Data.Zero), NullChar);
end;

end.

So: you can work around error “E2197 Constant object cannot be passed as var parameter” by clerverly(?) using with.

There is a better solution as of Delphi 2005 (CompilerVersion 17): you now have records on methods, so you can work around it by putting the initialization logic there:

unit VariantRecordUnit;

interface

{...}
  TPacket = packed record
    EntryType : Byte;
    ReturnKey : TVariantKey;
    DataType  : Byte;
    Data      : TVariantData;
{$if CompilerVersion >= 17}
  public
    procedure InitializeData;
{$ifend CompilerVersion >= 17}
  end;

const
  MarkerChar = #$AA;
  NullChar = #$00;
  SpaceChar = #$20;

implementation

{$if CompilerVersion >= 17}
procedure TPacket.InitializeData;
begin
  FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar);
  FillChar(Data.Zero, SizeOf(Data.Zero), NullChar);
end;
{$ifend CompilerVersion >= 17}

end.

The cool thing is that before Delphi 2005, the old code used to work, so now you can change the calling code to become this and have it work in all Delphi versions:

unit PacketWrapperUnit;

{...}

procedure TPacketWrapper.InitializePacketData3;
begin
  InitializePacket();
{$if CompilerVersion >= 17}
  Packet.InitializeData();
{$else}
//[Pascal Error] PacketWrapperUnit.pas(104): E2197 Constant object cannot be passed as var parameter
//[Pascal Error] PacketWrapperUnit.pas(105): E2197 Constant object cannot be passed as var parameter
  FillChar(Packet.Data.Contents, SizeOf(Packet.Data.Contents), SpaceChar);
  FillChar(Packet.Data.Zero, SizeOf(Packet.Data.Zero), NullChar);
{$ifend CompilerVersion >= 17}
end;

end.

When porting methods like InitializePacketData1..2 to TPacket to Delphi XE2, I found out that those too will cause the E2197 error. Som the various gradations of with depth trick doesn’t work any more, and you have to use these methods on records.

unit PacketWrapperUnit;

interface

uses
  VariantRecordUnit;

type

  TPacketBase = class(TObject)
  strict private
    FPacket: TPacket;
  strict protected
    procedure InitializeFPacket; virtual;
  public
    procedure InitializeFPacketData1; virtual;
    procedure InitializeFPacketData2; virtual;
    procedure InitializeFPacketData3; virtual;
    property Packet: TPacket read FPacket write FPacket;
  end;

  TPacketWrapper = class(TPacketBase)
  strict private
    procedure InitializePacket;
  public
    constructor Create;
    procedure InitializePacketData1; virtual;
    procedure InitializePacketData2; virtual;
    procedure InitializePacketData3; virtual;
  end;

implementation

procedure TPacketBase.InitializeFPacket;
begin
  FillChar(FPacket, SizeOf(FPacket), MarkerChar);
end;

procedure TPacketBase.InitializeFPacketData1;
begin
  InitializeFPacket();
  with FPacket do
    FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar);
  with FPacket do
    FillChar(Data.Zero, SizeOf(Data.Zero), NullChar);
end;

procedure TPacketBase.InitializeFPacketData2;
begin
  InitializeFPacket();
  with FPacket.Data do
    FillChar(Contents, SizeOf(Contents), SpaceChar);
  with FPacket.Data do
    FillChar(Zero, SizeOf(Zero), NullChar);
end;

procedure TPacketBase.InitializeFPacketData3;
begin
  InitializeFPacket();
  FillChar(FPacket.Data.Contents, SizeOf(FPacket.Data.Contents), SpaceChar);
  FillChar(FPacket.Data.Zero, SizeOf(FPacket.Data.Zero), NullChar);
end;

constructor TPacketWrapper.Create;
begin
  inherited;
  InitializeFPacket();
end;

procedure TPacketWrapper.InitializePacket;
begin
//[Pascal Error] PacketWrapperUnit.pas(74): E2197 Constant object cannot be passed as var parameter
//  FillChar(Packet, $AA, SizeOf(Packet));
  InitializeFPacket();
end;

procedure TPacketWrapper.InitializePacketData1;
begin
  InitializePacket();
  with Packet do
    FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar); //[Pascal Error] PacketWrapperUnit.pas(82): E2197 Constant object cannot be passed as var parameter
  with Packet do
    FillChar(Data.Zero, SizeOf(Data.Zero), NullChar); //[Pascal Error] PacketWrapperUnit.pas(84): E2197 Constant object cannot be passed as var parameter
end;

procedure TPacketWrapper.InitializePacketData2;
begin
  InitializePacket();
  with Packet.Data do
    FillChar(Contents, SizeOf(Contents), SpaceChar); //[Pascal Error] PacketWrapperUnit.pas(91): E2197 Constant object cannot be passed as var parameter
  with Packet.Data do
    FillChar(Zero, SizeOf(Zero), NullChar); //[Pascal Error] PacketWrapperUnit.pas(93): E2197 Constant object cannot be passed as var parameter
end;

procedure TPacketWrapper.InitializePacketData3;
begin
  InitializePacket();
{$if CompilerVersion >= 17}
  Packet.InitializeData();
{$else}
//[Pascal Error] PacketWrapperUnit.pas(104): E2197 Constant object cannot be passed as var parameter
//[Pascal Error] PacketWrapperUnit.pas(105): E2197 Constant object cannot be passed as var parameter
  FillChar(Packet.Data.Contents, SizeOf(Packet.Data.Contents), SpaceChar);
  FillChar(Packet.Data.Zero, SizeOf(Packet.Data.Zero), NullChar);
{$ifend CompilerVersion >= 17}
end;

end.

I think this change was introduced in Delphi 2009, and the PacketWrapperUnit code below shows how to workaround it.

Finally, it is important that you check the various mechanisms to see if they do initialization correctly (for instance: that the initialization of the record is not done on a copy of the record, but actually on the record field itself).

That’s what this tiny main program does (you can of course make this into a Unit Test using DUnit).

–jeroen

Complete source code

Main program

program D2006XE2MigrationExamples;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  VariantRecordUnit in 'VariantRecordUnit.pas',
  PacketWrapperUnit in 'PacketWrapperUnit.pas',
  Classes;

procedure Test(const Context: string; const PacketWrapper: TPacketWrapper; const Method: TThreadMethod);
var
  Character: TChar;
begin
  Writeln(Context);
  Method();
  for Character in PacketWrapper.Packet.Data.Contents do
  begin
    Writeln(Character, Byte(Character));
  end;
end;

var
  PacketWrapper: TPacketWrapper;
begin
  PacketWrapper := TPacketWrapper.Create();
  try
    Test('InitializeFPacketData1', PacketWrapper, PacketWrapper.InitializeFPacketData1);
    Test('InitializeFPacketData2', PacketWrapper, PacketWrapper.InitializeFPacketData2);
    Test('InitializeFPacketData3', PacketWrapper, PacketWrapper.InitializeFPacketData3);
    Test('InitializePacketData1', PacketWrapper, PacketWrapper.InitializePacketData1);
    Test('InitializePacketData2', PacketWrapper, PacketWrapper.InitializePacketData2);
    Test('InitializePacketData3', PacketWrapper, PacketWrapper.InitializePacketData3);
  finally
    PacketWrapper.Free;
  end;
end.

PacketWrapperUnit:

unit PacketWrapperUnit;

interface

uses
  VariantRecordUnit;

type

  TPacketBase = class(TObject)
  strict private
    FPacket: TPacket;
  strict protected
    procedure InitializeFPacket; virtual;
  public
    procedure InitializeFPacketData1; virtual;
    procedure InitializeFPacketData2; virtual;
    procedure InitializeFPacketData3; virtual;
    property Packet: TPacket read FPacket write FPacket;
  end;

  TPacketWrapper = class(TPacketBase)
  strict private
    procedure InitializePacket;
  public
    constructor Create;
    procedure InitializePacketData1; virtual;
    procedure InitializePacketData2; virtual;
    procedure InitializePacketData3; virtual;
  end;

implementation

procedure TPacketBase.InitializeFPacket;
begin
  FillChar(FPacket, SizeOf(FPacket), MarkerChar);
end;

procedure TPacketBase.InitializeFPacketData1;
begin
  InitializeFPacket();
  with FPacket do
    FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar);
  with FPacket do
    FillChar(Data.Zero, SizeOf(Data.Zero), NullChar);
end;

procedure TPacketBase.InitializeFPacketData2;
begin
  InitializeFPacket();
  with FPacket.Data do
    FillChar(Contents, SizeOf(Contents), SpaceChar);
  with FPacket.Data do
    FillChar(Zero, SizeOf(Zero), NullChar);
end;

procedure TPacketBase.InitializeFPacketData3;
begin
  InitializeFPacket();
  FillChar(FPacket.Data.Contents, SizeOf(FPacket.Data.Contents), SpaceChar);
  FillChar(FPacket.Data.Zero, SizeOf(FPacket.Data.Zero), NullChar);
end;

constructor TPacketWrapper.Create;
begin
  inherited;
  InitializeFPacket();
end;

procedure TPacketWrapper.InitializePacket;
begin
//[Pascal Error] PacketWrapperUnit.pas(74): E2197 Constant object cannot be passed as var parameter
//  FillChar(Packet, $AA, SizeOf(Packet));
  InitializeFPacket();
end;

procedure TPacketWrapper.InitializePacketData1;
begin
  InitializePacket();
{$if CompilerVersion >= 20}
  Packet.InitializeData(); // fix the E2197 tightening introduced in Delphi 2009
{$else}
  with Packet do
    FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar);
  with Packet do
    FillChar(Data.Zero, SizeOf(Data.Zero), NullChar);
{$ifend CompilerVersion >= 20}
end;

procedure TPacketWrapper.InitializePacketData2;
begin
  InitializePacket();
{$if CompilerVersion >= 20}
  Packet.InitializeData(); // fix the E2197 tightening introduced in Delphi 2009
{$else}
  with Packet.Data do
    FillChar(Contents, SizeOf(Contents), SpaceChar);
  with Packet.Data do
    FillChar(Zero, SizeOf(Zero), NullChar);
{$ifend CompilerVersion >= 20}
end;

procedure TPacketWrapper.InitializePacketData3;
begin
  InitializePacket();
{$if CompilerVersion >= 17}
  Packet.InitializeData();
{$else}
//[Pascal Error] PacketWrapperUnit.pas(104): E2197 Constant object cannot be passed as var parameter
//[Pascal Error] PacketWrapperUnit.pas(105): E2197 Constant object cannot be passed as var parameter
  FillChar(Packet.Data.Contents, SizeOf(Packet.Data.Contents), SpaceChar);
  FillChar(Packet.Data.Zero, SizeOf(Packet.Data.Zero), NullChar);
{$ifend CompilerVersion >= 17}
end;

end.

VariantRecordUnit:

unit VariantRecordUnit;

interface

{ First a few basic types}

const
  TGuidStringSize = 38;

type
  TChar = AnsiChar; { single byte character, as it interfaces with DOS and CoolGen programs through C interface }
  TChar2    = array[0..   1] of TChar;
  TChar8    = array[0..   7] of TChar;
  TChar10   = array[0..   9] of TChar;
  TChar20   = array[0..  19] of TChar;
  T1Char33 = array[1..33] of TChar; { 1-based because the DOS CAS sources expect this }
  TGuidChar   = array[0..TGuidStringSize-1] of TChar;
  TMessageId = array[0..23] of Byte;

{ now the record types }

type
  TVariantData = record
  case Boolean of
    False: (
      ProgramName: TChar10;
      InterChangeFormat: TChar10;
      FunctionCode: TChar2;
      ReturnCode: TChar2;
      ErrorCode: TChar2;
      Zero: TChar2);
    True: (Contents: T1Char33);
  end; { total: 33 bytes }

  TId = packed record
    NetBiosName: TChar20; { historically, as DOS app defined it wrongly }
    TimeStamp: TChar8; { HHMMSShh because a DOS directory name can be no longer than 8 characters }
  end; { total: 28 bytes }

  TVariantKey = packed record
  case Integer of
    0: ( // SNA
      ConversationId: TId; { 28 bytes }
      GuidChars: TGuidChar); { 38 bytes }
    2: ( // MQ
      ConversationIdFiller: TId;
      MessageId: TMessageID); // 24 bytes
  end; { total: 66 bytes }

  TPacket = packed record
    EntryType : Byte;
    ReturnKey : TVariantKey;
    DataType  : Byte;
    Data      : TVariantData;
{$if CompilerVersion >= 17}
  public
    procedure InitializeData;
{$ifend CompilerVersion >= 17}
  end;

const
  MarkerChar = #$AA;
  NullChar = #$00;
  SpaceChar = #$20;

implementation

{$if CompilerVersion >= 17}
procedure TPacket.InitializeData;
begin
  FillChar(Data.Contents, SizeOf(Data.Contents), SpaceChar);
  FillChar(Data.Zero, SizeOf(Data.Zero), NullChar);
end;
{$ifend CompilerVersion >= 17}

end.

3 Responses to “Porting Delphi 2006 to XE2, fields versus properties: tightened compiler error E2197”

  1. Joseph said

    My God, if ever there were a case for duck typing in Delphi, this is it…

  2. “With” magic!

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: