Reading/writing dynamic arrays of objects to a file - Delphi - delphi

Im trying to write some code which will read/write a dynamic array of objects to a file. The objects represent the structure of the java source code. I need to be able to scan the whole source code and gather information on Fields, Methods and Classes. I have an algorithm which does this and the result is kept in a structure of TFieldStruc, TMethodStruc and TClassStruc, all descendants of the TCoreStruc (a descendant of TObject). The java source code takes a couple of minutes to be scanned and have the virtual structure generated. Because of this my application scans all the source code once and saves it into a much more accessible format which is loaded when ever the IDE launches.
Is there a way (other than exporting the objects 'to string' and then re-creating them again when they are loaded) to stream the entire three arrays of TFieldStruc, TMethodStruc and TClassStruc, to a file so they can be read later?
I have tried reading and writing to a 'File of TFieldStruc..' and the TFileStream to save the objects to a file and read them back, but in both cases I get 'inaccessible value' in the debugger followed by an 'Access Violation' error as soon as the object is accessed again. If anyone has ideas on how to solve this problem it would be appreciated. Below is the code to TCodeStruc if any of its fields/methods may be causing issues:
type
TCoreStruc = class(TObject)
public
LowerPointer : integer;
HigherPointer : integer;
Line : integer;
Word : integer;
Char : integer;
CoreType : ansistring;
IsPublic : boolean;
IsPrivate : boolean;
IsStatic : boolean;
IsFinal : boolean;
Name : ansistring;
NestedStruc : TCoreStruc;
constructor Create(Name, CoreType : ansistring; NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean);
procedure UpdateValues(NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean);
procedure SetPosition(Line, Word, Char : integer);
end;

Here is an example using your structure.
A few notes about this:
There are lots of different ways to go about this. Take David Heffernan's advice and do some searches for serialization. I use the approach include below in one of my applications but other include using RTTI/Persistent objects to iterate the published properties of an object. There are libraries that will iterate object for you and do all the work.
You need to actually write out to the string the sizes of dynamic objects. That includes things like arrays and strings.
In my example each object knows how to read and write itself to the stream.
I use a struct for the fixed length parts of the object. That saves me from needing to write each data element individually.
Strings need to be written on their own to include their size (you could used a fixed length buffer like Delphi short strings but it is not that much work to write out a regular string. You need to decide what type of format you want string data written in. I picked UTF8 for my application.
For your other arrays you can write their data (including length) after the first array is written out. Sometime there will be a header section that includes the lengths for all the dynamic sections at the top, others will write the length write before the structure starts. The key part is to always write things in the same order and included somewhere it can be re-read how many there are.
There is no error checking or verification in the file structure below. If anything is different between the read and write it will blow up - probably with a stream read error.
Any change to the structures will cause old files to not be read properly. There are a number of ways to version a file to ensure you can still read old formats. Not included here.
In your application you would pass a TFileStream to the read and write function. I like to write the actual read/write functions with just a TStream. Then the object does not care where the data is coming from. It could be file, or it could already be in memory.
If you drop the following unit into a console application you should be able to add a call to Main and step through the example.
unit CoreStruct;
interface
uses Classes, Types;
type
TCoreStructData = packed record
LowerPointer : integer;
HigherPointer : integer;
Line : integer;
Word : integer;
Char : integer;
IsPublic : boolean;
IsPrivate : boolean;
IsStatic : boolean;
IsFinal : boolean;
HasNested: boolean;
end;
TCoreStruc = class(TObject)
private
FCoreData: TCoreStructData;
FNestedStruc : TCoreStruc;
procedure SetNestedStruc(AValue: TCoreStruc);
public
CoreType : String;
Name : String;
constructor Create(); overload;
procedure WriteToStream(Stream: TStream);
procedure ReadFromStream(Stream: TStream);
//constructor Create(Name, CoreType : ansistring; NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean); overload;
//procedure UpdateValues(NestedStruc : TCoreStruc; IsPublic, IsPrivate, IsStatic, IsFinal : boolean);
//procedure SetPosition(Line, Word, Char : integer);
property LowerPointer: integer read FCoreData.LowerPointer write FCoreData.LowerPointer;
property HigherPointer: integer read FCoreData.HigherPointer write FCoreData.HigherPointer;
property Line: integer read FCoreData.Line write FCoreData.Line;
property Word: integer read FCoreData.Word write FCoreData.Word;
property Char: integer read FCoreData.Char write FCoreData.Char;
property NestedStruc: TCoreStruc read FNestedStruc write SetNestedStruc;
end;
procedure Main();
implementation
function ReadUTF8StringFromStream(const Stream: TStream): String;
var
n: Integer;
Buffer8: Utf8String;
begin
Result := '';
Stream.ReadBuffer(n, SizeOf(n));
if n = 0 then
Exit;
SetLength(Buffer8, n);
Stream.ReadBuffer(Pointer(Buffer8)^, n);
Result := String(Buffer8);
end;
procedure WriteUtf8StringToStream(const Data: String; Stream: TStream);
var
Buffer8: Utf8String;
n: Integer;
begin
// When writing strings we need to make sure the length of the
// string is written out to the stream. That goes first so the
// reader knows how long the buffer is.
//
// You could you different formats to write to the file depending on
// needs. I like using UTF8 when writing out to file, but it does
// require an extra buffer copy when turning it back into a native
// Delphi unicode string.
Buffer8 := Utf8String(Data);
n := Length(Buffer8);
Stream.WriteBuffer(n, SizeOf(n));
Stream.WriteBuffer(Pointer(Buffer8)^, n);
end;
procedure Main();
var
Structs: array of TCoreStruc;
ArraySize: integer;
DataStream: TMemoryStream;
ArraySize_A: integer;
Structs_A: array of TCoreStruc;
i: integer;
begin
// Create and write some data
SetLength(Structs, 3);
Structs[0] := TCoreStruc.Create();
Structs[0].HigherPointer := 1;
Structs[0].Name := 'Testing';
Structs[0].NestedStruc := TCoreStruc.Create();
Structs[0].NestedStruc.HigherPointer := 100;
Structs[1] := TCoreStruc.Create();
Structs[1].HigherPointer := 2;
Structs[2] := TCoreStruc.Create();
Structs[2].HigherPointer := 3;
DataStream := TMemoryStream.Create();
// We need to start with the count we are writing out so
// the reader knows how many times to loop.
ArraySize := Length(Structs);
DataStream.WriteBuffer(ArraySize, SizeOf(integer));
for i := 0 to ArraySize - 1 do
begin
Structs[i].WriteToStream(DataStream);
end;
// Read the data into a new set of objects
DataStream.Position := 0;
DataStream.ReadBuffer(ArraySize_A, SizeOf(integer));
SetLength(Structs_A, ArraySize_A);
for i := 0 to ArraySize_A - 1 do
begin
Structs_A[i] := TCoreStruc.Create();
Structs_A[i].ReadFromStream(DataStream);
end;
end;
{ TCoreStruc }
constructor TCoreStruc.Create;
begin
Self.LowerPointer := 0;
Self.HigherPointer := 0;
Self.Line := 0;
Self.Word := 0;
Self.Char := 0;
Self.NestedStruc := nil;
end;
procedure TCoreStruc.WriteToStream(Stream: TStream);
begin
Stream.WriteBuffer(FCoreData, SizeOf(TCoreStructData));
WriteUtf8StringToStream(Name, Stream);
WriteUtf8StringToStream(CoreType, Stream);
if FCoreData.HasNested = true then
begin
FNestedStruc.WriteToStream(Stream)
end;
end;
procedure TCoreStruc.ReadFromStream(Stream: TStream);
begin
Stream.ReadBuffer(FCoreData, SizeOf(TCoreStructData));
Name := ReadUtf8StringFromStream(Stream);
Name := ReadUtf8StringFromStream(Stream);
if FCoreData.HasNested = true then
begin
FNestedStruc := TCoreStruc.Create();
FNestedStruc.ReadFromStream(Stream);
end;
end;
procedure TCoreStruc.SetNestedStruc(AValue: TCoreStruc);
begin
FNestedStruc := AValue;
if AValue = nil then
FCoreData.HasNested := false
else
FCoreData.HasNested := true;
end;
end.

Related

TClientDataSet Custom compare field function

I am using an in-memory TClientDataSet with a TStringField column which contains folders path (Delphi 7).
When I create an index on this column the order is not what I am looking for.
As an example I get :
c:\foo
c:\fôo\a
c:\foo\b
when I would like this order :
c:\foo
c:\foo\b
c:\fôo\a
So I searched a way to use my own compare field function.
Based on this RRUZ answer How to change the implementation (detour) of an externally declared function I tried the following :
type
TClientDataSetHelper = class(DBClient.TClientDataSet);
...
MyCDS : TClientDataSet;
...
// My custom compare field function
function FldCmpHack
(
iFldType : LongWord;
pFld1 : Pointer;
pFld2 : Pointer;
iUnits1 : LongWord;
iUnits2 : LongWord
): Integer; stdcall;
begin
// Just to test
Result := -1;
end;
...
---RRUZ code here---
...
procedure HookDataCompare;
begin
HookProc
(
(MyCDs as TClientDataSetHelper).DSBase.FldCmp, <== do not compile !!!
#FldCmpHack,
FldCmpBackup
);
end;
When I try to compile I get an error (MyCDs as TClientDataSetHelper).DSBase.FldCmp : not enough actual parameters
I do not understand why this does not compile. Could you please help me ?
Is it even possible to "detour" IDSBase.FldCmp in DSIntf.pas ? Am i totally wrong ?
Thank you
EDIT
Finally, thanks to Dsm answer, I transformed the TStringFieldcolumn into a TVarBytesField in order to avoid doubling the buffer. Plus, when a TVarBytesField is indexed the order is based on the bytes value so I get the order I want. For having all child folders after a parent folder and before the next parent folder (c:\foo.new after c:\foo\b), I patched TVarBytesFieldlike this :
TVarBytesField = class(DB.TVarBytesField)
protected
function GetAsString: string; override;
procedure GetText(var Text: string; DisplayText: Boolean); override;
procedure SetAsString(const Value: string); override;
end;
function TVarBytesField.GetAsString: string;
var
vBuffer : PAnsiChar;
vTaille : WORD;
vTexte : PAnsiChar;
vI : WORD;
begin
Result := '';
GetMem(vBuffer, DataSize);
try
if GetData(vBuffer) then
begin
vTaille := PWORD(vBuffer)^;
vTexte := vBuffer + 2;
SetLength(Result, vTaille);
for vI := 1 to vTaille do
begin
if vTexte^ = #2 then
begin
Result[vI] := '\';
end
else
begin
Result[vI] := vTexte^;
end;
Inc(vTexte);
end;
end;
finally
FreeMem(vBuffer);
end;
end;
procedure TVarBytesField.GetText(var Text: string; DisplayText: Boolean);
begin
Text := GetAsString;
end;
procedure TVarBytesField.SetAsString(const Value: string);
var
vBuffer : PAnsiChar;
vTaille : WORD;
vTexte : PAnsiChar;
vI : WORD;
begin
vBuffer := AllocMem(DataSize);
try
vTaille := WORD(Length(Value));
PWORD(vBuffer)^ := vTaille;
vTexte := vBuffer + 2;
for vI := 1 to vTaille do
begin
if Value[vI] = '\' then
begin
vTexte^ := #2
end
else
begin
vTexte^ := Value[vI];
end;
Inc(vTexte);
end;
SetData(vBuffer);
finally
FreeMem(vBuffer);
end;
end;
The message is telling you that FldCmp is a function, and it is expecting you to execute it, but it has not got enough parameters. I am sure that you already realised that and probably already tried to get the address of the function with the # (like you do for FldCmpHack) and found that that does not work.
The reason for that is, I am afraid, that FldCmp is not a normal function. DSBase is actually an interface, which will have been assigned (looking at the source code) by a class factory. What you actually need is the real function itself and for that you need the real object that the class factory creates. And I am sorry, but I can't see any realistic way of doing that.
However, the DSBase field is only created if it has not been assigned, so you could, in theory, create your own IDSBase interface object, which is the way this type of problem is meant to be handled. That is a lot of work, though, unless you know class that the class factory produces and can descend from that.
A sneakier alternative is to override the Translate property and create some sort of hash (perhaps by translating the ASCII codes to their HEX values) so that the database keeps them in the right order
TClientDataSetHelper = class(TClientDataSet)
public
function Translate(Src, Dest: PAnsiChar; ToOem: Boolean): Integer; override;
end;

how to read/write set enumeration from text file with delphi

I want to use SQL like condition in Delphi as
if VarI in SOMESET then
...
[SOMESET] to read from any text file.
Store few numbers in any text file like ini/txt and read it from file and replace it with set so that we can use in operator with if condition.
If I understand what you are asking correctly, the simple answer is yes. Your terminology may be a bit off - set means something quite specific in Delphi, but I don't think that is what you mean. Also, I don't think you are asking about the specifics of loading and saving so I have not included that. Instead I think you are asking how to allow 'in' to be defined in this specific case, and here is how
unit Unit1;
interface
uses
System.SysUtils;
type
TMySet = record
private
function GetEntry(const i: integer): integer;
public
Entries : array of integer;
// procedure LoadFromFile( const pFromFile : TFileName );
class operator in ( const pTest : integer; pMyset : TMyset ) : boolean;
property Entry[ const i : integer ] : integer
read GetEntry; default;
end;
implementation
{ TMySet }
function TMySet.GetEntry(const i: integer): integer;
begin
Result := Entries[ i ]; // allow default exceptions to occur
end;
class operator TMySet.in(const pTest: integer; pMyset: TMyset): boolean;
var
i: Integer;
begin
for i := Low(pMyset.Entries) to High(pMyset.Entries) do
begin
if i = pMyset[ i ] then
begin
Result := TRUE;
exit;
end;
end;
// else
Result := FALSE;
end;
end.
I hope I have understood your question, and that this helps.

Spring4d: How to "force" the container to believe a class implements an interface

I am using RemObjects DataAbstract along with Spring4d. RemObjects generates a SchemaServer_Intf.pas file that contains interfaces for every kind of table that exists in it's schema. It allows for "Strongly typed" datasets, allowing one to access a field using
(aDataSet as IMyDataSet).MyField := aValue
Here is a snapshot of one of the interfaces generated by DataAbstract
IEntiteType = interface(IDAStronglyTypedDataTable)
['{96B82FF7-D087-403C-821A-0323034B4B99}']
{ Property getters and setters }
function GetEntiteIdValue: String;
procedure SetEntiteIdValue(const aValue: String);
function GetEntiteIdIsNull: Boolean;
procedure SetEntiteIdIsNull(const aValue: Boolean);
function GetNameValue: WideString;
procedure SetNameValue(const aValue: WideString);
function GetNameIsNull: Boolean;
procedure SetNameIsNull(const aValue: Boolean);
function GetIsSystemValue: SmallInt;
procedure SetIsSystemValue(const aValue: SmallInt);
function GetIsSystemIsNull: Boolean;
procedure SetIsSystemIsNull(const aValue: Boolean);
{ Properties }
property EntiteId: String read GetEntiteIdValue write SetEntiteIdValue;
property EntiteIdIsNull: Boolean read GetEntiteIdIsNull write SetEntiteIdIsNull;
property Name: WideString read GetNameValue write SetNameValue;
property NameIsNull: Boolean read GetNameIsNull write SetNameIsNull;
property IsSystem: SmallInt read GetIsSystemValue write SetIsSystemValue;
property IsSystemIsNull: Boolean read GetIsSystemIsNull write SetIsSystemIsNull;
end;
Though, there is one problem. If you cast a dataTable like so:
aDataTable := IEntiteType(TDAMemDataTable.Create(nil));
You'll have an "Interface not supported error"
But, as soon as you do:
aDataTable.LogicalName := 'EntiteType';
aDataTable.BusinessRulesId := MyBusinessRuleID;
You can safely write
aDataTable := IEntiteType(TDAMemDataTable.Create(nil));
And you don't get any error.
So, with Spring4d, I thought of writing this in my registration unit:
aContainer.RegisterType<TDAMemDataTable>.Implements<IEntiteType>.DelegateTo(
function : TDAMemDataTable
var aDataTable : TDAMemDataTable;
begin
Result:= TDAMemDataTable.Create(nil);
Result.LogicalName := 'EntiteType';
Result.BusinessRulesId := MyBusinessRuleId;
end
)
But then, Spring4d throws (with reason) error :
Exception 'first chance' à $762D5B68. Classe d'exception ERegistrationException avec un message 'Component type "uDAMemDataTable.TDAMemDataTable" incompatible with service type "SchemaClient_Intf.IEntiteType".'. Processus EntiteREM2.exe (3088)
Is there a way to override this check?
Ok I've found a way to do that. Super simple actually :
aContainer.RegisterType<IAddress>.DelegateTo(
function : IAddress
var aTable : TDAMemDataTable;
begin
aTable := TDAMemDataTable.Create(nil);
aTable.LogicalName := nme_Address;
aTable.BusinessRulesID := RID_Address;
Result := aTable as IAddress;
end
);
Also, for people interested in registering many tables in an elegant fashion :
aContainer.RegisterType<IAddress>.DelegateTo(TableConfigurator.GetTableDelegate<IAddress>(nme_Address, RID_Address));
// Registering other tables here...
Just create some "Helper" class with this method :
class function TableConfigurator.GetTableDelegate<T>(aLogicalName, aBusinessRulesId: string): TActivatorDelegate<T>;
begin
Result := (function: T
var
aTable: TDAMemDataTable;
begin
aTable := TDAMemDataTable.Create(nil);
aTable.LogicalName := aLogicalName;
aTable.BusinessRulesID := aBusinessRulesId;
Result := T(TValue.From(aTable).AsInterface);
end);
end;

How do I save the contents of this 2d array to a file

I Need some help trying to save the contents of the 2d array into a file.
First of all im not sure what type the file should be etc .txt or dat.
I have edited the post so that the code is in text format not an image.
This is what ive got so far.
program CaptureTheSarum;
{$APPTYPE CONSOLE}
uses
SysUtils;
Const BoardDimension = 8;
Type
TBoard = Array[1..BoardDimension, 1..BoardDimension] Of String;
Var
Board : TBoard;
GameOver : Boolean;
StartSquare : Integer;
FinishSquare : Integer;
StartRank : Integer;
StartFile : Integer;
FinishRank : Integer;
FinishFile : Integer;
MoveIsLegal : Boolean;
PlayAgain : Char;
SampleGame : Char;
WhoseTurn : Char;
savedFile : text;
procedure InitialiseSave;
var
fileName : string;
begin
fileName := 'SavedGame.dat';
assignfile(savedfile,fileName);
if not fileexists(fileName)
then
begin
rewrite(savedfile);
closefile(savedfile)
end
{endif};
end;
procedure saveGame;
var
save : string;
RankNo,FileNo : integer;
begin
writeln('Would you like to save the Game?');
readln(save);
if (save = 'y') or (save = 'Y')
then
begin
reset(SavedFile);
write(SavedFile,board[fileno,Rankno]);
closeFile(SavedFile);
end
{endif};
To answer your main question, you can save a two-dimensional string array as follows:
procedure TForm9.FileSaveClick(Sender: TObject);
var
i, j: integer;
fn: string;
fs: TFileStream;
fw: TWriter;
begin
fn := 'c:\tmp\mychessfile.dat';
fs := nil;
fw := nil;
try
fs := TFileStream.Create(fn, fmCreate or fmShareDenyWrite);
fw := TWriter.Create(fs, 1024);
for i := 1 to BoardDimension do
for j := 1 to BoardDimension do
fw.WriteString(Board[i, j]);
finally
fw.Free;
fs.Free;
end;
end;
Subsequently you can read the file back to the array with:
procedure TForm9.FileReadClick(Sender: TObject);
var
i, j: integer;
fn: string;
fs: TFileStream;
fr: TReader;
begin
fn := 'c:\tmp\mychessfile.dat';
fs := nil;
fr := nil;
try
fs := TFileStream.Create(fn, fmOpenRead or fmShareDenyWrite);
fr := TReader.Create(fs, 1024);
for i := 1 to BoardDimension do
for j := 1 to BoardDimension do
Board[i, j] := fr.ReadString;
finally
fr.Free;
fs.Free;
end;
end;
As you see I chose the general purpose .dat extension, because the file will contain also binary data, like length of each text, data type etc. Those details are dealt with by the TWriter/TReader classes.
You should also consider the comments you received regarding choise of file structure.
For example, Googling for 'chess file format' (assuming you are working on a chess game), brings up Portable_Game_Notation and another reference from that page: Forsyth-Edwards Notation.
It seems you are trying to make some sort of board game (probably chess).
The main problem you are facing is that you haven't defined your board type as fixed size. You see in Delphi strings are of dynamic size. And while in older versions of Delphi they were limited to 255 characters in newer versions their size is only limited by available memory.
So you should change your board definition (array) to be of fixed type. For most board games you could use 2D array of Char.
TBoard = Array [0..7, 0..7] of Char;
On older non-Unicode versions of Delphi Char will be an AnsiChar which allows you to store 256 different characters or 256 different figures.
On newer Delphi versions that support Unicode you have even more possibilities.
Anyway the best thing about using static array of fixed type is that you can save the whole static array into a file with a single command
procedure SaveGame;
//When having fixed size types you can use File of Type to quickly get
//ability to store whole type at once.
//Note this only works for fixed sized records who don't contain any
//dynamic sized members (strings, dynamic arrays) and static arrays of
//fixed sized type (no strings or other dynamic sized arrays)
//
//With arrays it doesn't even matter whether they are one dimensional
//or multidimensional. but they need to be static
var Savefile: File of TBoard;
FileName: String;
begin
Filename := 'D:\Proba.txt';
//Assign file
Assignfile(Savefile,FileName);
//Check if the file exists if it does open it for editing (reser)
//else open it in rewrite mode which also automatically creates new
//file if the file doesn't exists
if not Fileexists(Filename) then
Rewrite(Savefile)
else
Reset(SaveFile);
//Becouse we have a file of fixed sized type we can write the whole
//type with just one Write command
//your program already know how many bytes it has to write
//
//Note if you want to store multiple savegames in a single file you
//need to use seek to move your current position
//And because we have file of type the seek moves the current position
//by N times of the type size
//So if the size of your type is 64 bytes calling Seek(YourFile,2)
//will move current position to the 128th byte
Write(SaveFile, Board);
//Close file
CloseFile(SaveFile);
end;
Reading the data from your file is done in similar way.
Read(Savefile, Board);
EDIT: If you are on older version of Delphi and the char does not allow you enough possibilities to store the state of your board cell you can always use array of integers like most other grid based games do.
INI file like this:
[d1]
1=element1
2=element2
...
[d2]
1=element1
...
But,recommend XML, like this:
<array>
<d1>
<element1>value1</element1>
<element2>value1</element2>
...
</d1>
<d2>
<element1>value1</element1>
...
</d2>
</array>

Enum a record in Delphi

I have a record that contains a bunch of pointers that I would like to free.
type
FreeStruct = record
Addr1 : Pointer;
Addr2 : Pointer;
Addr3 : Pointer;
// ....
end;
So I would like to do something like:
var
MyStruct : FreeStruct:
begin
//MyStruct.Addr1 := ...;
for i := 0 to NumberOfFieldsInRecord do begin
VirtualFree (ValueOfFieldNumberOfMyStruct[I],0, MEM_RELEASE);
end;
end;
I heard there is something like RTII but I don't know how to use it properly with records and Delphi 7.
As far as I'm aware there are no methods to support record field iteration.
You can however play with the record structure on a low level basis (look here: http://www.delphigroups.info/2/18/295611.html)
Maybe you are better off restructuring your code so you will not need records for pointer storage.
There is no Rtti solution for your Delphi-7 version.
If you don't want to implement #ain's suggestion with an array of pointers inside a record,
here is a suggestion that uses a variant part in the record to accomplish a similar thing.
You can freely use myStruct.AddrX names for clarity in code, but also myStruct.allAddresses[X-1] to address the same field.
Type
FreeStruct =
record
intExample : Integer; // not in the variant part
case boolean of // Variant structure where the different parts overlap in memory
true : ( Addr1 : Pointer;
Addr2 : Pointer;
Addr3 : Pointer);
false : (allAddresses : array[0..2] of Pointer);
end;
procedure ClearFreeStruct( var AStruct : FreeStruct);
var
i: Integer;
begin
for i := 0 to High(AStruct.allAddresses) do
begin
// Dispose of allocated pointers
end;
end;
You can expand the structure with more pointers, but also include other fields (before the variant part).
It is possible to use an array of pointers within your record without giving up clarity.
By using an enumeration, it is possible to address each pointer with a relevant name.
Example: myStruct.Addr[peAddr1]
Type
PointerEnum = (peAddr1,peAddr2,peAddr3);
FreeStruct =
record
intExample : Integer;
Addr : array[PointerEnum] of Pointer;
end;
procedure ClearFreeStruct( var AStruct : FreeStruct);
var
pe : PointerEnum;
begin
for pe := Low(PointerEnum) to High(PointerEnum) do
begin
// Dispose(AStruct.Addr[pe]);
end;
end;

Resources