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