Watch, Follow, &
Connect with Us

Please visit our new home
community.embarcadero.com.


Welcome, Guest
Guest Settings
Help

Thread: Syntax support for 'record' type in Delphi XE2


This question is answered.


Permlink Replies: 6 - Last Post: Jun 21, 2016 3:04 AM Last Post By: Rudy Velthuis (...
Ronald Hoek

Posts: 22
Registered: 7/1/01
Syntax support for 'record' type in Delphi XE2  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 10, 2016 3:11 AM
Hello everyone,

I was trying something new (from my point) in Delphi XE2 with record types. My idea was to use a record based type to parse some data into an array of record structures (see code below), which would be an private record variable. Then I added some functions en properties toe the record to expose the data.
I know this can be done using an object, but I did nog want to bother with the cleanup of the object, as the data referenced by the structures, was only based on simple data types and an array of a record containting simple types.

But, in some cases when I use this record base type, I get horrible AV's (probebly memory corruption).
SO my question is: is the syntax I used (it compiles/works) officially suport bij *XE2*? Or maybe it still was (or is) a bit buggy using records this way?

Unit code:

unit BorderInfoU;
 
interface
 
type
  TFpBorder = (fpbLeft, fpbRight, fpbTop, fpbBottom, fpbOutline);
  TFpBorders = set of TFpBorder;
 
  TFpBorderStyle = (
    fpbsNone,
    fpbsSolid, fpbsDash, fpbsDot, fpbsDashDot, fpbsDashDotDot,
    fpbsBlank,
    fpbsFineSolid, fpbsFineDash, fpbsFineDot, fpbsFineDashDot, fpbsFineDashDotDot);
 
  TFpBorderInfoItem = packed record
  private
    FBorder: TFpBorders;
    FColor : Integer;
    FStyle : TFpBorderStyle;
  public
    constructor Create(aBorder, aStyle, aColor: Integer); overload;
    constructor Create(s: string); overload;
    constructor Create(aBorder: TFpBorders; aStyle: TFpBorderStyle; aColor:
        Integer); overload;
    class function Empty: TFpBorderInfoItem; static;
    function FpBorder: Integer;
    function FpStyle: Integer;
    function ToString: string;
    property Border: TFpBorders read FBorder;
    property Color : Integer read FColor;
    property Style : TFpBorderStyle read FStyle;
  end;
 
  TFpBorderInfo = packed record
  private
    FCount: Integer;
    FItems: array of TFpBorderInfoItem;
    FSize: Integer;
    function GetItem(Index: Integer): TFpBorderInfoItem;
    procedure SetSize(aSize: Integer);
  public
    constructor Create(s: string); overload;
    constructor Create(const aItems: array of TFpBorderInfoItem); overload;
    class function Empty: TFpBorderInfo; static;
    property Count: Integer read FCount;
    property Item[Index: Integer]: TFpBorderInfoItem read GetItem; default;
  end;
 
 
implementation
 
uses
  System.SysUtils, System.RTLConsts;
 
const
{/// CellBorderStyle property settings }
  SS_BORDER_STYLE_DEFAULT = 0;
  SS_BORDER_STYLE_SOLID = 1;
  SS_BORDER_STYLE_DASH = 2;
  SS_BORDER_STYLE_DOT = 3;
  SS_BORDER_STYLE_DASH_DOT = 4;
  SS_BORDER_STYLE_DASH_DOT_DOT = 5;
  SS_BORDER_STYLE_BLANK = 6;
  SS_BORDER_STYLE_FINE_SOLID = 11;
  SS_BORDER_STYLE_FINE_DASH = 12;
  SS_BORDER_STYLE_FINE_DOT = 13;
  SS_BORDER_STYLE_FINE_DASH_DOT = 14;
  SS_BORDER_STYLE_FINE_DASH_DOT_DOT = 15;
 
{/// CellBorderType property settings }
  SS_BORDER_TYPE_NONE = 0;
  SS_BORDER_TYPE_LEFT = 1;
  SS_BORDER_TYPE_RIGHT = 2;
  SS_BORDER_TYPE_TOP = 4;
  SS_BORDER_TYPE_BOTTOM = 8;
  SS_BORDER_TYPE_OUTLINE = 16;
 
{/// SelBackColor property settings }
  SPREAD_COLOR_NONE = $8000000B;
 
const
  SS_BORDER_STYLE: array[TFpBorderStyle] of integer = (
    SS_BORDER_STYLE_DEFAULT          ,
    SS_BORDER_STYLE_SOLID            ,
    SS_BORDER_STYLE_DASH             ,
    SS_BORDER_STYLE_DOT              ,
    SS_BORDER_STYLE_DASH_DOT         ,
    SS_BORDER_STYLE_DASH_DOT_DOT     ,
    SS_BORDER_STYLE_BLANK            ,
    SS_BORDER_STYLE_FINE_SOLID       ,
    SS_BORDER_STYLE_FINE_DASH        ,
    SS_BORDER_STYLE_FINE_DOT         ,
    SS_BORDER_STYLE_FINE_DASH_DOT    ,
    SS_BORDER_STYLE_FINE_DASH_DOT_DOT );
 
constructor TFpBorderInfoItem.Create(aBorder, aStyle, aColor: Integer);
begin
  if (aBorder < SS_BORDER_TYPE_NONE) or (aBorder > 31) then
    raise Exception.CreateFmt('Invalid linespecification: %d', [aBorder]);
 
  FBorder := [];
  if (aBorder <> SS_BORDER_TYPE_OUTLINE) then
  begin
    if (aBorder and SS_BORDER_TYPE_LEFT)    <> 0 then Include(FBorder, fpbLeft);
    if (aBorder and SS_BORDER_TYPE_RIGHT)   <> 0 then Include(FBorder, fpbRight);
    if (aBorder and SS_BORDER_TYPE_TOP)     <> 0 then Include(FBorder, fpbTop);
    if (aBorder and SS_BORDER_TYPE_BOTTOM)  <> 0 then Include(FBorder, fpbBottom);
    if (aBorder and SS_BORDER_TYPE_OUTLINE) <> 0 then Include(FBorder, fpbOutline);
  end;
 
  case aStyle of
    SS_BORDER_STYLE_DEFAULT          : FStyle := fpbsNone ;
    SS_BORDER_STYLE_SOLID            : FStyle := fpbsSolid ;
    SS_BORDER_STYLE_DASH             : FStyle := fpbsDash ;
    SS_BORDER_STYLE_DOT              : FStyle := fpbsDot ;
    SS_BORDER_STYLE_DASH_DOT         : FStyle := fpbsDashDot ;
    SS_BORDER_STYLE_DASH_DOT_DOT     : FStyle := fpbsDashDotDot ;
    SS_BORDER_STYLE_BLANK            : FStyle := fpbsBlank ;
    SS_BORDER_STYLE_FINE_SOLID       : FStyle := fpbsFineSolid ;
    SS_BORDER_STYLE_FINE_DASH        : FStyle := fpbsFineDash ;
    SS_BORDER_STYLE_FINE_DOT         : FStyle := fpbsFineDot ;
    SS_BORDER_STYLE_FINE_DASH_DOT    : FStyle := fpbsFineDashDot ;
    SS_BORDER_STYLE_FINE_DASH_DOT_DOT: FStyle := fpbsFineDashDotDot ;
  else
    raise Exception.CreateFmt('Invalid linestyle: %d', [aStyle]);
  end;
 
  FColor := aColor;
end;
 
{ TFpBorderInfoItem }
 
constructor TFpBorderInfoItem.Create(s: string);
var
  iBorder: Integer;
  iPos: Integer;
  iStyle: Integer;
begin
  iPos := Pos(' ', s);
  if iPos = 0 then Abort;
  iBorder := StrToInt(Copy(s, 1, iPos - 1));
  Delete(s, 1, iPos);
 
  iPos := Pos(' ', s);
  if iPos = 0 then Abort;
  iStyle := StrToInt(Copy(s, 1, iPos - 1));
  Delete(s, 1, iPos);
 
  Create(iBorder, iStyle, StrToInt(s));
end;
 
constructor TFpBorderInfoItem.Create(aBorder: TFpBorders; aStyle: TFpBorderStyle;
    aColor: Integer);
begin
  FBorder := aBorder;
  FStyle := aStyle;
  FColor := aColor;
end;
 
class function TFpBorderInfoItem.Empty: TFpBorderInfoItem;
begin
  Result.FBorder := []; // SS_BORDER_TYPE_NONE
  Result.FStyle  := fpbsNone; // SS_BORDER_STYLE_DEFAULT
  Result.FColor  := SPREAD_COLOR_NONE;
end;
 
function TFpBorderInfoItem.FpBorder: Integer;
begin
  Result := Byte((@Border)^); // Bit-mask omzetten naar 'integer'
end;
 
function TFpBorderInfoItem.FpStyle: Integer;
begin
  Result := SS_BORDER_STYLE[Style];
end;
 
function TFpBorderInfoItem.ToString: string;
begin
  Result := Format('%d %d %d', [FpBorder, FpStyle, Color]);
end;
 
{ TFpBorderInfo }
 
constructor TFpBorderInfo.Create(s: string);
var
  iPos: Integer;
begin
  //RH: updated code to fix AV-  problem
  FillChar(Self, SizeOf(Self), 0);    // <========= THIS LINE IS THE ACTUAL FIX!!!!
 
  // Array?
  iPos := Pos(';', s);
  if iPos > 0 then
  begin
    SetSize(4); // Initieel 4 items
    repeat
      FItems[FCount] := TFpBorderInfoItem.Create(Copy(s, 1, iPos-1));
      Inc(FCount);
      Delete(s, 1, iPos);
      iPos := Pos(';', s);
      if FCount = FSize then
        SetSize(FSize + 4);
    until (iPos = 0);
  end else
    SetLength(FItems, 1); // Maar één item
 
  // Laatste item toevoegen
  FItems[FCount] := TFpBorderInfoItem.Create(s);
  Inc(FCount);
end;
 
constructor TFpBorderInfo.Create(const aItems: array of TFpBorderInfoItem);
var
  I: Integer;
begin
  //RH: updated code to fix AV-  problem
  FillChar(Self, SizeOf(Self), 0);    // <========= THIS LINE IS THE ACTUAL FIX!!!!
 
  SetSize(Length(aItems));
  for I := 0 to FSize - 1 do
    FItems[I] := aItems[I];
  FCount := FSize;
end;
 
class function TFpBorderInfo.Empty: TFpBorderInfo;
begin
  FillChar(Result, SizeOf(Result), 0);
end;
 
function TFpBorderInfo.GetItem(Index: Integer): TFpBorderInfoItem;
begin
  if Cardinal(Index) >= Cardinal(FCount) then
    Raise Exception.CreateResFmt(@SListIndexError, [Index]);
  Result := FItems[Index];
end;
 
procedure TFpBorderInfo.SetSize(aSize: Integer);
begin
  if aSize <> FSize then
  begin
    FSize := aSize;
    SetLength(FItems, aSize);
  end;
end;
 
end.


Edited by: Ronald Hoek on Jun 10, 2016 3:11 AM

Added code to fix the AV problems, see:

FillChar(Self, SizeOf(Self), 0); //RH: updated code to fix AV- problem
Rudy Velthuis (...


Posts: 7,684
Registered: 9/22/99
Re: Syntax support for 'record' type in Delphi XE2 [Edit]
Correct
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 10, 2016 4:19 AM   in response to: Ronald Hoek in response to: Ronald Hoek
Ronald Hoek wrote:

Hello everyone,

I was trying something new (from my point) in Delphi XE2 with record
types. My idea was to use a record based type to parse some data into
an array of record structures (see code below), which would be an
private record variable. Then I added some functions en properties
toe the record to expose the data. I know this can be done using an
object, but I did nog want to bother with the cleanup of the object,
as the data referenced by the structures, was only based on simple
data types and an array of a record containting simple types.

But, in some cases when I use this record base type, I get horrible
AV's (probebly memory corruption). SO my question is: is the syntax
I used (it compiles/works) officially suport bij XE2?

If the syntax were not supported, it would not compile.

I guess you have issues with uninitialized member fields. Only managed
member fields are initialized to nil (or equivalent). Other member
fields can have any kind of value. In my record types, I make sure that
I always check the managed fields before I access the non-managed ones.
If the managed field I check is nil, I know that the other fields may
be wrong and don't use them, or reset them to default values.

I didn't read your long piece of code, but in my BigInteger record
type, I don't use the value of the Size field if the dynamic array
containing the numeric data is nil. Nil means no data, so the Size must
be set to 0. I check this everywhere it is required. If not, I'm sure I
would get AVs or worse.
--
Rudy Velthuis http://www.rvelthuis.de

"Statistically one hundred percent of the shots you don't take
don't go in."
-- Wayne Gretsky
Linden ROTH

Posts: 467
Registered: 11/3/11
Re: Syntax support for 'record' type in Delphi XE2 [Edit]
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 10, 2016 3:20 PM   in response to: Rudy Velthuis (... in response to: Rudy Velthuis (...
Rudy Velthuis (TeamB) wrote:
Ronald Hoek wrote:

I didn't read your long piece of code, but in my BigInteger record
type, I don't use the value of the Size field if the dynamic array
containing the numeric data is nil. Nil means no data, so the Size must
be set to 0. I check this everywhere it is required. If not, I'm sure I
would get AVs or worse.
--
Rudy Velthuis http://www.rvelthuis.de


Rudy is right Initialize EVERYTHING in both TFpBorderInfo.Create set FSize to 0 BEFORE ANY THING (because if the last time this memory contained 4 it won't work

Also you use you setsize function sometimes and setlength other times be consistent

eg

        SetSize(FSize + 4);
    until (iPos = 0);
  end else
    SetLength(FItems, 1); // After this line fSize is undefined as is fCount !!!!!!!!
 
  // Laatste item toevoegen
  FItems[FCount] := TFpBorderInfoItem.Create(s); 
  Inc(FCount);


should be

  fSize := 0;
  FCount := 0;
 
 
...
 
        SetSize(FSize + 4);
    until (iPos = 0);
  end else
    SetSize( 1 ); //better
 
  // Laatste item toevoegen
  FItems[FCount] := TFpBorderInfoItem.Create(s);
  Inc(FCount);


--
Linden
"Mango" was Cool but "Wasabi" was Hotter but remember it's all in the "source"
Ronald Hoek

Posts: 22
Registered: 7/1/01
Re: Syntax support for 'record' type in Delphi XE2 [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 20, 2016 12:48 AM   in response to: Rudy Velthuis (... in response to: Rudy Velthuis (...
Thank Rudy (and Linden),

I now init the record in the 'Crete' statements en now I don't get AV's!
I thought al members of een record where initialized when it was 'created', but this was my confusion regarding classes, where private field get initialized!

Normally I always init records, when these are locally declares (var), but I never initialize them, when they are used as a private variabel of a class.

Basicly when using this syntax to 'create' a record I need to assume the same initializing requirements as local vars.
(As you can see, I did is with the 'Empty' function of the record ;)
Remy Lebeau (Te...


Posts: 9,219
Registered: 12/23/01
Re: Syntax support for 'record' type in Delphi XE2 [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 20, 2016 12:06 PM   in response to: Ronald Hoek in response to: Ronald Hoek
Ronald wrote:

I thought al members of een record where initialized when it was
'created'

Only compiler-managed types (strings, interfaces, variants, etc) are auto-initialized
in a record. Your record does not contain any managed types, so there is
no auto-initialization.

but this was my confusion regarding classes, where private field get initialized!

When an instance of a class is allocated, the allocated memory block is filled
with zeroes before the class constructor is called. The same is not true
for records.

Normally I always init records, when these are locally declares (var),
but I never initialize them, when they are used as a private variabel
of a class.

Because the initialization of the class implicitly zeroes out any records
that are members of the class.

Basicly when using this syntax to 'create' a record I need to assume
the same initializing requirements as local vars.

Never assume the contents of a record are auto-initialized, except for compiler-managed
types.

--
Remy Lebeau (TeamB)
Rudy Velthuis (...


Posts: 7,684
Registered: 9/22/99
Re: Syntax support for 'record' type in Delphi XE2 [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 21, 2016 2:38 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:

Ronald wrote:

I thought al members of een record where initialized when it was
'created'

Only compiler-managed types (strings, interfaces, variants, etc) are
auto-initialized in a record. Your record does not contain any
managed types, so there is no auto-initialization.

but this was my confusion regarding classes, where private field
get initialized!

When an instance of a class is allocated, the allocated memory block
is filled with zeroes before the class constructor is called. The
same is not true for records.

True. But especially because records are not always explicitly created
and allocated on the heap, like class instances are, but often simply
reside in the stack frame of a function, records can contain garbage.
It is up to the user to initialize them.

--
Rudy Velthuis http://www.rvelthuis.de

"Only two things are infinite, the universe and human
stupidity, and I'm not sure about the former."
-- Albert Einstein
Rudy Velthuis (...


Posts: 7,684
Registered: 9/22/99
Re: Syntax support for 'record' type in Delphi XE2 [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jun 21, 2016 3:04 AM   in response to: Ronald Hoek in response to: Ronald Hoek
Ronald Hoek wrote:

TFpBorderInfo = packed record
private
FCount: Integer;
FItems: array of TFpBorderInfoItem;

<snip>

{ TFpBorderInfo }

constructor TFpBorderInfo.Create(s: string);
var
iPos: Integer;
begin
//RH: updated code to fix AV- problem
FillChar(Self, SizeOf(Self), 0); // <========= THIS LINE IS THE
ACTUAL FIX!!!!

Actually, this is not a good fix, IMO. FpBorderInfo contains a managed
type (a dynamic array). You should generally not zero out such records
(although in this case, it won't do any damage).

The best way to initialize such a record is simply to initialize EVERY
field to a valid value. Then there is no need to zero out the record.
Here, you can simply do:

constructor TFpBorderInfo.Create(const S: string);
var
iPos: Integer;
begin
// Initieel, geen items in het array
FCount := 0; // THIS MUST BE SET TO 0, NO MORE.

// String scannen en items aan array toevoegen
iPos := Pos(';', s);
if iPos > 0 then
begin
SetSize(4); // Initieel 4 items
repeat
FItems[FCount] := TFpBorderInfoItem.Create(Copy(s, 1, iPos-1));
Inc(FCount);
Delete(s, 1, iPos);
iPos := Pos(';', s);
if FCount = FSize then
SetSize(FSize + 4);
until (iPos = 0);
end else
SetLength(FItems, 1); // Maar één item

// Laatste item toevoegen
FItems[FCount] := TFpBorderInfoItem.Create(s);
Inc(FCount);

And FWIW, why do you use packed records? Is there a need to be binary
compatible with structures elsewhere? If not, use normal, aligned
records.

And if you pass strings, use const if you can. That avoids reference
counting, which makes your code faster.

--
Rudy Velthuis http://www.rvelthuis.de

"Black holes are where God divided by zero."
- Steven Wright
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02