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
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.
Filed under:
Delphi,
Delphi 2006,
Delphi XE2,
Delphi XE3,
Development,
Software Development