Sorting TObjectList<T> swaps equal values [duplicate] - sorting

This question already has an answer here:
How Can I Replace StringList.Sort with a Stable Sort in Delphi?
(1 answer)
Closed 1 year ago.
I have the following (simplified) class definition:
TMyObject = class
private
FDoubleValue: Double;
FText: string;
protected
public
constructor Create(ADoubleValue: Double; AText: string);
property DoubleValue: Double read FDoubleValue write FDoubleValue;
property Text: string read FText write FText;
end;
The following sample code, shows how I am sorting the TObjectList<TMyObject> (FMyObjects) and displaying them in a TListBox.
constructor TfrmMain.Create(AOwner: TComponent);
begin
inherited;
FMyObjects := TObjectList<TMyObject>.Create;
FMyObjects.OwnsObjects := true; // Default but for clarity
end;
destructor TfrmMain.Destroy;
begin
FMyObjects.Free;
inherited;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
var
ii: Integer;
begin
FMyObjects.Add(TMyObject.Create(100.00, 'Item 1'));
FMyObjects.Add(TMyObject.Create(200.00, 'Item 2'));
FMyObjects.Add(TMyObject.Create(300.00, 'Item 3')); // Duplicate sort value
FMyObjects.Add(TMyObject.Create(300.00, 'Item 4')); // Duplicate sort value
FMyObjects.Add(TMyObject.Create(400.00, 'Item 5'));
ObjectsToListBox;
end;
procedure TfrmMain.SortList;
var
Comparer: IComparer<TMyObject>;
begin
Comparer := TDelegatedComparer<TMyObject>.Create(
function(const MyObject1, MyObject2: TMyObject): Integer
begin
result := CompareValue(MyObject1.DoubleValue, MyObject2.DoubleValue, 0);
end);
FMyObjects.Sort(Comparer);
end;
procedure TfrmMain.ObjectsToListBox;
var
ii: Integer;
begin
ListBox1.Items.Clear;
for ii := 0 to FMyObjects.Count - 1 do
ListBox1.Items.Add(Format('%d - %.1f - %s', [ii, FMyObjects[ii].DoubleValue,
FMyObjects[ii].Text]));
end;
procedure TfrmMain.Button1Click(Sender: TObject);
begin
SortList;
ObjectsToListBox;
end;
Every time Button1 is clicked (and the list sorted), FMyObjects[2] (Item3) and FMyObjects[3] ('Item4') swap position in the list. In my "real world" (drawing) application this is undesirable.
I also experimented with different values for Epsilon in the CompareValue function call and also a different implementation of the anonymous function (comparing values and returning 1, -1 or 0), but neither seems to make a difference.
Am I missing something (e.g. a property that controls this behavior) or is this "by design" and it cannot be prevented?

This is by design. The internally used Quicksort implementation is not a stable one, so reorder of equal items is expected. To make the sort stable you need to extend your comparer to take that into account. F.i. you can compare the Text properties when the DoubleValue properties are equal.

Related

Delphi - Sort TList<TObject> based on the object's properties [duplicate]

I'm kinda a Delphi-newbie and I don't get how the Sort method of a TList of Records is called in order to sort the records by ascending integer value.
I have a record like the following:
type
TMyRecord = record
str1: string;
str2: string;
intVal: integer;
end;
And a generic list of such records:
TListMyRecord = TList<TMyRecord>;
Have tried to find a code-example in the help files and found this one:
MyList.Sort(#CompareNames);
Which I can't use, since it uses classes. So I tried to write my own compare function with a little different parameters:
function CompareIntVal(i1, i2: TMyRecord): Integer;
begin
Result := i1.intVal - i2.intVal;
end;
But the compiler always throws a 'not enough parameters' - error when I call it with open.Sort(CompareIntVal);, which seems obvious; so I tried to stay closer to the help file:
function SortKB(Item1, Item2: Pointer): Integer;
begin
Result:=PMyRecord(Item1)^.intVal - PMyRecord(Item2)^.intVal;
end;
with PMyRecord as PMyRecord = ^TMyRecord;
I have tried different ways of calling a function, always getting some error...
The Sort overload you should be using is this one:
procedure Sort(const AComparer: IComparer<TMyRecord>);
Now, you can create an IComparer<TMyRecord> by calling TComparer<TMyRecord>.Construct. Like this:
var
Comparison: TComparison<TMyRecord>;
....
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal-Right.intVal;
end;
List.Sort(TComparer<TMyRecord>.Construct(Comparison));
I've written the Comparison function as an anonymous method, but you could also use a plain old style non-OOP function, or a method of an object.
One potential problem with your comparison function is that you may suffer from integer overflow. So you could instead use the default integer comparer.
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := TComparer<Integer>.Default.Compare(Left.intVal, Right.intVal);
end;
It might be expensive to call TComparer<Integer>.Default repeatedly so you could store it away in a global variable:
var
IntegerComparer: IComparer<Integer>;
....
initialization
IntegerComparer := TComparer<Integer>.Default;
Another option to consider is to pass in the comparer when you create the list. If you only ever sort the list using this ordering then that's more convenient.
List := TList<TMyRecord>.Create(TComparer<TMyRecord>.Construct(Comparison));
And then you can sort the list with
List.Sort;
The concise answer:
uses
.. System.Generics.Defaults // Contains TComparer
myList.Sort(
TComparer<TMyRecord>.Construct(
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal - Right.intVal;
end
)
);
I want to share my solution (based on the input I have gathered here).
It's a standard setup. A filedata class that holds data of a single file in a generic TObjectList. The list has the two private attributes fCurrentSortedColumn and fCurrentSortAscending to control the sort order. The AsString-method is the path and filename combined.
function TFileList.SortByColumn(aColumn: TSortByColums): boolean;
var
Comparison: TComparison<TFileData>;
begin
result := false;
Comparison := nil;
case aColumn of
sbcUnsorted : ;
sbcPathAndName: begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcSize : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<int64>.Default.Compare(Left.Size,Right.Size);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcDate : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TDateTime>.Default.Compare(Left.Date,Right.Date);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcState : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TFileDataTestResults>.Default.Compare(Left.FileDataResult,Right.FileDataResult);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
end;
if assigned(Comparison) then
begin
Sort(TComparer<TFileData>.Construct(Comparison));
// Control the sort order
if fCurrentSortedColumn = aColumn then
fCurrentSortAscending := not fCurrentSortAscending
else begin
fCurrentSortedColumn := aColumn;
fCurrentSortAscending := true;
end;
if not fCurrentSortAscending then
Reverse;
result := true;
end;
end;
I found a much simpler modified sort function to alphabetize a TList of records or nonstandard list of items.
Example
PList = ^TContact;
TContact = record //Record for database of user contact records
firstname1 : string[20];
lastname1 : string[20];
phonemobile : Integer; //Fields in the database for contact info
phonehome : Integer;
street1 : string;
street2 : string;
type
TListSortCompare = function (Item1,
Item2: TContact): Integer;
var
Form1: TForm1;
Contact : PList; //declare record database for contacts
arecord : TContact;
Contacts : TList; //List for the Array of Contacts
function CompareNames(i1, i2: TContact): Integer;
begin
Result := CompareText(i1.lastname1, i2.lastname1) ;
end;
and the function to call to sort your list
Contacts.Sort(#CompareNames);

Delphi: Store multiple Sortings of TObjectList

I have a bunch of TCoordinates which are stored in a TObjectList. To find a Coordinate faster the List must be sorted. The problem is that iam alternating searching for x and y. Is there a build in way to store the outcome of the sorting, so i dont need to sort the list again and again.
unit uStackoverflowQuestion;
interface
uses
System.Generics.Collections, System.Generics.defaults;
type
TCoordinate = class(Tobject)
public
x: Integer;
y: Integer;
end;
TMultipleSortedList = class(TObjectlist<TCoordinate>)
public
// StoredSortingByX: List;
// StoredSortingByY: List;
procedure SortAndStoreByX;
procedure SortAndStoreByY;
end;
implementation
procedure TMultipleSortedList.SortAndStoreByX;
begin
// TODO -cMM: TMultipleSortedList.SortAndStoreByX default body inserted
end;
procedure TMultipleSortedList.SortAndStoreByY;
begin
// TODO -cMM: TMultipleSortedList.SortAndStoreByY default body inserted
end;
end.
Create an index map to represent the two different orders. This is simply a dynamic array of integer.
type
TListOrder = TArray<Integer>;
When you wish to read an item using that order you do so like this:
function GetItem(Index: Integer; const Order: TListOrder): TItem;
begin
Result := List[Order[Index]];
end;
The key point here is that we don't modify the content of List ever. We regard that as unordered. Instead, we hold the order separate to the container. That allows us to have multiple such orders.
The next question is how to create an order. First of all populate the order with all the valid indices:
var
i: Integer;
Order: TListOrder;
....
SetLength(Order, List.Count);
for i := 0 to List.Count-1 do
Order[i] := i;
Now you can sort the order like so:
TArray.Sort<Integer>(Order, Comparer);
Finally, what to use as the comparer. This is where the magic happens.
var
Comparer: IComparer<Integer>;
....
Comparer :=
function(const Left, Right: Integer): Integer
var
LeftItem, RightItem: TItem;
begin
LeftItem := GetItem(Left, Order);
RightItem := GetItem(Right, Order);
Result := ...; // your compare logic goes here
end;
And that's it.
If the objects in your list do not change, you can use a TList<> to store an additional reference to the objects instead of the integer array that David Heffernan suggested. It has a small advantage in access time.

Strange behavior with TThread.CreateAnonymousThread

I was unable to follow how it is working.
A very simple example first, to try explain my situation better.
This code is inside a new Form Form1 create in a new project. Where mmo1 is a Memo component.
TOb = class
Name : String;
constructor Create(Name : String);
procedure Go();
end;
procedure TOb.Go;
begin
Form1.mmo1.Lines.Add(Name);
end;
Then I have a button with this event:
procedure TForm1.btn4Click(Sender: TObject);
var
Index : Integer;
begin
mmo1.Lines.Clear;
for Index := 1 to 3 do
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(Index)).Go).Start;
end;
And my output on the memo is:
Thread 4
Thread 4
Thread 4
I really don't got it.
First question: Why the "Name" output is: Thread 4? Is a For loop from 1 to 3. At least should be 1 or 3
Second: Why it only execute the last thread "Thread 4", instead of 3 times in sequence "Thread 1", "Thread 2", "Thread 3"?
Why I'm asking this? I have an object that has already a process working fine. But now I found me in a situation that I need a List of this object to be processed. Sure work fine process one by one, but in my case they are independent one of other so I thought "hm, lets put them in threads, so it will run faster".
To avoid modifying the object to extend TThread and overriding Execute I look up on how to execute a thread with a procedure instead of an object that inherits from TThread and found the Anonymous Thread. Works really great with one object, but when I tried loop through my object list, strange behaviors happens.
This has the same effect.
for Index := 1 to 3 do
TThread.CreateAnonymousThread(
procedure
var
Ob : TOb;
begin
OB := TOb.Create('Thread ' + IntToStr(Index));
OB.Go;
end
).Start;
Sure I'm not clean the object, this was just some tests that I was running.
Any Ideas? Or in this case I will need to inherits from TThread and override the Execute methode?
The funny thing is that THIS runs just fine.
mmo1.Lines.Clear;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(1)).Go).Start;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(2)).Go).Start;
TThread.CreateAnonymousThread(TOb.Create('Thread ' + IntToStr(3)).Go).Start;
Output:
Thread 1
Thread 2
Thread 3
Works really great with one object, but when I tried loop through my object list, strange behaviors happens.
You are likely not taking into account how anonymous procedures bind to variables. In particular:
Note that variable capture captures variables--not values. If a variable's value changes after being captured by constructing an anonymous method, the value of the variable the anonymous method captured changes too, because they are the same variable with the same storage. Captured variables are stored on the heap, not the stack.
For example, if you do something like this:
var
Index: Integer;
begin
for Index := 0 to ObjList.Count-1 do
TThread.CreateAnonymousThread(TOb(ObjList[Index]).Go).Start;
end;
You will actually cause an EListError exception in the threads (I least when I tested it - I don't know why it happens. Verified by assigning an OnTerminate handler to the threads before calling Start(), and then having that handler check the TThread(Sender).FatalException property).
If you do this instead:
var
Index: Integer;
Ob: TOb;
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
TThread.CreateAnonymousThread(Ob.Go).Start;
end;
end;
The threads won't crash anymore, but they are likely to operate on the same TOb object, because CreateAnonymousThread() is taking a reference to the TOb.Go() method itself, and then your loop is modifying that reference's Self pointer on each iteration. I suspect the compiler is likely generating code similar to this:
var
Index: Integer;
Ob: TOb;
Proc: TProc; // <-- silently added
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
Proc := Ob.Go; // <-- silently added
TThread.CreateAnonymousThread(Proc).Start;
end;
end;
If you do this instead, it will have a similar issue:
procedure StartThread(Proc: TProc);
begin
TThread.CreateAnonymousThread(Proc).Start;
end;
...
var
Index: Integer;
Ob: TOb;
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
StartThread(Ob.Go);
end;
end;
Probably because the compiler generates code similar to this:
procedure StartThread(Proc: TProc);
begin
TThread.CreateAnonymousThread(Proc).Start;
end;
...
var
Index: Integer;
Ob: TOb;
Proc: TProc; // <--
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
Proc := Ob.Go; // <--
StartThread(Proc);
end;
end;
This will work fine, though:
procedure StartThread(Ob: TOb);
begin
TThread.CreateAnonymousThread(Ob.Go).Start;
end;
...
var
Index: Integer;
Ob: TOb;
begin
for Index := 0 to ObjList.Count-1 do
begin
Ob := TOb(ObjList[Index]);
StartThread(Ob);
// or just: StartThread(TOb(ObjList[Index]));
end;
end;
By moving the call to CreateAnonymousThread() into a separate procedure that isolates the actual reference to TOb.Go() into a local variable, you remove any chance of conflict in capturing the reference for multiple objects.
Anonymous procedures are funny that way. You have to be careful with how they capture variables.
After reading a the article that Remy Lebeau post on the comments, I found this solution.
changing the main object by add one more procedure that make the call.
Change the loop instead of creating the anonymous thread at the main loop, it is created inside the object.
TOb = class
Name : String;
constructor Create(Name : String);
procedure Process();
procedure DoWork();
end;
procedure TOb.Process;
begin
TThread.CreateAnonymousThread(DoWork).Start;
end;
procedure TOb.DoWork;
var
List : TStringList;
begin
List := TStringList.Create;
List.Add('I am ' + Name);
List.Add(DateTimeToStr(Now));
List.SaveToFile('D:\file_' + Name + '.txt');
List.Free;
end;
And the loop:
List := TObjectList<TOb>.Create();
List.Add(TOb.Create('Thread_A'));
List.Add(TOb.Create('Thread_B'));
List.Add(TOb.Create('Thread_C'));
List.Add(TOb.Create('Thread_D'));
for Obj in List do
//TThread.CreateAnonymousThread(Obj.Go).Start;
Obj.Process;
Thats resolves the problem with just a minimum change on the Main Object.
This about race condition. When you increased to max value to 100, you will see different values. Threading not guarantee when Thread starts or ends.
You can try this code block.
for I := 1 to 100 do
begin
TThread.CreateAnonymousThread(
procedure
var
Msg : string;
begin
try
Msg := 'This' + I.ToString;
MessageDlg(Msg,mtCustom,
[mbYes,mbAll,mbCancel], 0);
Except
on E: Exception do
End;
end
).Start;
end;
If you want a guarantee to write 1 to 4, you should instantiate every value before send to Thread.
for I := 1 to 100 do
begin
TThread.CreateAnonymousThread(
procedure
var
Msg : string;
begin
var instanceValue := I;
try
Msg := 'This' + instanceValue.ToString;
MessageDlg(Msg,mtCustom,
[mbYes,mbAll,mbCancel], 0);
Except
on E: Exception do
End;
end
).Start;
end;

pass two difference arrays for same array

I was trying to use same printing procedure for two types of arrays(1st arry length was 10, 2nd array length was 15).
I could not find any solution over internet. Did any one have any solution for this problem.
this is the Two arrays
program pp1;
const
m=10;
n=15;
type
matrix1=array[1..m] of integer;
matrix2=array[1..n] of integer;
var
m1:matrix1;
m2:matrix2;
this is the method which it tried. in method 'x' mens the length of the array.
procedure writeMatrix(var data: array of integer ;x:integer);
var
j:integer;
begin
for j:=1 to x do
begin
write(data[j]:3);
end;
end;
my main method
begin
writeMatrix(m1,10);
writeMatrix(m2,10);
end.
How can i use the same this writeMatrix method to print both of the arrays.. Is there any stranded way to do it.
As I said in my comment before, your implementation is fine, but you have to put something in your matrix before printing it, or you will get a bunch of zeroes in the screen (in the best).
Try this:
program pp1;
const
m=10;
n=15;
type
matrix1=array[1..m] of integer;
matrix2=array[1..n] of integer;
var
m1:matrix1;
m2:matrix2;
procedure fillMatrix(var data:array of integer; x:integer);
var
j:integer;
begin
for j:= 1 to x do begin
data[j]:=j;
end;
end;
procedure writeMatrix(var data: array of integer; x:integer);
var
j:integer;
begin
for j:=1 to x do
begin
write(data[j]:3);
end;
end;
begin
fillMatrix(m1,10);
fillMatrix(m2,10);
writeMatrix(m1,10);
writeMatrix(m2,10);
readln;
readln;
end.
Hint: consider avoid using global variables, m1 and m2 in this case should be declared in the main program.
How can i use the same this writeMatrix method to print both of the arrays.. Is there any stranded way to do it.
Yes, there is a standard way to this. It is called conformant-array parameters. It is standardized in (level 1) of the ISO standard 7185 (Standard “Unextended” Pascal). It looks like this:
procedure print(protected matrix: array[
columnMinimum..columnMaximum: integer;
rowMinimum..rowMaximum: integer
] of integer);
const
totalWidth = 6;
var
x: type of columnMinimum;
y: type of rowMinimum;
begin
for y := rowMinimum to rowMaximum do
begin
for x := columnMinimum to columnMaximum do
begin
write(matrix[x, y]:totalWidth);
end;
writeLn;
end;
end;
It’s as if there were additional const values, but they are dynamic depending on the passed matrix. This code furthermore uses type inquiries (type of …) and the protected modifier, both defined in ISO 10206 (Extended Pascal) which builds on top of ISO 7185. In EP you could and would also consider schemata to pass such data as parameters.

saving a records containing a member of type string to a file (Delphi, Windows)

I have a record that looks similar to:
type
TNote = record
Title : string;
Note : string;
Index : integer;
end;
Simple. The reason I chose to set the variables as string (as opposed to an array of chars) is that I have no idea how long those strings are going to be. They can be 1 char long, 200 or 2000.
Of course when I try to save the record to a type file (file of...) the compiler complains that I have to give a size to string.
Is there a way to overcome this? or a way to save those records to an untyped file and still maintain a sort of searchable way?
Please do not point me to possible solutions, if you know the solution please post code.
Thank you
You can't do it with a typed file. Try something like this, with a TFileStream:
type
TStreamEx = class helper for TStream
public
procedure writeString(const data: string);
function readString: string;
procedure writeInt(data: integer);
function readInt: integer;
end;
function TStreamEx.readString: string;
var
len: integer;
iString: UTF8String;
begin
self.readBuffer(len, 4);
if len > 0 then
begin
setLength(iString, len);
self.ReadBuffer(iString[1], len);
result := string(iString);
end;
end;
procedure TStreamEx.writeString(const data: string);
var
len: cardinal;
oString: UTF8String;
begin
oString := UTF8String(data);
len := length(oString);
self.WriteBuffer(len, 4);
if len > 0 then
self.WriteBuffer(oString[1], len);
end;
function TStreamEx.readInt: integer;
begin
self.readBuffer(result, 4);
end;
procedure TStreamEx.writeInt(data: integer);
begin
self.WriteBuffer(data, 4);
end;
type
TNote = record
Title : string;
Note : string;
Index : integer;
procedure Save(stream: TStream);
end;
procedure TNote.Save(stream: TStream);
var
temp: TMemoryStream;
begin
temp := TMemoryStream.Create;
try
temp.writeString(Title);
temp.writeString(Note);
temp.writeInt(Index);
temp.seek(0, soFromBeginning);
stream.writeInt(temp.size);
stream.copyFrom(temp, temp.size);
finally
temp.Free;
end;
end;
I'll leave the Load procedure to you. Same basic idea, but it shouldn't need a temp stream. With the record size in front of each entry, you can read it and know how far to skip if you're looking for a certain record # instead of reading the whole thing.
EDIT: This was written specifically for versions of Delphi that use Unicode strings. On older versions, you could simplify it quite a bit.
Why not write this out as XML? See my session "Practical XML with Delphi" on how to get started with this.
Another possibility would be to make your records into classes descending form TComponent and store/retreive your data in DFM files.
This Stackoverflow entry shows you how to do that.
--jeroen
PS: Sorry my XML answer was a bit dense; I'm actually on the road for two conferences (BASTA! and DelphiLive! Germany).
Basically what you need to do is very simple: create a sample XML file, then start the Delphi XML Data Binding Wizard (available in Delphi since version 6).
This wizard will generate a unit for you that has the interfaces and classes mapping XML to Delphi objects, and a few helper functions for reading them from file, creating a new object, etc. My session (see the first link above) actually contains most of the details for this process.
The above link is a video demonstrating the usage of the Delphi XML Data Binding Wizard.
You could work with two different files, one that just stores the strings in some convenient way, the other stores the records with a reference to the strings. That way you will still have a file of records for easy access even though you don't know the size of the actual content.
(Sorry no code.)
TNote = record
Title : string;
Note : string;
Index : integer;
end;
could be translated as
TNote = record
Title : string[255];
Note : string[255];
Index : integer;
end;
and use Stream.writebuffer(ANodeVariable, sizeof(TNode), but you said that strings get go over 255 chars in this case IF a string goes over 65535 chars then change WORD to INTEGER
type
TNodeHeader=Record
TitleLen,
NoteLen: Word;
end;
(* this is for writing a TNode *)
procedure saveNodetoStream(theNode: TNode; AStream: TStream);
var
header: TNodeHeader;
pStr: PChar;
begin
...
(* writing to AStream which should be initialized before this *)
Header.TitleLen := Length(theNode.Title);
header.NodeLen := Length(theNode.Note);
AStream.WriteBuffer(Header, sizeof(TNodeHeader);
(* save strings *)
PStr := PChar(theNode.Title);
AStream.writeBuffer(PStr^, Header.TitleLen);
PStr := PChar(theNode.Note);
AStream.writebuffer(PStr^, Header.NoteLen);
(* save index *)
AStream.writebuffer(theNode.Index, sizeof(Integer));
end;
(* this is for reading a TNode *)
function readNode(AStream: TStream): TNode;
var
header: THeader
PStr: PChar;
begin
AStream.ReadBuffer(Header, sizeof(TNodeHeader);
SetLength(Result.Title, Header.TitleLen);
PStr := PChar(Result.Title);
AStream.ReadBuffer(PStr^, Header.TitleLen);
SetLength(Result.Note, Header.NoteLen);
PStr := PChar(Result.Note);
AStream.ReadBuffer(PStr^, Header.NoteLen);
AStream.ReadBuffer(REsult.Index, sizeof(Integer)(* 4 bytes *);
end;
You can use the functions available in this Open Source unit.
It allows you to serialize any record content into binary, including even dynamic arrays within:
type
TNote = record
Title : string;
Note : string;
Index : integer;
end;
var
aSave: TRawByteString;
aNote, aNew: TNote;
begin
// create some content
aNote.Title := 'Title';
aNote.Note := 'Note';
aNote.Index := 10;
// serialize the content
aSave := RecordSave(aNote,TypeInfo(TNote));
// unserialize the content
RecordLoad(aNew,pointer(aSave),TypeInfo(TNote));
// check the content
assert(aNew.Title = 'Title');
assert(aNew.Note = 'Note');
assert(aNew.Index = 10);
end;

Resources